qq-official-bot
Version:
277 lines (276 loc) • 10.4 kB
JavaScript
"use strict";
/**
* 认证管理器 - 负责处理QQ Bot的认证相关功能
* 从SessionManager中提取认证相关功能
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Auth = void 0;
/**
* 认证管理器
* 专门负责处理token获取、刷新和网关信息获取
*/
class Auth {
constructor(config, bot) {
this.bot = bot;
this.refreshRetryCount = 0;
this.config = {
tokenRefreshBuffer: 45, // 提前45秒刷新
maxRetries: 3,
retryDelay: 1000,
...config
};
}
/**
* 获取访问令牌
* 如果当前token有效则返回,否则重新获取
*/
async getAccessToken() {
if (this.isTokenValid()) {
return this.currentToken.access_token;
}
const tokenInfo = await this.fetchNewToken();
this.setToken(tokenInfo);
return tokenInfo.access_token;
}
/**
* 强制刷新访问令牌
*/
async refreshAccessToken() {
this.bot.logger.debug("[AUTH] 强制刷新访问令牌");
const tokenInfo = await this.fetchNewToken();
this.setToken(tokenInfo);
return tokenInfo;
}
/**
* 获取网关连接地址
*/
async getGatewayUrl() {
if (this.gatewayInfo?.url) {
return this.gatewayInfo.url;
}
const gatewayInfo = await this.fetchGatewayInfo();
this.gatewayInfo = gatewayInfo;
return gatewayInfo.url;
}
/**
* 获取完整的网关信息
*/
async getGatewayInfo() {
if (!this.gatewayInfo) {
this.gatewayInfo = await this.fetchGatewayInfo();
}
return this.gatewayInfo;
}
/**
* 从API获取新的访问令牌
*/
async fetchNewToken() {
const { appid, secret } = this.config;
for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {
try {
this.bot.logger.debug(`[AUTH] 获取访问令牌,尝试次数: ${attempt}`);
const tokenUrl = this.config.accessTokenUrl ?? Auth.DEFAULT_ACCESS_TOKEN_URL;
const response = await this.bot.request.post(tokenUrl, {
appId: appid,
clientSecret: secret
}, {
timeout: 10000,
headers: {
'Content-Type': 'application/json',
'User-Agent': 'QQBot/1.0'
}
});
if (response.status === 200 && response.data?.access_token) {
const tokenInfo = {
access_token: response.data.access_token,
expires_in: response.data.expires_in,
expires_at: Date.now() + (response.data.expires_in * 1000)
};
this.bot.logger.debug("[AUTH] 访问令牌获取成功", {
expires_in: tokenInfo.expires_in,
expires_at: new Date(tokenInfo.expires_at).toISOString()
});
return tokenInfo;
}
else {
throw new Error(`无效的响应: ${response.status} ${JSON.stringify(response.data)}`);
}
}
catch (error) {
this.bot.logger.error(`[AUTH] 获取访问令牌失败 (尝试 ${attempt}/${this.config.maxRetries}):`, error);
if (attempt === this.config.maxRetries) {
throw new Error(`获取访问令牌失败,已重试 ${this.config.maxRetries} 次: ${error}`);
}
// 等待后重试
await this.delay(this.config.retryDelay * attempt);
}
}
throw new Error("获取访问令牌失败");
}
/**
* 从API获取网关信息
*/
async fetchGatewayInfo() {
const token = await this.getAccessToken();
for (let attempt = 1; attempt <= this.config.maxRetries; attempt++) {
try {
this.bot.logger.debug(`[AUTH] 获取网关信息,尝试次数: ${attempt}`);
const gatewayUrl = this.config.gatewayUrl ?? Auth.DEFAULT_GATEWAY_URL;
const response = await this.bot.request.get(gatewayUrl, {
headers: {
Accept: "*/*",
"Accept-Encoding": "utf-8",
"Accept-Language": "zh-CN,zh;q=0.8",
Connection: "keep-alive",
"User-Agent": "v1",
Authorization: `QQBot ${token}`
},
timeout: 10000
});
if (response.data?.url) {
const gatewayInfo = {
url: response.data.url,
shards: response.data.shards,
session_start_limit: response.data.session_start_limit
};
this.bot.logger.debug("[AUTH] 网关信息获取成功", gatewayInfo);
return gatewayInfo;
}
else {
throw new Error(`无效的网关响应: ${JSON.stringify(response.data)}`);
}
}
catch (error) {
this.bot.logger.error(`[AUTH] 获取网关信息失败 (尝试 ${attempt}/${this.config.maxRetries}):`, error);
if (attempt === this.config.maxRetries) {
throw new Error(`获取网关信息失败,已重试 ${this.config.maxRetries} 次: ${error}`);
}
await this.delay(this.config.retryDelay * attempt);
}
}
throw new Error("获取网关信息失败");
}
/**
* 设置令牌并启动自动刷新
*/
setToken(tokenInfo) {
const expiresAt = tokenInfo.expires_at ?? (Date.now() + tokenInfo.expires_in * 1000);
this.currentToken = {
...tokenInfo,
expires_at: expiresAt
};
this.refreshRetryCount = 0;
this.scheduleTokenRefresh();
this.bot.logger.info("[AUTH] 访问令牌已设置", {
expires_in: tokenInfo.expires_in,
expires_at: tokenInfo.expires_at ? new Date(tokenInfo.expires_at).toISOString() : 'unknown'
});
}
/**
* 计划令牌刷新
*/
scheduleTokenRefresh() {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
if (!this.currentToken) {
return;
}
const expiresAt = this.currentToken.expires_at ?? (Date.now() + this.currentToken.expires_in * 1000);
const remainingTokenLifetime = Math.max(expiresAt - Date.now(), Auth.MIN_REFRESH_DELAY_MS);
// 计算刷新时间(提前缓冲时间刷新)
const refreshTime = remainingTokenLifetime - (this.config.tokenRefreshBuffer * 1000);
const fallbackRefreshTime = Math.max(Math.floor(remainingTokenLifetime * Auth.FALLBACK_REFRESH_RATIO), Auth.MIN_REFRESH_DELAY_MS);
const nextRefreshTime = refreshTime > 0 ? refreshTime : fallbackRefreshTime;
if (refreshTime <= 0) {
this.bot.logger.warn(`[AUTH] 令牌剩余有效期(${remainingTokenLifetime}ms)小于等于刷新缓冲(${this.config.tokenRefreshBuffer * 1000}ms),使用保底定时器 ${nextRefreshTime}ms`);
}
this.refreshTimer = setTimeout(async () => {
try {
this.bot.logger.debug("[AUTH] 自动刷新访问令牌");
await this.refreshAccessToken();
}
catch (error) {
this.bot.logger.error("[AUTH] 自动刷新令牌失败:", error);
this.scheduleTokenRefreshRetry();
}
}, nextRefreshTime);
this.bot.logger.debug(`[AUTH] 令牌刷新已计划,将在 ${nextRefreshTime / 1000} 秒后执行`);
}
scheduleTokenRefreshRetry() {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
}
const maxRetries = this.config.maxRetries ?? 3;
if (this.refreshRetryCount >= maxRetries) {
this.bot.logger.error(`[AUTH] 自动刷新重试次数超过上限(${maxRetries}),停止自动重试`);
this.refreshTimer = undefined;
return;
}
this.refreshRetryCount += 1;
this.refreshTimer = setTimeout(async () => {
try {
this.bot.logger.debug("[AUTH] 重试刷新访问令牌");
await this.refreshAccessToken();
}
catch (error) {
this.bot.logger.error("[AUTH] 重试刷新令牌失败:", error);
this.scheduleTokenRefreshRetry();
}
}, Auth.REFRESH_RETRY_DELAY_MS);
}
/**
* 检查当前令牌是否有效
*/
isTokenValid() {
if (!this.currentToken || !this.currentToken.expires_at) {
return false;
}
// 检查是否在缓冲时间内即将过期
const bufferTime = this.config.tokenRefreshBuffer * 1000;
return Date.now() < (this.currentToken.expires_at - bufferTime);
}
/**
* 获取当前令牌信息
*/
getCurrentTokenInfo() {
return this.currentToken ? { ...this.currentToken } : null;
}
/**
* 检查认证状态
*/
isAuthenticated() {
return this.isTokenValid();
}
/**
* 清除认证信息
*/
clearAuth() {
if (this.refreshTimer) {
clearTimeout(this.refreshTimer);
this.refreshTimer = undefined;
}
this.currentToken = undefined;
this.gatewayInfo = undefined;
this.bot.logger.debug("[AUTH] 认证信息已清除");
}
/**
* 工具方法:延迟
*/
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 销毁认证管理器
*/
destroy() {
this.clearAuth();
this.bot.logger.debug("[AUTH] 认证管理器已销毁");
}
}
exports.Auth = Auth;
Auth.DEFAULT_ACCESS_TOKEN_URL = 'https://bots.qq.com/app/getAppAccessToken';
Auth.DEFAULT_GATEWAY_URL = '/gateway/bot';
Auth.MIN_REFRESH_DELAY_MS = 1000;
Auth.FALLBACK_REFRESH_RATIO = 0.5;
Auth.REFRESH_RETRY_DELAY_MS = 10000;