26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
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);
|
|
}
|
|
}
|