amesu
Version:
Node.js SDK for QQ Bot.
238 lines (237 loc) • 8.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Session = void 0;
const ws_1 = require("ws");
const node_events_1 = require("node:events");
const logger_1 = require("../utils/logger");
const common_1 = require("../utils/common");
var OpCode;
(function (OpCode) {
/** 服务端进行消息推送 */
OpCode[OpCode["Dispatch"] = 0] = "Dispatch";
/** 客户端或服务端发送心跳 */
OpCode[OpCode["Heartbeat"] = 1] = "Heartbeat";
/** 客户端发送鉴权 */
OpCode[OpCode["Identify"] = 2] = "Identify";
/** 客户端恢复连接 */
OpCode[OpCode["Resume"] = 6] = "Resume";
/** 服务端通知客户端重新连接 */
OpCode[OpCode["Reconnect"] = 7] = "Reconnect";
/** 当 Identify 或 Resume 的时候,如果参数有错,服务端会返回该消息 */
OpCode[OpCode["InvalidSession"] = 9] = "InvalidSession";
/** 当客户端与网关建立 ws 连接之后,网关下发的第一条消息 */
OpCode[OpCode["Hello"] = 10] = "Hello";
/** 当发送心跳成功之后,就会收到该消息 */
OpCode[OpCode["HeartbeatAck"] = 11] = "HeartbeatAck";
/** 仅用于 http 回调模式的回包,代表机器人收到了平台推送的数据 */
OpCode[OpCode["HttpCallbackAck"] = 12] = "HttpCallbackAck";
})(OpCode || (OpCode = {}));
var DispatchType;
(function (DispatchType) {
DispatchType["READY"] = "READY";
DispatchType["RESUMED"] = "RESUMED";
})(DispatchType || (DispatchType = {}));
/** 事件类型 */
var Intent;
(function (Intent) {
Intent[Intent["GUILDS"] = 1] = "GUILDS";
Intent[Intent["GUILD_MEMBERS"] = 2] = "GUILD_MEMBERS";
Intent[Intent["GUILD_MESSAGES"] = 512] = "GUILD_MESSAGES";
Intent[Intent["GUILD_MESSAGE_REACTIONS"] = 1024] = "GUILD_MESSAGE_REACTIONS";
Intent[Intent["DIRECT_MESSAGE"] = 4096] = "DIRECT_MESSAGE";
Intent[Intent["GROUP_AND_C2C_EVENT"] = 33554432] = "GROUP_AND_C2C_EVENT";
Intent[Intent["INTERACTION"] = 67108864] = "INTERACTION";
Intent[Intent["MESSAGE_AUDIT"] = 134217728] = "MESSAGE_AUDIT";
Intent[Intent["FORUMS_EVENT"] = 268435456] = "FORUMS_EVENT";
Intent[Intent["AUDIO_ACTION"] = 536870912] = "AUDIO_ACTION";
Intent[Intent["PUBLIC_GUILD_MESSAGES"] = 1073741824] = "PUBLIC_GUILD_MESSAGES";
})(Intent || (Intent = {}));
class SessionError extends Error {
constructor(message) {
super(message);
this.name = 'SessionError';
}
}
class Session extends node_events_1.EventEmitter {
config;
token;
ackTimeout;
/** 心跳间隔 */
heartbeat_interval;
/** 是否重连 */
is_reconnect;
/** 记录器 */
logger;
/** 重连计数 */
retry;
/** 最大重连数 */
max_retry;
/** 消息序列号 */
seq;
/** 会话 id */
session_id;
ws;
constructor(config, token) {
super();
this.config = config;
this.token = token;
this.ackTimeout = null;
this.is_reconnect = false;
this.logger = (0, logger_1.getLogger)(config.appid);
this.retry = 0;
this.max_retry = config.max_retry;
this.seq = 0;
this.session_id = null;
this.ws = null;
}
onOpen() {
if (this.retry) {
this.retry = 0;
}
this.logger.debug('连接 socket 成功');
}
async onClose(code) {
clearTimeout(this.ackTimeout);
this.ackTimeout = null;
this.ws.removeAllListeners();
this.logger.debug(`Session Exit Code: ${code}.`);
if (!this.is_reconnect) {
this.ws = null;
this.logger.info('会话连接已关闭');
return;
}
this.logger.warn('会话连接已被中断');
await this.token.renew();
this.reconnect();
}
onError(error) {
this.logger.fatal(error);
}
onMessage(data) {
const payload = JSON.parse(data.toString());
this.logger.debug(`收到 payload 数据: ${(0, common_1.objectToString)(payload)}`);
switch (payload.op) {
case OpCode.Dispatch:
this.onDispatch(payload);
break;
case OpCode.Reconnect:
this.logger.info('当前会话已失效,等待断开后自动重连');
this.ws.close();
break;
case OpCode.InvalidSession:
this.logger.error('发送的 payload 参数有误');
throw new SessionError('The Payload parameter sent is incorrect.');
case OpCode.Hello:
this.heartbeat_interval = payload.d.heartbeat_interval;
this.is_reconnect ? this.sendResumePayload() : this.sendAuthPayload();
break;
case OpCode.HeartbeatAck:
this.ackTimeout = setTimeout(() => this.heartbeat(), this.heartbeat_interval);
break;
}
}
onDispatch(payload) {
const { d, s, t } = payload;
this.seq = s;
switch (t) {
case DispatchType.READY:
const { session_id } = d;
this.session_id = session_id;
this.logger.mark(`Hello, ${d.user.username}`);
case DispatchType.RESUMED:
this.logger.trace('开始发送心跳...');
this.heartbeat();
break;
}
const dispatch = {
t,
d,
};
this.emit('dispatch', dispatch);
}
heartbeat() {
const payload = {
op: OpCode.Heartbeat,
d: this.seq,
};
this.sendPayload(payload);
}
sendPayload(payload) {
try {
const data = (0, common_1.objectToString)(payload);
this.ws.send(data);
this.logger.debug(`发送 payload 数据: ${data}`);
}
catch (error) {
this.logger.error(error);
}
}
getIntents() {
const events = this.config.events;
const intents = events.reduce((previous, current) => previous | Intent[current], 0);
return intents;
}
sendAuthPayload() {
const payload = {
op: OpCode.Identify,
d: {
token: this.token.authorization,
intents: this.getIntents(),
shard: this.config.shard,
// TODO: /人◕ ‿‿ ◕人\
properties: {},
},
};
this.is_reconnect = true;
this.sendPayload(payload);
}
sendResumePayload() {
const payload = {
op: OpCode.Resume,
d: {
token: this.token.authorization,
seq: this.seq,
session_id: this.session_id,
},
};
this.sendPayload(payload);
}
async reconnect() {
if (this.retry === this.max_retry) {
this.logger.error('重连失败,请检查网络和配置。');
throw new SessionError('Reached the maximum number of reconnection attempts.');
}
this.retry++;
try {
this.logger.info(`尝试重连... x${this.retry}`);
await (0, common_1.sleep)(this.retry * 3000);
this.connect(this.ws.url);
}
catch (error) {
this.reconnect();
}
}
connect(url) {
if (this.ws && this.ws.readyState === ws_1.WebSocket.OPEN) {
this.logger.warn('已建立会话通信,不要重复连接。');
return;
}
this.logger.trace('开始建立 ws 通信...');
const ws = new ws_1.WebSocket(url);
ws.on('open', () => this.onOpen());
ws.on('close', code => this.onClose(code));
ws.on('error', error => this.onError(error));
ws.on('message', data => this.onMessage(data));
this.ws = ws;
}
disconnect() {
if (!this.ws) {
this.logger.warn('未建立会话通信,无效的操作。');
return;
}
this.is_reconnect = false;
this.logger.trace('正在断开 ws 通信...');
this.ws.close();
}
}
exports.Session = Session;