244 lines
8.5 KiB
TypeScript
244 lines
8.5 KiB
TypeScript
import http from 'node:http';
|
|
import {
|
|
BadRequestError,
|
|
InternalEndpointError,
|
|
UnknownError,
|
|
} from './Errors.js';
|
|
import {
|
|
IOptions,
|
|
IBaseAnswer,
|
|
TPostData,
|
|
ISessionsAnswer,
|
|
ISessionCreateOptions,
|
|
ISessionAnswer,
|
|
IRequestGetOptions,
|
|
IRequestPostOptions,
|
|
IRequestAnswer,
|
|
IRawRequestAnswer,
|
|
} from './Interfaces.js';
|
|
|
|
export class FlareSolverr {
|
|
private readonly endpoint: URL;
|
|
|
|
/**
|
|
* Default settings
|
|
* @alpha
|
|
* @internal
|
|
*/
|
|
private readonly options: IOptions = {
|
|
limit: 3,
|
|
};
|
|
|
|
constructor(endpoint: string | URL, options?: Partial<IOptions>) {
|
|
this.endpoint = new URL(endpoint.toString());
|
|
Object.assign(this.options, options);
|
|
}
|
|
|
|
/**
|
|
* Makes a request and returns a response object of type T.
|
|
* @param data - Data to send
|
|
* @returns A Promise that resolves to the response object of type T
|
|
*
|
|
* @throws InternalEndpointError - If there is an error on the internal server
|
|
* @throws UnknownError - If there is an unknown error
|
|
* @throws BadRequestError - If the response status is 'error'
|
|
* @throws Error - If any node.js errors happened
|
|
*
|
|
* @internal
|
|
*/
|
|
private _makeRequest<T extends IBaseAnswer>(data: TPostData): Promise<T> {
|
|
return new Promise<T>((resolve, reject) => {
|
|
let _resp_data = '';
|
|
const resp = http.request(
|
|
this.endpoint,
|
|
{
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
},
|
|
(resp) => {
|
|
resp.setEncoding('utf8');
|
|
resp.on('data', (chunk) => (_resp_data += chunk));
|
|
resp.on('error', (error) => {
|
|
reject(error);
|
|
});
|
|
resp.on('end', () => {
|
|
switch (resp.statusCode) {
|
|
case 500: {
|
|
try {
|
|
const answer = JSON.parse(_resp_data) as {
|
|
error?: string;
|
|
} & IBaseAnswer;
|
|
if (answer.error) {
|
|
reject(
|
|
new InternalEndpointError(
|
|
answer.error,
|
|
),
|
|
);
|
|
} else {
|
|
if (answer.status !== undefined) {
|
|
reject(new BadRequestError(answer));
|
|
}
|
|
}
|
|
} finally {
|
|
reject(
|
|
new InternalEndpointError(
|
|
'Unknown error',
|
|
),
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
case 200: {
|
|
resolve(JSON.parse(_resp_data) as T);
|
|
break;
|
|
}
|
|
default: {
|
|
reject(
|
|
new InternalEndpointError(
|
|
`Unknown error, ${
|
|
resp.statusCode ?? 'XXX'
|
|
} response code`,
|
|
),
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
},
|
|
);
|
|
resp.on('error', (error_) => {
|
|
if (Object.keys(error_).includes('code')) {
|
|
const code = (error_ as Error & { code: string }).code;
|
|
const error = new UnknownError();
|
|
switch (code) {
|
|
case 'ECONNREFUSED': {
|
|
error.message = 'Is there correct endpoint url?';
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (error.stack) {
|
|
error.stack = error.stack.replace(
|
|
'\n ',
|
|
'\n ' +
|
|
'Original error message:' +
|
|
error_.message +
|
|
'\n ',
|
|
);
|
|
}
|
|
reject(error);
|
|
} else {
|
|
reject(error_);
|
|
}
|
|
});
|
|
|
|
resp.end(JSON.stringify(data));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Retrieves a list of current sessions.
|
|
* @remarks All possible errors you can see in the {@link FlareSolverr._makeRequest | _makeRequest} method
|
|
* @returns Promise that is resolved to an object of type ISessionsAnswer.
|
|
*/
|
|
async getSessions() {
|
|
return await this._makeRequest<ISessionsAnswer>({
|
|
cmd: 'sessions.list',
|
|
});
|
|
}
|
|
|
|
private _getProxyFromURL(proxy: URL) {
|
|
return {
|
|
url: proxy.href,
|
|
username: proxy.username,
|
|
password: proxy.password,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Creates a session.
|
|
* @remarks All possible errors you can see in the {@link FlareSolverr._makeRequest | _makeRequest} method
|
|
* @returns Promise that is resolved to an object of type ISessionAnswer.
|
|
*/
|
|
async createSession(options?: Partial<ISessionCreateOptions>) {
|
|
return await this._makeRequest<ISessionAnswer>({
|
|
cmd: 'sessions.create',
|
|
session: options?.session,
|
|
proxy:
|
|
options?.proxy === undefined
|
|
? undefined
|
|
: this._getProxyFromURL(options.proxy),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Destroys a session.
|
|
* @remarks All possible errors you can see in the {@link FlareSolverr._makeRequest | _makeRequest} method
|
|
* @returns Promise that is resolved to an object of type IBaseAnswer.
|
|
*
|
|
*/
|
|
async destroySession(session: string) {
|
|
return await this._makeRequest<IBaseAnswer>({
|
|
cmd: 'sessions.destroy',
|
|
session: session,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This method works like a normal get request, but bypasses cloudflare's protection if cloudflare is present
|
|
* @remarks All possible errors you can see in the {@link FlareSolverr._makeRequest | _makeRequest} method
|
|
* @returns Promise that is resolved to an object of type IRequestAnswer.
|
|
*/
|
|
async get(options: IRequestGetOptions): Promise<IRequestAnswer> {
|
|
const response = await this._makeRequest<IRawRequestAnswer>({
|
|
cmd: 'request.get',
|
|
...options,
|
|
proxy:
|
|
options.proxy === undefined
|
|
? undefined
|
|
: this._getProxyFromURL(options.proxy),
|
|
});
|
|
return {
|
|
...response,
|
|
solution: {
|
|
...response.solution,
|
|
url: new URL(response.solution.url),
|
|
},
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This method works like a normal post request, but bypasses cloudflare's protection if cloudflare is present
|
|
* @remarks All possible errors you can see in the {@link FlareSolverr._makeRequest | _makeRequest} method
|
|
* @returns Promise that is resolved to an object of type IRequestAnswer.
|
|
*/
|
|
async post(options: IRequestPostOptions): Promise<IRequestAnswer> {
|
|
const response = await this._makeRequest<IRawRequestAnswer>({
|
|
cmd: 'request.post',
|
|
...options,
|
|
proxy:
|
|
options.proxy === undefined
|
|
? undefined
|
|
: this._getProxyFromURL(options.proxy),
|
|
postData: Object.keys(options.postData)
|
|
.map(
|
|
(key) =>
|
|
encodeURIComponent(key) +
|
|
'=' +
|
|
encodeURIComponent(options.postData[key]),
|
|
)
|
|
.join('&'),
|
|
});
|
|
|
|
return {
|
|
...response,
|
|
solution: {
|
|
...response.solution,
|
|
url: new URL(response.solution.url),
|
|
},
|
|
};
|
|
}
|
|
}
|