kt-template-online-api/src/modules/qqbot/plugin-platform/infrastructure/integration/sdk/plugin-http-client.service.ts

163 lines
5.1 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import * as http from 'node:http';
import * as https from 'node:https';
export type QqbotPluginHttpClientRequest = {
body?: Buffer | string;
context?: string;
failureMessage?: (statusCode: number) => string;
headers?: Record<string, string>;
invalidJsonMessage?: string;
method?: string;
timeoutMessage?: string;
timeoutMs?: number;
url: string | URL;
};
@Injectable()
export class QqbotPluginHttpClientService {
/**
* 执行 QQBot 插件平台流程。
* @param input - input 输入;使用 `invalidJsonMessage`、`context` 字段生成结果。
* @returns 异步完成后的 QQBot 插件平台结果。
*/
async requestJson<T>(input: QqbotPluginHttpClientRequest): Promise<T> {
const body = await this.requestText(input);
try {
return JSON.parse(body) as T;
} catch {
throw new Error(
input.invalidJsonMessage ||
`${input.context || '插件 HTTP 接口'}返回不是合法 JSON`,
);
}
}
/**
* 执行 QQBot 插件平台流程。
* @param input - input 输入;使用 `url`、`method`、`timeoutMs`、`context` 字段生成结果。
* @returns QQBot 插件平台渲染后的图片、画布或文本。
*/
requestBuffer(input: QqbotPluginHttpClientRequest): Promise<Buffer> {
const url = input.url instanceof URL ? input.url : new URL(input.url);
const method = input.method || 'GET';
const timeoutMs = input.timeoutMs || 8000;
const context = input.context || '插件 HTTP 接口';
return new Promise<Buffer>((resolve, reject) => {
const client = url.protocol === 'http:' ? http : https;
const request = client.request(
url,
{
headers: {
Accept: '*/*',
'User-Agent': 'kt-template-online-api/qqbot-plugin',
...(input.headers || {}),
},
method,
timeout: timeoutMs,
},
(response) => {
const chunks: Buffer[] = [];
response.on('data', (chunk) => {
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
});
response.on('end', () => {
const statusCode = response.statusCode || 500;
if (statusCode >= 400) {
reject(
createPluginHttpError(
input.failureMessage?.(statusCode) ||
`${context}请求失败:${statusCode}`,
statusCode,
),
);
return;
}
resolve(Buffer.concat(chunks));
});
},
);
request.on('timeout', () => {
request.destroy(
new Error(input.timeoutMessage || `${context}请求超时`),
);
});
request.on('error', reject);
if (input.body) request.write(input.body);
request.end();
});
}
/**
* 执行 QQBot 插件平台流程。
* @param input - input 输入;使用 `url`、`method`、`timeoutMs`、`context` 字段生成结果。
* @returns QQBot 插件平台渲染后的图片、画布或文本。
*/
requestText(input: QqbotPluginHttpClientRequest): Promise<string> {
const url = input.url instanceof URL ? input.url : new URL(input.url);
const method = input.method || 'GET';
const timeoutMs = input.timeoutMs || 8000;
const context = input.context || '插件 HTTP 接口';
return new Promise<string>((resolve, reject) => {
const client = url.protocol === 'http:' ? http : https;
const request = client.request(
url,
{
headers: {
Accept: 'application/json',
'User-Agent': 'kt-template-online-api/qqbot-plugin',
...(input.headers || {}),
},
method,
timeout: timeoutMs,
},
(response) => {
let responseBody = '';
response.setEncoding('utf8');
response.on('data', (chunk) => {
responseBody += chunk;
});
response.on('end', () => {
const statusCode = response.statusCode || 500;
if (statusCode >= 400) {
reject(
createPluginHttpError(
input.failureMessage?.(statusCode) ||
`${context}请求失败:${statusCode}`,
statusCode,
),
);
return;
}
resolve(responseBody);
});
},
);
request.on('timeout', () => {
request.destroy(
new Error(input.timeoutMessage || `${context}请求超时`),
);
});
request.on('error', reject);
if (input.body) request.write(input.body);
request.end();
});
}
}
/**
* 创建 QQBot 插件平台对象或配置。
* @param message - message 输入;驱动 `Object.assign()` 的 插件平台步骤。
* @param statusCode - statusCode 输入;生成 插件平台对象。
*/
function createPluginHttpError(message: string, statusCode: number) {
return Object.assign(new Error(message), {
response: {
status: statusCode,
},
statusCode,
});
}