Files
qqbot/src/api.ts
2026-01-28 17:18:41 +08:00

117 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* QQ Bot API 鉴权和请求封装
*/
const API_BASE = "https://api.sgroup.qq.com";
const TOKEN_URL = "https://bots.qq.com/app/getAppAccessToken";
let cachedToken: { token: string; expiresAt: number } | null = null;
/**
* 获取 AccessToken带缓存
*/
export async function getAccessToken(appId: string, clientSecret: string): Promise<string> {
// 检查缓存,提前 5 分钟刷新
if (cachedToken && Date.now() < cachedToken.expiresAt - 5 * 60 * 1000) {
return cachedToken.token;
}
const response = await fetch(TOKEN_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ appId, clientSecret }),
});
const data = (await response.json()) as { access_token?: string; expires_in?: number };
if (!data.access_token) {
throw new Error(`Failed to get access_token: ${JSON.stringify(data)}`);
}
cachedToken = {
token: data.access_token,
expiresAt: Date.now() + (data.expires_in ?? 7200) * 1000,
};
return cachedToken.token;
}
/**
* 清除 Token 缓存
*/
export function clearTokenCache(): void {
cachedToken = null;
}
/**
* API 请求封装
*/
export async function apiRequest<T = unknown>(
accessToken: string,
method: string,
path: string,
body?: unknown
): Promise<T> {
const url = `${API_BASE}${path}`;
const options: RequestInit = {
method,
headers: {
Authorization: `QQBot ${accessToken}`,
"Content-Type": "application/json",
},
};
if (body) {
options.body = JSON.stringify(body);
}
const res = await fetch(url, options);
const data = (await res.json()) as T;
if (!res.ok) {
const error = data as { message?: string; code?: number };
throw new Error(`API Error [${path}]: ${error.message ?? JSON.stringify(data)}`);
}
return data;
}
/**
* 获取 WebSocket Gateway URL
*/
export async function getGatewayUrl(accessToken: string): Promise<string> {
const data = await apiRequest<{ url: string }>(accessToken, "GET", "/gateway");
return data.url;
}
/**
* 发送 C2C 单聊消息
*/
export async function sendC2CMessage(
accessToken: string,
openid: string,
content: string,
msgId?: string
): Promise<{ id: string; timestamp: number }> {
return apiRequest(accessToken, "POST", `/v2/users/${openid}/messages`, {
content,
msg_type: 0,
...(msgId ? { msg_id: msgId } : {}),
});
}
/**
* 发送频道消息
*/
export async function sendChannelMessage(
accessToken: string,
channelId: string,
content: string,
msgId?: string
): Promise<{ id: string; timestamp: string }> {
return apiRequest(accessToken, "POST", `/channels/${channelId}/messages`, {
content,
...(msgId ? { msg_id: msgId } : {}),
});
}