UNPKG

ntqq-cqhttp

Version:

基于 ntqq onebot 框架进行二次封装的 sdk 开发包,主要方便直接进行机器人的操作。

317 lines (313 loc) 12.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.CQBotSDK = void 0; const axios_1 = __importDefault(require("axios")); const Robot_1 = require("./bot/Robot"); const express_1 = __importDefault(require("express")); const body_parser_1 = __importDefault(require("body-parser")); /* ************************************************************** * 机器人的主类 * 使用工厂模式实例化一个机器人视为开启一个机器人的操控,该机器人的 * 动作、事件均由此类的公共方法实现,通过其他各模块进行扩展维护。 * 开发思路 * 1、机器人实例化的机器人通过 Map 存储,主控信息轮询获取获消息的 * qq 然后通过 Map 查询并分发下去 * 2、可以通过机器人配置默认类,仅在构造函数传入 qq 号即可完成一个 * 机器人的实例化。 * 3、通过 SDK 首先实例化一个平台对象,传入框架服务器的相关信息 ***************************************************************/ /********************** Tools Functions *************************/ function unicode2string(unicode) { let res = unicode.replace(/\\\\/g, '\\'); return res ? res : '[]'; } // 处理一些无法被 JSON 解析的特殊字符例如 ASCII 为 0~28 的 function dealSomeStr(strCode) { let obj = strCode.split(''); obj.forEach((item, index) => { if (item.charCodeAt(0) <= 28) { obj[index] = ''; } }); return obj.join(''); } /** * @param {Object} dataPack 数据包 * @描述 格式化数据包规范 * @returns 如果找不到类型则返回null */ function getFormatData(dataPack) { let formatData = new Object(); switch (dataPack.post_type) { // 私聊,群聊类型 case 'message': { switch (dataPack.message_type) { // 群组消息 case 'group': Object.assign(formatData, { fromUser: dataPack.sender.user_id, fromGroup: dataPack.group_id, rawMessage: filterAt(dataPack.raw_message, dataPack.self_id), robot: dataPack.self_id, isAt: isAt(dataPack.raw_message, dataPack.self_id), QQInfo: { card: dataPack.sender.card, // 群里的自定义名称 nickname: dataPack.sender.nickname }, success: true }); break; // 私聊消息 case 'private': Object.assign(formatData, { fromUser: dataPack.user_id, rawMessage: filterAt(dataPack.raw_message, dataPack.self_id), robot: dataPack.self_id, isAt: isAt(dataPack.raw_message, dataPack.self_id), QQInfo: { nickname: dataPack.sender.nickname // QQ 昵称 }, success: true }); break; } break; } // 通知事件 case 'notice': Object.assign(formatData, { fromUser: {}, fromGroup: {}, rawMessage: '', robot: dataPack.self_id, isAt: false, QQInfo: {}, user_id: dataPack.user_id, group_id: dataPack.group_id, notice_type: dataPack.notice_type, operator: dataPack.operator_id, sub_type: dataPack.sub_type, success: true }); break; } Object.assign(formatData, { type: dataPack.post_type == 'message' ? dataPack.message_type : (dataPack.post_type == 'notice' ? 'notice' : '') }); return formatData; } // 过滤 @ 机器人 function filterAt(str, loginQQ) { if (!str) { return ''; } let reg = new RegExp(`\\[CQ:at,qq=${loginQQ}\\]`, 'g'); return str.replace(reg, '').trim(); } function isAt(str, loginQQ) { return (str.indexOf(`[CQ:at,qq=${loginQQ}]`) != -1); } /****************************************************************/ /** * 框架类,实例化以后以 create 方法传入 qq 号实例化一个机器人 */ class CQBotSDK { /** * 初始化一个机器人框架 * @param {string} host 框架主机地址 127.0.0.1 * @param {number} postPort 上报端口,如果多q均可使用该端口,配置时多个q也需要同一个反向 port 默认5701 * @param {string} postPath 上报路径如 '/botmsg',反向 post url 'http://127.0.0.1:5701/botmsg',默认为空 */ constructor(host, postPort = 5701, postPath = '') { this.botID = 0; // bot ID this.host = '127.0.0.1'; this.postPort = 5701; // 上报端口 this.postPath = ''; // 上报路径 this.botList = new Map(); this.host = host; this.postPort = postPort; this.postPath = postPath; // 有设置上报地址则采用 if (this.postPort) { // 创建事件上报监听服务 let app = (0, express_1.default)(); app.use((0, body_parser_1.default)({ extended: false })); app.post('/' + this.postPath, (req, response) => { let objData = req.body; // 遍历数据包然后分发下去且过滤掉自己发送的消息 let resData = getFormatData(objData); if (resData && resData.success && resData.robot !== resData.fromUser) { // console.log(resData); // 获取 bot 对象并正确将消息分发下去 let botArray = this.botList.get(String(resData.robot)); if (botArray) { botArray.forEach(bot => { bot.fire(resData.type, resData); }); } } response.send(); }); app.listen(this.postPort, '0.0.0.0', () => { console.log('post listen start for ' + this.postPort + '...'); }); } else { /* let timeStamp = Math.round(new Date().getTime() / 1000) let ws: WebSocket = null; let timeCount = 0; let isReceiveHeart: boolean = false; // 没有上报地址则使用 ws let initWs = () => { if (ws) { ws.removeAllListeners() ws.terminate(); } timeCount = 0; ws = new WebSocket(`${url.replace('http', 'ws')}/ws?user=${user}&timestamp=${timeStamp}&signature=${md5(user + "/ws" + md5(pass) + timeStamp.toString())}`) console.log('ws started ...'); // 接受信息 ws.on('message', (message: String) => { if (message.toString() == '{"type":"heartbeatreply"}') { // 返回的心跳检测数据 // console.log(message.toString()) isReceiveHeart = true return; } let data = message.toString().replace(/\\\\/g, '\\'); let originData: Array<any> = null; if (data && 'string' == typeof data) { let res = data.split('\n') res.forEach((elm, index) => { // 测试崩溃 JSON.parse(unicode2string(elm)); res[index] = JSON.parse(dealSomeStr(unicode2string(elm))) }) originData = res } else { originData = data ? [JSON.parse(dealSomeStr(unicode2string(JSON.stringify(data))))] : []; } for (let key in originData) { let obj = originData[key]; // 排除掉来自机器人的消息 if (obj.fromqq && obj.fromqq.qq == obj.logonqq) { continue } // 遍历数据包然后分发下去且过滤掉自己发送的消息 let resData = (getFormatData(obj) as any) if (resData && resData.success && resData.robot !== resData.fromUser) { // 获取 bot 对象并正确将消息分发下去 let botArray = this.botList.get(String(resData.robot)); if (botArray) { botArray.forEach(bot => { bot.fire(resData.type, resData) }) } } } }) ws.on('error', error => { // console.log('Error:', error) console.log('connect closed and will reconnect ws...'); initWs(); }) ws.on('close', info => { console.log('connect closed ... info:' + info + ' , will reconnect ws...'); initWs(); }) } let timeRepeatFun = () => { setTimeout(timeRepeatFun, 5000); try { timeCount += 5; // console.log('----------------------------------') // console.log('send heart , ws hold ' + timeCount + 's ...') } catch (error) { initWs(); } isReceiveHeart = false; setTimeout(() => { // 收不到心跳 1s 后进行重连 if (!isReceiveHeart) { initWs(); } }, 1000) } setTimeout(() => { timeRepeatFun(); }, 5000) initWs(); */ } } // 为每个 bot 创建自己的 axios 请求 createHttp(listPort) { let botHttp = axios_1.default.create({ baseURL: `http://${this.host}:${listPort}`, timeout: 1000 * 20 // 超时时间 20s }); botHttp.defaults.withCredentials = true; // 请求拦截器,本后台管理系统的所有请求均带上 token botHttp.interceptors.request.use(function (config) { config.headers = { 'Content-Type': 'application/x-www-form-urlencoded', }; return config; }, function (error) { // axios发生错误的处理 return Promise.reject(error); }); // 响应拦截器,不要那么多复杂数据了直接返回 data 就行 botHttp.interceptors.response.use(function (response) { let returnData = response.data; try { } catch (error) { console.log(error); } return returnData; }, function (error) { // axios请求服务器端发生错误的处理 return Promise.reject(error); }); return botHttp; } /** * 创建一个 bot * @param qq QQ号码 * @param listPort qq监听的正向端口 */ createBot(qq, listPort) { qq = String(qq); ++this.botID; const bot = new Robot_1.Robot(qq, this.createHttp(listPort), this.botID); let botArray = this.botList.get(qq); if (!botArray) { botArray = []; } botArray.push(bot); this.botList.set(qq, botArray); return bot; } /** * 销毁一个 bot * @param botID bot的id */ destroyBot(bot) { let botArray = this.botList.get(String(bot.QQ)); if (!botArray) { return; } } } exports.CQBotSDK = CQBotSDK; // 防止异常中断 process.on('uncaughtException', function (err) { console.log('Caught exception: ' + err); });