heybox-bot
Version:
A heybox chat bot frame
398 lines • 17.7 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HeyBoxBot = void 0;
const gugle_event_1 = require("gugle-event");
const ws_1 = require("ws");
const process = __importStar(require("node:process"));
const constants_1 = __importDefault(require("./constants"));
const command_1 = require("./command");
const dayjs_1 = __importDefault(require("dayjs"));
const type_1 = require("./type");
const utils_1 = require("./utils");
const logger_1 = require("./logger");
const fs = __importStar(require("node:fs"));
const cron = __importStar(require("node-cron"));
/**
* `HeyBoxBot` 类代表一个聊天机器人,用于处理命令和事件
*/
// noinspection JSUnusedGlobalSymbols
class HeyBoxBot {
/**
* 构造函数,用于初始化机器人实例
* @param config {BotConfig} 机器人配置对象,包含机器人运行所需的各种配置信息
*/
constructor(config) {
this.path = process.cwd();
this.heartbeatTimes = 0;
/**
* 标记WebSocket连接是否已打开
*/
this.wsOpened = false;
// 将传入的配置对象赋值给实例变量config
this.config = config;
// 初始化事件管理器实例
this.eventManager = new gugle_event_1.EventManager();
// 初始化命令管理器实例
this.commandManager = new command_1.HeyBoxCommandManager({
debug: msg => {
var _a;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug(msg);
},
info: msg => {
var _a;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.info(msg);
},
warning: msg => {
var _a;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.warning(msg);
},
error: msg => {
var _a;
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(msg);
}
});
// 根据WebSocketURL模板和当前配置的token创建WebSocket连接
this.ws = new ws_1.WebSocket(`${constants_1.default.WSS_URL}${constants_1.default.COMMON_PARAMS}${constants_1.default.TOKEN_PARAMS}${this.config.token || ''}`);
// 监听WebSocket连接错误事件
this.ws.on('error', (e) => {
var _a;
let message = e.message;
const stack = e.stack;
if (message.endsWith('401')) {
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`${stack}`);
message = '无法连接至 WebSocket 服务器,请检查你的 Token! ';
throw new Error(message);
}
throw e;
});
// 当WebSocket连接打开时,启动定时器每30秒发送一个PING保持连接
this.ws.on('open', () => {
// 标记WebSocket连接已打开
this.wsOpened = true;
// 定义并启动PING发送定时器
const ping = () => {
var _a;
if (this.heartbeatTimes > 5) {
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error('WebSocket connection lost, reconnecting...');
this.ws.terminate();
return;
}
this.ws.send('PING');
this.heartbeatTimes += 1;
setTimeout(ping, 30000);
};
ping();
});
}
/**
* 异步启动方法,用于启动HeyBoxBot实例
* 此方法允许指定一个可选的路径参数,默认为当前工作目录
* 它在启动前后分别触发一系列事件,并设置WebSocket消息监听器
*
* @param {string} path - 启动的目录路径,默认为当前工作目录
* @returns {Promise<HeyBoxBot>} 返回实例本身,允许链式调用
*/
async start(path = process.cwd()) {
// 在启动前触发'before-start'事件,传递当前实例和路径作为参数
await this.post('before-start', this, path).then(args => {
// 根据'before-start'事件处理结果更新路径
path = args[1];
this.path = path;
const logPath = `${this.path}/logs`;
if (!fs.existsSync(logPath))
fs.mkdirSync(logPath);
if (fs.existsSync(`${logPath}/latest.log`)) {
let logName = `${logPath}/${(0, dayjs_1.default)().format('YYYY-MM-DD-HH-mm-ss')}.log`;
let count = 0;
while (fs.existsSync(logName)) {
count++;
logName = `${logPath}/${(0, dayjs_1.default)().format('YYYY-MM-DD-HH-mm-ss')}-${count}.log`;
}
fs.renameSync(`${logPath}/latest.log`, logName);
}
this.logger = logger_1.LoggerFactory.createLogger('HeyBoxBot', logPath, this.config.logLevel || 'info');
this.logger.info(`HeyBox Bot starting...`);
this.eventManager.listen('websocket-message', this.onWebsocketMsg);
this.eventManager.listen('command-message', this.onCommandMessage);
this.eventManager.listen('user-add-or-remove-emoji-to-msg', this.onUserAddOrRemoveEmojiToMsg);
this.eventManager.listen('user-join-or-leave-room', this.onUserJoinOrLeaveRoom);
this.eventManager.listen('card-message-btn-click', this.onCardMessageBtnClick);
// 设置WebSocket消息监听器
this.ws.on('message', event => {
// 当接收到WebSocket消息时,触发'websocket-message'事件
this.post('websocket-message', this, event);
});
// 在启动后触发'after-start'事件,传递当前实例作为参数
this.post('after-start', this).then();
});
utils_1.HeyboxBotRuntimeContext.setBot(this);
utils_1.HeyboxBotRuntimeContext.setLogger(this.logger);
// 返回实例本身,支持链式调用
return this;
}
/**
* 停止HeyBoxBot实例
*
* 此方法在停止机器人之前和之后执行一些钩子函数,确保资源被适当管理
* 如果WebSocket连接是打开的状态,则会关闭该连接
*
* @returns {HeyBoxBot} 返回HeyBoxBot实例,允许链式调用
*/
stop() {
// 在停止之前触发'before-stop'事件,传递当前实例
this.post('before-stop', this).then(() => {
// 如果WebSocket连接是打开的状态,关闭连接
if (this.wsOpened)
this.ws.close();
// 在停止之后触发'after-stop'事件,传递当前实例
this.post('after-stop', this).then();
});
return this;
}
/**
* 定义一个命令装饰器,用于在类中动态添加命令处理逻辑
*
* @param command 命令的字符串表示,用于指定命令的结构和参数
* @param permission 可选的权限字符串,用于限定执行该命令所需的权限
* @returns {(executor: (...args: any) => boolean) => void} 返回一个函数,该函数接受一个执行器函数作为参数,并在适当的时候调用它
*
* @example
* @ bot.command('/test {arg1: NUMBER} {arg2?: NUMBER}')
* public calc(source: CommandSource, arg1: number, arg2: number | undefined = undefined): boolean {}
*/
command(command, permission = undefined) {
// 当前命令管理器实例的别名,用于内部函数中引用
const commandManager = this.commandManager;
// 返回一个函数,该函数接受一个执行器函数作为参数,并在适当的时候调用它
return function (executor) {
// 调用命令管理器的解析方法,根据传入的命令字符串和权限字符串来解析并执行命令
commandManager === null || commandManager === void 0 ? void 0 : commandManager.parse(command, permission)(executor);
};
}
/**
* 定义一个 cron 装饰器,用于根据给定的 cron 表达式调度任务
*
* @param _cron cron 表达式,用于指定任务执行的时间
* @returns {(executor: (bot: HeyBoxBot) => void) => void} 返回一个函数,该函数接受一个执行器函数作为参数,并在指定时间执行该执行器函数
*
* @example
* @ bot.cron('0/30 * * * * *')
* public cron(bot: HeyBoxBot): void {}
*/
cron(_cron) {
// 保存当前实例的引用,以便在后续的执行器函数中使用
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
// 返回一个函数,该函数负责调度执行器函数
return function (executor) {
// 使用 cron 表达式调度任务,当时间匹配时执行执行器函数
cron.schedule(_cron, () => {
executor(self);
});
};
}
/**
* 发布一个事件,触发该事件的所有监听器
*
* @param event {string} 事件名称
* @param args {...args: any} 传递给事件回调的参数
* @returns {any[]} 事件回调的返回值(如果有)
*/
async post(event, ...args) {
return await this.eventManager.post(event, ...args);
}
/**
* 定义一个事件订阅装饰器,用于根据事件触发回调
*
* @param event {string} 事件名称
* @param namespace {string} 命名空间,用于组织事件监听器
* @param priority {number} 优先级,决定事件回调的执行顺序
* @param cancelable {boolean} 是否可取消,决定是否可以取消事件,为 true 时,处理器第一个参数会传入 Cancelable
* @returns {(callback: (...args: any) => void) => void} 一个函数,接受事件回调并注册该回调到指定事件
*
* @example
* @ bot.subscribe('after-start', true)
* public test(cancelable: Cancelable, bot: HeyBoxBot) {}
*/
subscribe(event, cancelable = false, namespace = 'gugle-event', priority = 100) {
return this.eventManager.subscribe(event, namespace, priority, cancelable);
}
/**
* 处理WebSocket消息的函数
* 该函数解析从WebSocket接收到的消息,并根据消息内容执行相应操作
* @param bot {HeyBoxBot} HeyBoxBot实例,用于访问机器人的功能和属性
* @param data {RawData} 从WebSocket接收到的原始数据
*/
onWebsocketMsg(bot, data) {
// 将接收到的原始数据转换为UTF-8字符串,并记录调试信息
const msg = data.toString('utf-8');
bot.logger.debug(msg);
// 如果消息是"PONG",则重置心跳次数
if (msg === 'PONG') {
bot.heartbeatTimes = 0;
return;
}
// 如果消息是JSON格式,则尝试解析并处理
if (msg.startsWith('{') && msg.endsWith('}')) {
try {
// 解析JSON消息
const data = JSON.parse(msg);
if (data.type === '50') {
const commandMsg = data.data;
const user = commandMsg.sender_info;
bot.post('command-message', bot, user, commandMsg).then();
}
else if (data.type === '3001') {
const userJoinOrLeaveRoomWSMsgData = data.data;
const user = userJoinOrLeaveRoomWSMsgData.user_info;
bot.post('user-join-or-leave-room', bot, user, userJoinOrLeaveRoomWSMsgData).then();
}
else if (data.type === '5003') {
const userAddOrRemoveEmojiToMsgWSMsgData = data.data;
bot.post('user-add-or-remove-emoji-to-msg', bot, userAddOrRemoveEmojiToMsgWSMsgData).then();
}
else if (data.type === 'card_message_btn_click') {
const cardMessageBtnClickWSMsgData = data.data;
const user = cardMessageBtnClickWSMsgData.sender_info;
bot.post('card-message-btn-click', bot, user, cardMessageBtnClickWSMsgData).then();
}
}
catch (e) {
// 如果解析过程中出现错误,记录错误信息
bot.logger.error(e);
}
}
}
/**
* 当接收到命令消息时调用该方法处理
* @param bot 当前机器人实例
* @param user 发送命令的用户信息
* @param commandMsg 命令消息数据
*/
onCommandMessage(bot, user, commandMsg) {
var _a;
// 记录用户执行命令的日志
bot.logger.info(`[${user.nickname}|${user.user_id}] run command: ${commandMsg.command_info.name}`);
// 创建WSMsgImpl实例,封装消息发送方法和命令消息数据
const userMsg = new type_1.WSMsgImpl(bot.sendMsg, bot.sendUserMsg, commandMsg, {
room_id: commandMsg.room_base_info.room_id,
room_nickname: commandMsg.room_base_info.room_name,
channel_id: commandMsg.channel_base_info.channel_id,
channel_name: commandMsg.channel_base_info.channel_name,
channel_type: commandMsg.channel_base_info.channel_type,
user_info: commandMsg.sender_info
});
// 执行对应命令
(_a = bot.commandManager) === null || _a === void 0 ? void 0 : _a.execute(commandMsg, userMsg);
}
/**
* 当用户对消息添加或移除表情时调用该方法处理
* @param bot 当前机器人实例
* @param msg 用户添加或移除表情的消息数据
*/
onUserAddOrRemoveEmojiToMsg(bot, msg) {
// 记录用户添加或移除表情的日志
bot.logger.info(`[unknown|${msg.user_id}] ${msg.is_add ? 'add' : 'remove'} ${msg.emoji} to msg`);
}
/**
* 当用户加入或离开房间时调用该方法处理
* @param bot 当前机器人实例
* @param user 加入或离开房间的用户信息
* @param msg 用户加入或离开房间的消息数据
*/
onUserJoinOrLeaveRoom(bot, user, msg) {
// 记录用户加入或离开房间的日志
bot.logger.info(`[${user.nickname}|${user.user_id}] ${msg.state ? 'join' : 'leave'} room ${msg.room_base_info.room_name}`);
}
/**
* 当用户点击卡片消息按钮时调用该方法处理
* @param bot 当前机器人实例
* @param user 点击按钮的用户信息
* @param msg 卡片消息按钮点击事件数据
*/
onCardMessageBtnClick(bot, user, msg) {
// 记录用户点击按钮的日志
bot.logger.info(`[${user.nickname}|${user.user_id}] click ${msg.text} button(${msg.event}/${msg.value})`);
}
/**
* 通过回调函数构建并发送消息
* @param callback 用于构建消息的回调函数
*/
sendMsgBy(callback) {
const builder = new type_1.MessageBuilder();
callback(builder);
// 发送构建的消息
utils_1.Request.sendMessage(builder.build()).then();
}
/**
* 发送消息对象
* @param msg 要发送的消息对象
*/
sendMsg(msg) {
// 发送消息
utils_1.Request.sendMessage(msg).then();
}
/**
* 通过回调函数构建并发送用户消息
* @param callback 用于构建用户消息的回调函数
*/
sendUserMsgBy(callback) {
const builder = new type_1.UserMessageBuilder();
callback(builder);
// 发送构建的用户消息
utils_1.Request.sendUserMessage(builder.build()).then();
}
/**
* 发送用户消息对象
* @param msg 要发送的用户消息对象
*/
sendUserMsg(msg) {
// 发送用户消息
utils_1.Request.sendUserMessage(msg).then();
}
/**
* 获取机器人的配置
* @returns 机器人的配置对象
*/
getConfig() {
return this.config;
}
}
exports.HeyBoxBot = HeyBoxBot;
//# sourceMappingURL=index.js.map