amesu
Version:
Node.js SDK for QQ Bot.
229 lines (228 loc) • 8.73 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Client = void 0;
const node_events_1 = require("node:events");
const api_1 = require("../api");
const token_1 = require("../bot/token");
const session_1 = require("../bot/session");
const common_1 = require("../utils/common");
const request_1 = require("../utils/request");
const logger_1 = require("../utils/logger");
class ClientError extends Error {
constructor(message) {
super(message);
this.name = 'ClientError';
}
}
class Client extends node_events_1.EventEmitter {
config;
logger;
api;
request;
token;
session;
eventInterceptors;
constructor(config) {
super();
this.config = config;
config.sandbox ??= false;
config.shard ??= [0, 1];
config.max_retry ??= 3;
config.log_level ??= 'INFO';
this.logger = (0, logger_1.createLogger)(config.appid, config.log_level);
this.checkConfig();
this.logger.mark('Client is initializing...');
this.api = this.createApi();
this.request = this.createRequest();
this.token = new token_1.Token(config);
this.session = new session_1.Session(config, this.token);
this.eventInterceptors = [];
this.useEventInterceptor(dispatch => {
const { t, d } = dispatch;
switch (t) {
case 'GUILD_MEMBER_ADD':
case 'GUILD_MEMBER_UPDATE':
case 'GUILD_MEMBER_REMOVE':
case 'MESSAGE_REACTION_ADD':
case 'MESSAGE_REACTION_REMOVE':
case 'FORUM_THREAD_CREATE':
case 'FORUM_THREAD_UPDATE':
case 'FORUM_THREAD_DELETE':
case 'FORUM_POST_CREATE':
case 'FORUM_POST_DELETE':
case 'FORUM_REPLY_CREATE':
case 'FORUM_REPLY_DELETE':
d.reply = (params) => {
return this.api.sendChannelMessage(d.channel_id, {
event_id: d.id,
...params,
});
};
break;
// case 'INTERACTION_CREATE':
case 'GROUP_ADD_ROBOT':
case 'GROUP_MSG_RECEIVE':
d.reply = (params) => {
return this.api.sendGroupMessage(d.group_openid, {
event_id: d.id,
...params,
});
};
break;
case 'MESSAGE_CREATE':
case 'AT_MESSAGE_CREATE':
d.reply = (params) => {
return this.api.sendChannelMessage(d.channel_id, {
msg_id: d.id,
...params,
});
};
break;
case 'DIRECT_MESSAGE_CREATE':
d.reply = (params) => {
return this.api.sendDmMessage(d.guild_id, {
msg_id: d.id,
...params,
});
};
break;
case 'GROUP_AT_MESSAGE_CREATE':
d.reply = (params) => {
return this.api.sendGroupMessage(d.group_openid, {
msg_id: d.id,
...params,
});
};
break;
case 'C2C_MESSAGE_CREATE':
d.reply = (params) => {
return this.api.sendUserMessage(d.author.user_openid, {
msg_id: d.id,
...params,
});
};
break;
}
return dispatch;
});
}
/**
* 机器人上线。
*/
async online() {
const { data } = await this.api.getGateway();
this.session.connect(data.url);
this.session.on('dispatch', data => this.onDispatch(data));
this.on('c2c.message.create', this.onMessage);
this.on('group.at.message.create', this.onMessage);
this.on('direct.message.create', this.onMessage);
this.on('at.message.create', this.onMessage);
}
/**
* 机器人下线。
*/
offline() {
this.session.disconnect();
this.session.removeAllListeners('dispatch');
this.off('c2c.message.create', this.onMessage);
this.off('group.at.message.create', this.onMessage);
this.off('direct.message.create', this.onMessage);
this.off('at.message.create', this.onMessage);
}
/**
* 添加事件拦截器。
*/
useEventInterceptor(interceptor) {
this.eventInterceptors.push(interceptor);
}
async onDispatch(dispatch) {
for (const interceptor of this.eventInterceptors) {
try {
dispatch = await interceptor(dispatch);
}
catch (error) {
const message = (0, common_1.parseError)(error);
this.logger.error(message);
throw new ClientError(message);
}
}
const { t, d } = dispatch;
const data = {
t,
...d,
};
const events = t.toLowerCase().split('_');
// 不存在下划线就是 session 自身的事件,例如 READY、RESUMED
if (events.length === 1) {
events.unshift('session');
}
do {
const event = events.join('.');
this.emit(event, data);
this.logger.debug(`推送 "${event}" 事件`);
events.pop();
} while (events.length);
}
onMessage(message) {
const { attachments, content } = message;
const text = attachments ? `<attachment>${content}` : content;
this.logger.info(`Received message: "${text}"`);
}
checkConfig() {
if (!this.config.events.length) {
const wiki = 'https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/event-emit.html#%E4%BA%8B%E4%BB%B6%E8%AE%A2%E9%98%85Intents';
this.logger.error(`检测到 events 为空,请查阅相关文档:${wiki}`);
throw new ClientError('Events cannot be empty.');
}
else if (!Array.isArray(this.config.shard) ||
this.config.shard.length !== 2 ||
this.config.shard[0] >= this.config.shard[1]) {
const wiki = 'https://bot.q.qq.com/wiki/develop/api-v2/dev-prepare/interface-framework/event-emit.html';
this.logger.error(`检测到 shard 配置错误,请查阅相关文档:${wiki}`);
throw new ClientError('Shard configuration is incorrect.');
}
}
createApi() {
const request = new request_1.Request();
const origin = this.config.sandbox
? 'https://sandbox.api.sgroup.qq.com'
: 'https://api.sgroup.qq.com';
request.useRequestInterceptor(async (config) => {
this.logger.trace('开始调用接口请求...');
await this.token.renew();
(0, common_1.deepAssign)(config, {
origin,
headers: {
'Authorization': this.token.authorization,
'X-Union-Appid': this.config.appid,
},
});
this.logger.debug(`API Request: ${(0, common_1.objectToString)(config)}`);
return config;
});
request.useResponseInterceptor(result => {
const { data } = result;
this.logger.debug(`API Response: ${(0, common_1.objectToString)(data)}`);
if (data?.code) {
this.logger.error(`API Code: ${data.code}, ${data.message}.`);
throw new request_1.RequestError(`Code ${data.code}, ${data.message}.`);
}
return result;
});
return (0, api_1.generateApi)(request);
}
createRequest() {
const request = new request_1.Request();
request.useRequestInterceptor(config => {
this.logger.trace('开始发起网络请求...');
this.logger.debug(`HTTP Request: ${(0, common_1.objectToString)(config)}`);
return config;
});
request.useResponseInterceptor(result => {
this.logger.debug(`HTTP Response: ${(0, common_1.objectToString)(result.data)}`);
return result;
});
return request;
}
}
exports.Client = Client;