first commit
This commit is contained in:
116
src/api.ts
Normal file
116
src/api.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* 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 } : {}),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user