mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-01-07 19:49:01 +08:00
Replaces legacy tag fetching logic in napcat-common with a new mirror.ts module that centralizes GitHub mirror configuration, selection, and tag retrieval. Updates helper.ts to use the new mirror system and semver comparison, and exports compareSemVer for broader use. Updates workflows and scripts to generate and propagate build version information, and improves build status comment formatting for PRs. Also updates release workflow to use a new OpenAI key and model.
131 lines
4.4 KiB
TypeScript
131 lines
4.4 KiB
TypeScript
import https from 'node:https';
|
||
import http from 'node:http';
|
||
|
||
export class RequestUtil {
|
||
// 适用于获取服务器下发cookies时获取,仅GET
|
||
static async HttpsGetCookies (url: string): Promise<{ [key: string]: string; }> {
|
||
const client = url.startsWith('https') ? https : http;
|
||
return new Promise((resolve, reject) => {
|
||
const req = client.get(url, (res) => {
|
||
const cookies: { [key: string]: string; } = {};
|
||
|
||
res.on('data', () => { }); // Necessary to consume the stream
|
||
res.on('end', () => {
|
||
this.handleRedirect(res, url, cookies)
|
||
.then(resolve)
|
||
.catch(reject);
|
||
});
|
||
|
||
if (res.headers['set-cookie']) {
|
||
this.extractCookies(res.headers['set-cookie'], cookies);
|
||
}
|
||
});
|
||
|
||
req.on('error', (error: Error) => {
|
||
reject(error);
|
||
});
|
||
});
|
||
}
|
||
|
||
private static async handleRedirect (res: http.IncomingMessage, url: string, cookies: { [key: string]: string; }): Promise<{ [key: string]: string; }> {
|
||
if (res.statusCode === 301 || res.statusCode === 302) {
|
||
if (res.headers.location) {
|
||
const redirectUrl = new URL(res.headers.location, url);
|
||
const redirectCookies = await this.HttpsGetCookies(redirectUrl.href);
|
||
// 合并重定向过程中的cookies
|
||
return { ...cookies, ...redirectCookies };
|
||
}
|
||
}
|
||
return cookies;
|
||
}
|
||
|
||
private static extractCookies (setCookieHeaders: string[], cookies: { [key: string]: string; }) {
|
||
setCookieHeaders.forEach((cookie) => {
|
||
const parts = cookie.split(';')[0]?.split('=');
|
||
if (parts) {
|
||
const key = parts[0];
|
||
const value = parts[1];
|
||
if (key && value && key.length > 0 && value.length > 0) {
|
||
cookies[key] = value;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
// 请求和回复都是JSON data传原始内容 自动编码json
|
||
// 支持 301/302 重定向(最多 5 次)
|
||
static async HttpGetJson<T> (url: string, method: string = 'GET', data?: any, headers: {
|
||
[key: string]: string;
|
||
} = {}, isJsonRet: boolean = true, isArgJson: boolean = true, maxRedirects: number = 5): Promise<T> {
|
||
const option = new URL(url);
|
||
const protocol = url.startsWith('https://') ? https : http;
|
||
const options = {
|
||
hostname: option.hostname,
|
||
port: option.port,
|
||
path: option.pathname + option.search,
|
||
method,
|
||
headers,
|
||
};
|
||
// headers: {
|
||
// 'Content-Type': 'application/json',
|
||
// 'Content-Length': Buffer.byteLength(postData),
|
||
// },
|
||
return new Promise((resolve, reject) => {
|
||
const req = protocol.request(options, (res: http.IncomingMessage) => {
|
||
// 处理重定向
|
||
if ((res.statusCode === 301 || res.statusCode === 302 || res.statusCode === 307 || res.statusCode === 308) && res.headers.location) {
|
||
if (maxRedirects <= 0) {
|
||
reject(new Error('Too many redirects'));
|
||
return;
|
||
}
|
||
const redirectUrl = new URL(res.headers.location, url).href;
|
||
// 递归跟随重定向
|
||
this.HttpGetJson<T>(redirectUrl, method, data, headers, isJsonRet, isArgJson, maxRedirects - 1)
|
||
.then(resolve)
|
||
.catch(reject);
|
||
return;
|
||
}
|
||
|
||
let responseBody = '';
|
||
res.on('data', (chunk: string | Buffer) => {
|
||
responseBody += chunk.toString();
|
||
});
|
||
|
||
res.on('end', () => {
|
||
try {
|
||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||
if (isJsonRet) {
|
||
const responseJson = JSON.parse(responseBody);
|
||
resolve(responseJson as T);
|
||
} else {
|
||
resolve(responseBody as T);
|
||
}
|
||
} else {
|
||
reject(new Error(`Unexpected status code: ${res.statusCode}`));
|
||
}
|
||
} catch (parseError: unknown) {
|
||
reject(new Error((parseError as Error).message));
|
||
}
|
||
});
|
||
});
|
||
|
||
req.on('error', (error: Error) => {
|
||
reject(error);
|
||
});
|
||
if (method === 'POST' || method === 'PUT' || method === 'PATCH') {
|
||
if (isArgJson) {
|
||
req.write(JSON.stringify(data));
|
||
} else {
|
||
req.write(data);
|
||
}
|
||
}
|
||
req.end();
|
||
});
|
||
}
|
||
|
||
// 请求返回都是原始内容
|
||
static async HttpGetText (url: string, method: string = 'GET', data?: any, headers: { [key: string]: string; } = {}) {
|
||
return this.HttpGetJson<string>(url, method, data, headers, false, false);
|
||
}
|
||
}
|