diff --git a/.gitignore b/.gitignore index aaf4567..942e437 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ config.yml +subscribe.yml node_modules/ -package-lock.json \ No newline at end of file +dist/ diff --git a/BroadcastChannel.js b/BroadcastChannel.js deleted file mode 100644 index 6b01f0e..0000000 --- a/BroadcastChannel.js +++ /dev/null @@ -1,31 +0,0 @@ -var Channel = require('./Channel'); - -class BroadcastChannel extends Channel { - constructor(app){ - super(app, {}); - } - - initialize(){ - this.channelName = 'broadcast'; - this.baseTemplate = '{{data.message}}'; - this.parseTemplate = this.buildTemplateCallback(this.baseTemplate); - - this.initPush(); - } - - onMessage(data){ - try { - let finalMessage = this.parseMessage(data); - if(data.target.group){ - this.app.robot.sendToGroup(data.target.group, finalMessage); - } - if(data.target.user){ - this.app.robot.sendToUser(data.target.group, finalMessage); - } - } catch(ex){ - console.log(ex); - } - } -} - -module.exports = BroadcastChannel; \ No newline at end of file diff --git a/Channel.js b/Channel.js deleted file mode 100644 index 23addec..0000000 --- a/Channel.js +++ /dev/null @@ -1,101 +0,0 @@ -var utils = require('./Utils'); - -class Channel { - constructor(app, data){ - this.app = app; - - this.initialize(data); - } - - initialize(data){ - this.config = data; - - this.channelName = data.channel; - this.baseTemplates = data.templates; - this.prepareFileList = data.files; - this.receiver = data.receiver; - - this.initPush(); - this.initTemplates(); - } - - initPush(){ - this.channel = this.app.pusher.subscribe(this.channelName); - this.channel.bind_global(this.onPush.bind(this)); - } - - initTemplates(){ - this.template = {}; - for(let key in this.baseTemplates){ - let one = this.baseTemplates[key]; - this.template[key] = this.buildTemplateCallback(one); - } - } - - initPrepareFileList(){ - this.prepareFileCallback = {}; - for(let key in this.prepareFileList){ - let one = this.prepareFileList[key]; - this.prepareFileCallback[key] = this.buildPrepareFileCallback(one); - } - } - - destory(){ - this.app.pusher.unsubscribe(this.channelName); - this.channel.unbind(); - } - - parseTemplate(template){ - template = template.replace(/\\/g, "\\\\").replace(/\r\n/g, "\n").replace(/\n/g, "\\n").replace(/'/g, "\\'"); - if(template.indexOf('{{') == 0){ //开头是{{ - template = template.substr(2); - } else { - template = "'" + template; - } - - if(template.indexOf('}}') == template.length - 2){ //结尾是}} - template = template.substr(0, template.length - 2); - } else { - template = template + "'"; - } - - template = template.replace(/\{\{/g, "' + ").replace(/\}\}/g, " + '"); - return template; - } - - buildTemplateCallback(template){ - return eval('(function(data){ return ' + this.parseTemplate(template) + '; })').bind(this); - } - - buildPrepareFileCallback(cond){ - return eval('(function(data){ return ' + cond + '; })').bind(this); - } - - parseMessage(data){ - try { - return this.parseTemplate(data); - } catch(ex){ - return this.baseTemplate; - } - } - - onPush(channel, data){ - try { - if(channel.indexOf('pusher:') == 0 || !this.template[channel]){ - return; - } - - let finalMessage = this.template[channel](data); - if(this.receiver.group){ - this.app.robot.sendToGroup(this.receiver.group, finalMessage); - } - if(this.receiver.user){ - this.app.robot.sendToUser(this.receiver.user, finalMessage); - } - } catch(ex){ - console.log(ex); - } - } -} - -module.exports = Channel; \ No newline at end of file diff --git a/ChannelManager.js b/ChannelManager.js deleted file mode 100644 index 1969f9f..0000000 --- a/ChannelManager.js +++ /dev/null @@ -1,37 +0,0 @@ -var fs = require('fs'); -var path = require('path'); - -var Yaml = require('yaml'); -var Channel = require('./Channel'); -var BroadcastChannel = require('./BroadcastChannel'); - -class ChannelManager { - constructor(app, configPath){ - this.app = app; - this.configPath = configPath; - this.channels = {}; - - this.initChannels(); - } - - initChannels(){ - let files = fs.readdirSync(this.configPath); - files.forEach((file) => { - if(!file.match(/\.yml$/)){ - return; - } - let name = file.replace(/\.yml$/, ''); - let content = fs.readFileSync(this.configPath + '/' + file, {encoding: 'utf-8'}); - let config = Yaml.parse(content); - let channel = new Channel(this.app, config); - - this.channels[name] = channel; - - console.log('已加载Channel配置: ' + name + ',对应channel: ' + config.channel); - }); - - this.channels['broadcast'] = new BroadcastChannel(this.app); - } -} - -module.exports = ChannelManager; \ No newline at end of file diff --git a/QQRobot.js b/QQRobot.js deleted file mode 100644 index 9405f57..0000000 --- a/QQRobot.js +++ /dev/null @@ -1,67 +0,0 @@ -const request = require('request-promise'); - -class QQRobot { - constructor(config){ - this.endpoint = 'http://' + config.host; - } - - /** - * 发送私聊消息 - * @param {int|int[]} user - QQ号 - * @param {string} message - 消息 - * @returns {Promise} 回调 - */ - sendToUser(user, message){ - if(Array.isArray(user)){ //发送给多个用户的处理 - let queue = []; - user.forEach((one) => { - queue.push(this.sendToUser(one, message)); - }); - return Promise.all(queue); - } - - return this.doApiRequest('send_private_msg', { - user_id: user, - message: message, - }); - } - - /** - * 发送群消息 - * @param {int|int[]} group - 群号 - * @param {string} message - 消息 - * @returns {Promise} 回调 - */ - sendToGroup(group, message){ - if(Array.isArray(group)){ //发送给多个用户的处理 - let queue = []; - group.forEach((one) => { - queue.push(this.sendToGroup(one, message)); - }); - return Promise.all(queue); - } - - return this.doApiRequest('send_group_msg', { - group_id: group, - message: message, - }); - } - - /** - * 执行酷Q的API调用 - * @param {string} method - 方法名 - * @param {any} data - 数据 - * @returns {Promise} 回调 - */ - doApiRequest(method, data){ - let opt = { - method: 'POST', - uri: this.endpoint + '/' + method, - body: data, - json: true, - } - return request(opt); - } -} - -module.exports = QQRobot; \ No newline at end of file diff --git a/README.md b/README.md index 0730946..32e611d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Pusher2CoolQ -将Pusher的推送消息转换为酷Q消息发送(通过CoolQ Http) +# Pusher2QQ +将Pusher的推送消息转换为QQ机器人消息发送(例如通过CoolQ Http) 方便配合服务器可用性监测系统,提供比邮件更加实时的提示功能 仅支持单向推送 \ No newline at end of file diff --git a/Utils.js b/Utils.js deleted file mode 100644 index e9135f1..0000000 --- a/Utils.js +++ /dev/null @@ -1,17 +0,0 @@ -class Utils { - static dictJoin(dict, d1 = ": ", d2 = "\n"){ - let lines = []; - for(var key in dict){ - let value = dict[key]; - lines.push(key + d1 + value); - } - return lines.join(d2); - } - - static getCurrentDate(){ - let date = new Date(); - return date.getFullYear() + '年' + date.getMonth() + '月' + date.getDate() + '日'; - } -} - -module.exports = Utils; \ No newline at end of file diff --git a/channels/debug.yml b/channels/debug.yml deleted file mode 100644 index f182c3a..0000000 --- a/channels/debug.yml +++ /dev/null @@ -1,8 +0,0 @@ -channel: "debug" -templates: - message: "{{data.message}}" -receiver: - group: - - 11111111111 - user: - - 11111111111 \ No newline at end of file diff --git a/channels/feed.yml b/channels/feed.yml deleted file mode 100644 index 72c5fe5..0000000 --- a/channels/feed.yml +++ /dev/null @@ -1,12 +0,0 @@ -channel: "feed" -files: - thumb: "data.image" -templates: - thumb: "[CQ:image,file={{data.image}}]" - feed: "{{utils.getCurrentDate()}}的异世界百科摘要:\n{{utils.dictJoin(data.feedList)}}" -receiver: - group: - - 111111111 - - 111111111 - user: - - 111111111 \ No newline at end of file diff --git a/channels/isekaiwiki/_group.yml b/channels/isekaiwiki/_group.yml new file mode 100644 index 0000000..d370f19 --- /dev/null +++ b/channels/isekaiwiki/_group.yml @@ -0,0 +1 @@ +name: "异世界百科" diff --git a/channels/isekaiwiki/admin/_group.yml b/channels/isekaiwiki/admin/_group.yml new file mode 100644 index 0000000..3766773 --- /dev/null +++ b/channels/isekaiwiki/admin/_group.yml @@ -0,0 +1 @@ +name: "管理信息" diff --git a/channels/isekaiwiki/admin/autoReview.yml b/channels/isekaiwiki/admin/autoReview.yml new file mode 100644 index 0000000..95b1e86 --- /dev/null +++ b/channels/isekaiwiki/admin/autoReview.yml @@ -0,0 +1,11 @@ +name: 自动审核通过 +provider: pusher +source: + service: pusher + channel: isekaiwiki + type: autoReview +tpl: + default: |- + {{data.author}} 更新了新页面: {{data.title}} + {{data.summary}} + {{data.result}} diff --git a/channels/isekaiwiki/admin/needReview.yml b/channels/isekaiwiki/admin/needReview.yml new file mode 100644 index 0000000..0ef04fe --- /dev/null +++ b/channels/isekaiwiki/admin/needReview.yml @@ -0,0 +1,11 @@ +name: 待审页面 +provider: pusher +source: + service: pusher + channel: isekaiwiki + type: needReview +tpl: + default: |- + Review队列更新: + {{data.author}} 更新了新页面: {{data.title}} + {{data.summary}} diff --git a/channels/isekaiwiki/editPage.yml b/channels/isekaiwiki/editPage.yml new file mode 100644 index 0000000..3b2a693 --- /dev/null +++ b/channels/isekaiwiki/editPage.yml @@ -0,0 +1,11 @@ +name: 修改页面 +provider: pusher +source: + service: pusher + channel: isekaiwiki + type: edit +tpl: + default: |- + {{data.author}} 更新了页面: {{data.title}} + {{data.summary}} + {{data.url}} diff --git a/channels/isekaiwiki/newPage.yml b/channels/isekaiwiki/newPage.yml new file mode 100644 index 0000000..867490c --- /dev/null +++ b/channels/isekaiwiki/newPage.yml @@ -0,0 +1,11 @@ +name: 新页面 +provider: pusher +source: + service: pusher + channel: isekaiwiki + type: new +tpl: + default: |- + {{data.author}} 创建了新页面: {{data.title}} + {{data.summary}} + {{data.url}} diff --git a/channels/serverStatus.yml b/channels/serverStatus.yml deleted file mode 100644 index 19dab50..0000000 --- a/channels/serverStatus.yml +++ /dev/null @@ -1,10 +0,0 @@ -channel: "server_status" -templates: - disconnect: "{{data.srcName}} 与 {{data.dstName}} 的连接已断开" - connect: "{{data.srcName}} 与 {{data.dstName}} 的连接已恢复" -receiver: - group: - - 111111111 - - 111111111 - user: - - 111111111 \ No newline at end of file diff --git a/index.js b/index.js index 174b882..0c87bcb 100644 --- a/index.js +++ b/index.js @@ -1,37 +1,2 @@ -var fs = require('fs'); - -var Yaml = require('yaml'); -var QQRobot = require('./QQRobot'); -var ChannelManager = require('./ChannelManager'); -var Pusher = require('pusher-js'); - -class App { - constructor(){ - this.config = Yaml.parse(fs.readFileSync('./config.yml', {encoding: 'utf-8'})); - - this.initRobot(); - this.initPusher(); - this.initChannelManager(); - console.log('加载完成,正在接收消息'); - } - - initRobot(){ - this.robot = new QQRobot(this.config.robot); - } - - initPusher(){ - this.pusher = new Pusher(this.config.pusher.key, { - cluster: this.config.pusher.cluster, - forceTLS: true, - }); - if(this.config.debug){ - Pusher.logToConsole = true; - } - } - - initChannelManager(){ - this.channels = new ChannelManager(this, this.config.channel_config_path); - } -} - -new App(); \ No newline at end of file +var App = require('./dist/App').default; +new App(__dirname + "/config.yml"); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0483405 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,909 @@ +{ + "name": "isekai-feedbot", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/runtime": { + "version": "7.9.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", + "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@cspotcode/source-map-consumer": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", + "integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz", + "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==", + "dev": true, + "requires": { + "@cspotcode/source-map-consumer": "0.8.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", + "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", + "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", + "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", + "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", + "dev": true + }, + "@types/bluebird": { + "version": "3.5.36", + "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.36.tgz", + "integrity": "sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q==", + "dev": true + }, + "@types/braces": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/braces/-/braces-3.0.1.tgz", + "integrity": "sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==", + "dev": true + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==", + "dev": true, + "requires": { + "@types/braces": "*" + } + }, + "@types/node": { + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", + "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==" + }, + "@types/request": { + "version": "2.48.4", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz", + "integrity": "sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } + } + }, + "@types/request-promise": { + "version": "4.1.48", + "resolved": "https://registry.npmjs.org/@types/request-promise/-/request-promise-4.1.48.tgz", + "integrity": "sha512-sLsfxfwP5G3E3U64QXxKwA6ctsxZ7uKyl4I28pMj3JvV+ztWECRns73GL71KMOOJME5u1A5Vs5dkBqyiR1Zcnw==", + "dev": true, + "requires": { + "@types/bluebird": "*", + "@types/request": "*" + } + }, + "@types/throttle-debounce": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/@types/throttle-debounce/download/@types/throttle-debounce-2.1.0.tgz", + "integrity": "sha1-HD32JL/Eti+ZLTASuExW1B6rN3Y=", + "dev": true + }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==" + }, + "@types/yaml": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@types/yaml/-/yaml-1.9.7.tgz", + "integrity": "sha512-8WMXRDD1D+wCohjfslHDgICd2JtMATZU8CkhH8LVJqcJs6dyYj5TGptzP8wApbmEullGBSsCEzzap73DQ1HJaA==", + "dev": true, + "requires": { + "yaml": "*" + } + }, + "acorn": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", + "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, + "ajv": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", + "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npm.taobao.org/anymatch/download/anymatch-3.1.1.tgz", + "integrity": "sha1-xV7PAhheJGklk5kxDBc84xIzsUI=", + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npm.taobao.org/binary-extensions/download/binary-extensions-2.2.0.tgz?cache=0&sync_timestamp=1610299293319&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fbinary-extensions%2Fdownload%2Fbinary-extensions-2.2.0.tgz", + "integrity": "sha1-dfUC7q+f/eQvyYgpZFvk6na9ni0=" + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npm.taobao.org/braces/download/braces-3.0.2.tgz", + "integrity": "sha1-NFThpGLujVmeI23zNs2epPiv4Qc=", + "requires": { + "fill-range": "^7.0.1" + } + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.nlark.com/call-bind/download/call-bind-1.0.2.tgz", + "integrity": "sha1-sdTonmiBGcPJqQOtMKuy9qkZvjw=", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npm.taobao.org/chokidar/download/chokidar-3.5.1.tgz?cache=0&sync_timestamp=1610719440699&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fchokidar%2Fdownload%2Fchokidar-3.5.1.tgz", + "integrity": "sha1-7pznu+vSt59J8wR5nVRo4x4U5oo=", + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cron-parser": { + "version": "3.5.0", + "resolved": "https://registry.nlark.com/cron-parser/download/cron-parser-3.5.0.tgz", + "integrity": "sha1-sanalRTAMQqn75nC8/HQ+MI1JXw=", + "requires": { + "is-nan": "^1.3.2", + "luxon": "^1.26.0" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debrief": { + "version": "1.5.0", + "resolved": "https://registry.nlark.com/debrief/download/debrief-1.5.0.tgz", + "integrity": "sha1-nuMcTKumUc6Hw6KGxFXfJPmAP0k=" + }, + "decoders": { + "version": "1.25.3", + "resolved": "https://registry.nlark.com/decoders/download/decoders-1.25.3.tgz", + "integrity": "sha1-amTZm8BdIlCk1AFOi2ikcpVpMeY=", + "requires": { + "debrief": "^1.5.0", + "lemons": "^1.4.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.nlark.com/define-properties/download/define-properties-1.1.3.tgz?cache=0&sync_timestamp=1618847174317&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fdefine-properties%2Fdownload%2Fdefine-properties-1.1.3.tgz", + "integrity": "sha1-z4jabL7ib+bbcJT2HYcMvYTO6fE=", + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npm.taobao.org/fill-range/download/fill-range-7.0.1.tgz", + "integrity": "sha1-GRmmp8df44ssfHflGYU12prN2kA=", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npm.taobao.org/fsevents/download/fsevents-2.3.2.tgz?cache=0&sync_timestamp=1612537044236&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ffsevents%2Fdownload%2Ffsevents-2.3.2.tgz", + "integrity": "sha1-ilJveLj99GI7cJ4Ll1xSwkwC/Ro=", + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.nlark.com/function-bind/download/function-bind-1.1.1.tgz", + "integrity": "sha1-pWiZ0+o8m6uHS7l3O3xe3pL0iV0=" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.nlark.com/get-intrinsic/download/get-intrinsic-1.1.1.tgz", + "integrity": "sha1-FfWfN2+FXERpY5SPDSTNNje0q8Y=", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "requires": { + "is-glob": "^4.0.1" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.nlark.com/has/download/has-1.0.3.tgz", + "integrity": "sha1-ci18v8H2qoJB8W3YFOAR4fQeh5Y=", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.nlark.com/has-symbols/download/has-symbols-1.0.2.tgz", + "integrity": "sha1-Fl0wcMADCXUqEjakeTMeOsVvFCM=" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "is-base64": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-base64/-/is-base64-1.1.0.tgz", + "integrity": "sha512-Nlhg7Z2dVC4/PTvIFkgVVNvPHSO2eR/Yd0XzhGiXCXEvWnptXlXa/clQ8aePPiMuxEGcWfzWbGw2Fe3d+Y3v1g==" + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npm.taobao.org/is-binary-path/download/is-binary-path-2.1.0.tgz", + "integrity": "sha1-6h9/O4DwZCNug0cPhsCcJU+0Wwk=", + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npm.taobao.org/is-extglob/download/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npm.taobao.org/is-glob/download/is-glob-4.0.1.tgz", + "integrity": "sha1-dWfb6fL14kZ7x3q4PEopSCQHpdw=", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-nan": { + "version": "1.3.2", + "resolved": "https://registry.nlark.com/is-nan/download/is-nan-1.3.2.tgz", + "integrity": "sha1-BDpUreoxdItVts1OCara+mm9nh0=", + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npm.taobao.org/is-number/download/is-number-7.0.0.tgz", + "integrity": "sha1-dTU0W4lnNNX4DE0GxQlVUnoU8Ss=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "lemons": { + "version": "1.6.0", + "resolved": "https://registry.nlark.com/lemons/download/lemons-1.6.0.tgz", + "integrity": "sha1-ScGH4I+WdaHvPg8He0GSgFUx0b8=" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "long-timeout": { + "version": "0.1.1", + "resolved": "https://registry.npm.taobao.org/long-timeout/download/long-timeout-0.1.1.tgz", + "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=" + }, + "lua-runner": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/lua-runner/download/lua-runner-2.0.3.tgz", + "integrity": "sha1-GV+PYVhMioURjgHVE75k5THXCFk=" + }, + "luxon": { + "version": "1.28.0", + "resolved": "https://registry.nlark.com/luxon/download/luxon-1.28.0.tgz", + "integrity": "sha1-5/ltqtOTjAamLeD7AnEV0lElH78=" + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.nlark.com/micromatch/download/micromatch-4.0.4.tgz", + "integrity": "sha1-iW1Rnf6dsl/OlM63pQCRm/iB6/k=", + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "dependencies": { + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/picomatch/download/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + } + } + }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, + "node-schedule": { + "version": "2.0.0", + "resolved": "https://registry.npm.taobao.org/node-schedule/download/node-schedule-2.0.0.tgz", + "integrity": "sha1-c6tJV9BWxjcIQJzB+rZ24OFJwZE=", + "requires": { + "cron-parser": "^3.1.0", + "long-timeout": "0.1.1", + "sorted-array-functions": "^1.3.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npm.taobao.org/normalize-path/download/normalize-path-3.0.0.tgz", + "integrity": "sha1-Dc1p/yOhybEf0JeDFmRKA4ghamU=" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.nlark.com/object-keys/download/object-keys-1.1.1.tgz", + "integrity": "sha1-HEfyct8nfzsdrwYWd9nILiMixg4=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npm.taobao.org/picomatch/download/picomatch-2.2.2.tgz", + "integrity": "sha1-IfMz6ba46v8CRo9RRupAbTRfTa0=" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "pusher": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/pusher/-/pusher-3.0.1.tgz", + "integrity": "sha512-jrI4N33paSh1vsYvEJx7QmXbf/zeeHIeoEAREqj4i2jJdK5I2FxhS96DsRl8+iwBJcsnk6TMMbfIrDniAFs5AA==", + "requires": { + "@types/request": "^2.47.1", + "is-base64": "^1.1.0", + "request": "2.88.0", + "tweetnacl": "^1.0.0", + "tweetnacl-util": "^0.15.0" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" + } + } + }, + "pusher-js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-5.1.1.tgz", + "integrity": "sha512-f2tdoA7NvJQkU8Y/iCH25ZSNGxnwCXrVbwos38isX6gnjsSZ1aksWvyZddm2N0sHJWyl+oPBz/MzU5cevVDyEQ==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npm.taobao.org/readdirp/download/readdirp-3.5.0.tgz", + "integrity": "sha1-m6dMAZsV02UnjS6Ru4xI17TULJ4=", + "requires": { + "picomatch": "^2.2.1" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "request-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", + "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "requires": { + "lodash": "^4.17.15" + } + }, + "safe-buffer": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sorted-array-functions": { + "version": "1.3.0", + "resolved": "https://registry.npm.taobao.org/sorted-array-functions/download/sorted-array-functions-1.3.0.tgz", + "integrity": "sha1-hgVpVWMpTf+yyXltYCvYRZ96DdU=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + }, + "throttle-debounce": { + "version": "3.0.1", + "resolved": "https://registry.nlark.com/throttle-debounce/download/throttle-debounce-3.0.1.tgz", + "integrity": "sha1-MvlNhN+olPeGyaHykOemRbahmrs=" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npm.taobao.org/to-regex-range/download/to-regex-range-5.0.1.tgz", + "integrity": "sha1-FkjESq58jZiKMmAY7XL1tN0DkuQ=", + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "ts-node": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz", + "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "0.7.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "yn": "3.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "tweetnacl-util": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", + "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==" + }, + "typescript": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz", + "integrity": "sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==", + "dev": true + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "yaml": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.8.3.tgz", + "integrity": "sha512-X/v7VDnK+sxbQ2Imq4Jt2PRUsRsP7UcpSl3Llg6+NRRqWLIvxkMFYtH1FmvwNGYRKKPa+EPA4qDBlI9WVG1UKw==", + "requires": { + "@babel/runtime": "^7.8.7" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 1a34535..eb44d71 100644 --- a/package.json +++ b/package.json @@ -1,18 +1,35 @@ { - "name": "isekai-pusher2coolq", + "name": "isekai-feedbot", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { - "run": "node index.js" + "start": "node index.js", + "start-dev": "tsc && node index.js", + "build": "tsc" }, "author": "hyperzlib", "license": "MIT", "dependencies": { + "chokidar": "^3.5.1", + "decoders": "^1.25.3", + "lua-runner": "^2.0.3", + "micromatch": "^4.0.4", + "node-schedule": "^2.0.0", "pusher": "^3.0.1", "pusher-js": "^5.1.1", "request": "^2.88.2", "request-promise": "^4.2.5", + "throttle-debounce": "^3.0.1", "yaml": "^1.8.3" + }, + "devDependencies": { + "@types/micromatch": "^4.0.2", + "@types/node": "^17.0.8", + "@types/request-promise": "^4.1.48", + "@types/throttle-debounce": "^2.1.0", + "@types/yaml": "^1.9.7", + "ts-node": "^10.4.0", + "typescript": "^4.5.4" } } diff --git a/push-test/index.js b/push-test/index.js index 2675bf3..789c86b 100644 --- a/push-test/index.js +++ b/push-test/index.js @@ -1,10 +1,19 @@ +var fs = require('fs'); +var Yaml = require('yaml'); var Pusher = require('pusher'); +var config = Yaml.parse(fs.readFileSync('../config.yml', {encoding: 'utf-8'})); + var pusher = new Pusher({ - appId: '', - key: '', - secret: '', - cluster: '' + appId: config.service.pusher.app_id, + key: config.service.pusher.key, + secret: config.service.pusher.secret, + cluster: config.service.pusher.cluster }); -pusher.trigger('debug', 'message', {"message": "Isekai Puser远端推送测试 (二周目)"}); \ No newline at end of file +pusher.trigger('isekai', 'newPage', { + author: "Hyperzlib", + title: "沙盒", + summary: "机器人应该可以用了", + url: "https://www.isekai.cn/ShaHe" +}); diff --git a/src/App.ts b/src/App.ts new file mode 100644 index 0000000..ce6ad52 --- /dev/null +++ b/src/App.ts @@ -0,0 +1,105 @@ +import fs from 'fs'; +import path from 'path'; +import Yaml from 'yaml'; +import { BaseProvider, MultipleMessage } from './base/provider/BaseProvider'; + +import { ChannelManager } from './ChannelManager'; +import { ChannelConfig, Config } from './Config'; +import { ProviderManager } from './ProviderManager'; +import { RobotManager } from './RobotManager'; +import { Service, ServiceManager } from './ServiceManager'; +import { SubscribeManager, Target } from './SubscribeManager'; + +export default class App { + public config: Config; + public srcPath: string = __dirname; + + public robot!: RobotManager; + public provider!: ProviderManager; + public service!: ServiceManager; + public subscribe!: SubscribeManager; + public channel!: ChannelManager; + + constructor(configFile: string) { + this.config = Yaml.parse(fs.readFileSync(configFile, { encoding: 'utf-8' })); + this.initialize(); + } + + async initialize() { + await this.initRobot(); + await this.initProviderManager(); + await this.initServiceManager(); + await this.initSubscribeManager(); + await this.initChannelManager(); + console.log('初始化完成,正在接收消息'); + } + + async initRobot() { + this.robot = new RobotManager(this, this.config.robot); + await this.robot.initialize(); + } + + async initProviderManager() { + this.provider = new ProviderManager(this); + await this.provider.initialize(); + } + + async initServiceManager() { + this.service = new ServiceManager(this, this.config.service); + await this.service.initialize(); + } + + async initSubscribeManager() { + this.subscribe = new SubscribeManager(this, this.config.subscribe_config); + await this.subscribe.initialize(); + } + + async initCommandManager() { + + } + + async initChannelManager() { + this.channel = new ChannelManager(this, this.config.channel_config_path); + + this.channel.on('add', (channelId) => { + this.subscribe.addChannel(channelId); + }); + this.channel.on('remove', (channelId) => { + this.subscribe.removeChannel(channelId); + }); + + await this.channel.initialize(); + } + + /** + * 获取服务 + * @param serviceName + * @returns + */ + getService(serviceName: string): T { + return this.service.get(serviceName); + } + + createChannel(provider: string, channelId: string, config: ChannelConfig): BaseProvider | null { + return this.provider.create(provider, channelId, config); + } + + getSubscriber(channelId: string, robotId: string): Target[] | null { + return this.subscribe.getSubscriber(channelId, robotId); + } + + /** + * 发送消息 + * @param channelId Channel ID + * @param messages 消息内容 + * @returns + */ + async sendMessage(channelId: string, messages: MultipleMessage): Promise { + console.log(`[${channelId}] 消息: `, messages); + this.robot.sendMessage(channelId, messages); + } + + require(file: string): any { + return require(path.join(this.srcPath, file)); + } +} diff --git a/src/ChannelManager.ts b/src/ChannelManager.ts new file mode 100644 index 0000000..76d7996 --- /dev/null +++ b/src/ChannelManager.ts @@ -0,0 +1,165 @@ +import fs from 'fs/promises'; +import path from 'path'; +import Yaml from 'yaml'; +import chokidar from 'chokidar'; +import { debounce } from 'throttle-debounce'; +import EventEmitter from 'events'; + +import App from './App'; +import { BaseProvider } from './base/provider/BaseProvider'; +import { ChannelConfig } from './Config'; + +export class ChannelManager extends EventEmitter { + private app: App; + private channelPath: string; + private loadChannelCallback: (file: string) => any; + private removeChannelCallback: (file: string) => any; + private setLoading?: debounce; + private watcher!: chokidar.FSWatcher; + + public channels: { [key: string]: BaseProvider }; + public channelName: { [key: string]: string }; + + constructor(app: App, channelPath: string) { + super(); + + this.app = app; + this.channelPath = channelPath; + this.channels = {}; + this.channelName = {}; + + this.loadChannelCallback = this.loadChannel.bind(this); + this.removeChannelCallback = this.removeChannel.bind(this); + } + + /** + * 加载所有Channel + */ + async initialize() { + this.watcher = chokidar.watch(this.channelPath, { + ignored: '*.bak', + ignorePermissionErrors: true, + persistent: true + }); + + this.watcher.on('add', this.loadChannelCallback); + this.watcher.on('change', this.loadChannelCallback); + this.watcher.on('unlink', this.removeChannelCallback); + } + + /** + * 获取Channel ID + * @param {string} file - channel config file + * @returns + */ + getChannelId(file: string): string { + let channelPath = path.relative(this.channelPath, file).replace(/\\/g, "/").replace(/\..*?$/, ""); + return channelPath; + } + + /** + * 获取Channel的全名 + * @param {string} channelId - channel ID + */ + getChannelFullName(channelId: string): string { + // 从最顶层开始查找 + let pathList = channelId.split("/"); + let nameList: string[] = []; + for (let i = 0; i < pathList.length; i++) { + let currentPath = pathList.slice(0, i + 1).join("/"); + console.log(currentPath); + let findedName = this.channelName[currentPath]; + if (findedName) { + nameList.push(findedName); + } else { + nameList.push(pathList[i]); + } + } + return nameList.join("/"); + } + + /** + * 获取Provider名 + * @param config + * @returns + */ + getProviderName(config: ChannelConfig): string { + return config.provider; + } + + /** + * 读取或更新Channel配置文件 + * @param {string} file + */ + async loadChannel(file: string) { + try { + let content = await fs.readFile(file, { encoding: 'utf-8' }); + let config = Yaml.parse(content); + let channelId = this.getChannelId(file); + if (path.basename(channelId) === "_group") { + // 只是group标记 + channelId = path.dirname(channelId); + this.channelName[channelId] = config?.name; + } else { + if (BaseProvider.checkConfig(config)) { + // console.log(`正在加载Channel: ${channelId}`); + // 处理channel + let providerName = this.getProviderName(config); + let isReload = false; + if (channelId in this.channels) { + // 重载配置 + isReload = true; + await this.channels[channelId].destory(); + } + // 读取配置 + let channel = this.app.createChannel(providerName, channelId, config); + if (channel) { + await channel.initialize(); + // 更新列表 + this.channels[channelId] = channel; + this.channelName[channelId] = config?.name; + if (isReload) { + this.emit('reload', channelId); + console.log(`已重载Channel: ${channelId}`); + } else { + this.emit('add', channelId); + console.log(`已加载Channel: ${channelId}`); + } + } + } else { + console.error(`配置文件: ${file} 格式错误`); + } + } + return true; + } catch (err) { + console.error(err); + return false; + } + } + + /** + * 移除Channel + * @param {string} file + */ + async removeChannel(file: string) { + let channelId = this.getChannelId(file); + if (path.basename(channelId) === "_group") { + // 仅删除组名 + channelId = path.basename(channelId); + delete this.channelName[channelId]; + } else { + let channel = this.channels[channelId]; + if (channel) { + await channel.destory(); + delete this.channels[channelId]; + delete this.channelName[channelId]; + this.emit('remove', channelId); + console.log("已移除Channel: ", this.getChannelFullName(channelId)); + } + } + } + + onLoad() { + + } +} diff --git a/src/Config.ts b/src/Config.ts new file mode 100644 index 0000000..349962f --- /dev/null +++ b/src/Config.ts @@ -0,0 +1,25 @@ +import { JsonFilterConfig } from "./generator/JsonFilter"; +import { RegexFilterConfig } from "./generator/RegexFilter"; + +export type Config = { + channel_config_path: string; + subscribe_config: string; + debug: boolean; + robot: { [key: string]: RobotConfig }; + service: { [key: string]: ServiceConfig }; +}; + +export type RobotConfig = { + type: string; + baseId: string; +}; + +export type ServiceConfig = { [name: string]: any }; + +export type ChannelConfig = any; + +export type GeneratorConfig = { + json: JsonFilterConfig; + match: RegexFilterConfig; + tpl: any; +}; diff --git a/src/ProviderManager.ts b/src/ProviderManager.ts new file mode 100644 index 0000000..b1c173b --- /dev/null +++ b/src/ProviderManager.ts @@ -0,0 +1,52 @@ +import fs from 'fs'; +import path from 'path'; +import App from './App'; +import { BaseProvider } from './base/provider/BaseProvider'; +import { ChannelConfig } from './Config'; + +const PROVIDER_PATH = __dirname + "/provider"; + +export class ProviderManager { + private app: App; + private providerClasses: { [key: string]: any } + + constructor(app: App) { + this.app = app; + this.providerClasses = {}; + } + + async initialize() { + for (let file of fs.readdirSync(PROVIDER_PATH)) { + let providerFile = `${PROVIDER_PATH}/${file}`; + if (providerFile.match(/\.(js|mjs)$/)) { + // 加载js文件 + let providerName = path.basename(providerFile).replace(/Provider\.(js|mjs)$/gi, "").toLocaleLowerCase(); + try { + let provider = require(providerFile)?.default; + if (!provider) { + throw new Error("provider is empty"); + } + this.providerClasses[providerName] = provider; + console.log(`已加载Provider: ${providerName}`); + } catch(err) { + console.log(`无法加载Provider: ${providerName}`, err); + } + } + } + } + + /** + * 创建Provider + * @param {string} providerName + * @param {any} config + */ + create(providerName: string, channelId: string, config: ChannelConfig): BaseProvider | null { + providerName = providerName.toLocaleLowerCase(); + if (providerName in this.providerClasses) { + let CurrentProvider: any = this.providerClasses[providerName]; + return new CurrentProvider(this.app, channelId, config); + } else { + return null; + } + } +} diff --git a/src/RobotManager.ts b/src/RobotManager.ts new file mode 100644 index 0000000..6a7cc77 --- /dev/null +++ b/src/RobotManager.ts @@ -0,0 +1,102 @@ +import fs from "fs"; +import path from "path"; + +import App from "./App"; +import { MultipleMessage } from "./base/provider/BaseProvider"; +import { RobotConfig } from "./Config"; +import { Target } from "./SubscribeManager"; + +const ROBOT_PATH = __dirname + "/robot"; + +export interface Robot { + initialize(): Promise; + sendMessage(targets: Target[], message: string): Promise; + baseId?: string; +} + +export class RobotManager { + private app: App; + private config: { [key: string]: RobotConfig }; + + private robotClasses: { [key: string]: any }; + private robots: { [key: string]: Robot }; + + constructor(app: App, config: { [key: string]: RobotConfig }) { + this.app = app; + this.config = config; + + this.robotClasses = {}; + this.robots = {}; + } + + async initialize() { + for (let file of fs.readdirSync(ROBOT_PATH)) { + let robotFile = `${ROBOT_PATH}/${file}`; + if (robotFile.match(/\.(js|mjs)$/)) { + // 加载js文件 + let robotName = path.basename(robotFile).replace(/Robot\.(js|mjs)$/gi, "").toLocaleLowerCase(); + try { + let robotClass = require(robotFile)?.default; + if (!robotClass) { + throw new Error("robot api is empty"); + } + this.robotClasses[robotName] = robotClass; + } catch(err) { + console.log(`无法加载Robot API: ${robotName}`, err); + } + } + } + + for (let robotId in this.config) { + let robotConfig = this.config[robotId]; + let robotType: string = robotConfig.type; + if (!robotType) { + console.error("无法加载 " + robotId + " Robot: 配置文件中未定义 'type'"); + continue; + } + robotType = robotType.toLocaleLowerCase(); + if (robotType in this.robotClasses) { + let robotClass = this.robotClasses[robotType]; + try { + let robotObject: Robot = new robotClass(this.app, robotId, robotConfig); + await robotObject.initialize(); + this.robots[robotId] = robotObject; + console.log(`已加载Robot: ${robotId}`); + } catch(err) { + console.error(`无法加载 ${robotId} Robot: `, err); + } + } else { + console.error(`无法加载 ${robotId} Robot: Robot不存在`); + } + } + } + + public async sendMessage(channelId: string, messages: MultipleMessage) { + for (let robotId in this.robots) { + let robot = this.robots[robotId]; + let baseId = robot.baseId; + let currentMsg: string | null = null; + if (robotId in messages) { + currentMsg = messages[robotId]; + } else if (baseId && baseId in messages) { + currentMsg = messages[baseId]; + } else if ("base" in messages) { + currentMsg = messages["base"]; + } + if (!currentMsg) { // 未找到消息 + continue; + } + + let targets = this.app.getSubscriber(channelId, robotId); + if (!targets) { + continue; + } + + try { + robot.sendMessage(targets, currentMsg); + } catch(err) { + console.error(`[${channelId}] 无法发送消息到 ${robotId} : `, err); + } + } + } +} diff --git a/src/ServiceManager.ts b/src/ServiceManager.ts new file mode 100644 index 0000000..0dbadb3 --- /dev/null +++ b/src/ServiceManager.ts @@ -0,0 +1,87 @@ +import fs from 'fs'; +import path from 'path'; + +import App from './App'; +import { ServiceConfig } from './Config'; + +const SERVICE_PATH = __dirname + "/service"; + +export interface Service { + initialize(): Promise; + destory(): Promise; +} + +export class ServiceNotExistsError extends Error { + public serviceName: string; + + constructor(message: string, serviceName: string) { + super(message); + this.serviceName = serviceName; + } +} + +export class ServiceManager { + private app: App; + private config: ServiceConfig; + + public serviceClasses: { [key: string]: any }; + public services: { [key: string]: Service }; + + constructor(app: App, config: ServiceConfig) { + this.app = app; + this.config = config; + + this.serviceClasses = {}; + this.services = {}; + } + + async initialize() { + for (let file of fs.readdirSync(SERVICE_PATH)) { + let serviceFile = `${SERVICE_PATH}/${file}`; + if (serviceFile.match(/\.(js|mjs)$/)) { + // 加载js文件 + let serviceName = path.basename(serviceFile).replace(/Service\.(js|mjs)$/gi, "").toLocaleLowerCase(); + try { + let serviceClass = require(serviceFile)?.default; + if (!serviceClass) { + throw new Error("service is empty"); + } + this.serviceClasses[serviceName] = serviceClass; + } catch(err) { + console.log(`无法加载Service: ${serviceName}`, err); + } + } + } + + for (let serviceName in this.config) { + let serviceConfig = this.config[serviceName]; + let serviceType: string = serviceConfig.type; + if (!serviceType) { + console.error(`无法加载 ${serviceName} Service: 配置文件中未定义 'type'`); + continue; + } + serviceType = serviceType.toLocaleLowerCase(); + if (serviceType in this.serviceClasses) { + let serviceClass = this.serviceClasses[serviceType]; + try { + let serviceObject: Service = new serviceClass(this.app, serviceConfig); + await serviceObject.initialize(); + this.services[serviceName] = serviceObject; + console.log(`已加载Service: ${serviceName}`); + } catch(err) { + console.error(`无法加载 ${serviceName} Service: `, err); + } + } else { + console.error(`无法加载 ${serviceName} Service: Service 不存在`); + } + } + } + + public get(name: string): T { + if (name in this.services) { + return this.services[name] as T; + } else { + throw new ServiceNotExistsError(`Service ${name} not exists`, name); + } + } +} diff --git a/src/SubscribeManager.ts b/src/SubscribeManager.ts new file mode 100644 index 0000000..1275677 --- /dev/null +++ b/src/SubscribeManager.ts @@ -0,0 +1,96 @@ +import fs from "fs"; +import Yaml from "yaml"; +import micromatch from "micromatch"; +import chokidar from 'chokidar'; + +import App from "./App"; + +export interface Target { + type: string; + identity: string; +} + +export class SubscribeManager { + private app: App; + private subscribeFile: string; + private watcher!: chokidar.FSWatcher; + private subscribeList: { + [channelId: string]: { + [robotId: string]: Target[] + } + }; + + private subscribeConfig: { + [robotId: string]: { + [targetType: string]: { + [targetIdentity: string]: string[] + } + } + }; + + constructor(app: App, subscribeFile: string) { + this.app = app; + this.subscribeFile = subscribeFile; + this.subscribeList = {}; + this.subscribeConfig = {}; + + this.loadSubscribeFile(); + } + + public async initialize() { + this.watcher = chokidar.watch(this.subscribeFile, { + ignorePermissionErrors: true, + persistent: true + }); + + this.watcher.on('change', () => { + this.reloadSubscribeFile(); + }); + } + + private loadSubscribeFile() { + this.subscribeConfig = Yaml.parse(fs.readFileSync(this.subscribeFile, { encoding: 'utf-8' })); + } + + private reloadSubscribeFile() { + this.loadSubscribeFile(); + this.subscribeList = {}; + for (let channelId in this.app.channel.channels) { + this.addChannel(channelId); + } + console.log('已重载Subscribe'); + } + + public addChannel(channelId: string) { + this.subscribeList[channelId] = {}; + for (let robotId in this.subscribeConfig) { + let targetConf = this.subscribeConfig[robotId]; + let matchedTargetList: Target[] = []; + for (let targetType in targetConf) { + let targetList = targetConf[targetType]; + for (let targetIdentity in targetList) { + let matchList = targetList[targetIdentity]; + if (micromatch.isMatch(channelId, matchList)) { + matchedTargetList.push({ + type: targetType, + identity: targetIdentity + }); + } + } + } + this.subscribeList[channelId][robotId] = matchedTargetList; + } + } + + public removeChannel(channelId: string) { + delete this.subscribeList[channelId]; + } + + public getSubscriber(channelId: string, robotId: string): Target[] | null { + if (channelId in this.subscribeList && robotId in this.subscribeList[channelId]) { + return this.subscribeList[channelId][robotId]; + } else { + return null; + } + } +} diff --git a/src/Utils.ts b/src/Utils.ts new file mode 100644 index 0000000..632a29b --- /dev/null +++ b/src/Utils.ts @@ -0,0 +1,40 @@ +export class Utils { + static dictJoin(dict: { [key: string]: any }, d1: string = ": ", d2: string = "\n"): string { + let lines: string[] = []; + for(var key in dict){ + let value = dict[key]; + lines.push(key + d1 + value); + } + return lines.join(d2); + } + + static getCurrentDate(): string { + let date = new Date(); + return date.getFullYear() + '年' + date.getMonth() + '月' + date.getDate() + '日'; + } + + static count(dict: { [key: string]: any }): number { + try { + return Object.keys(dict).length; + } catch(e) { + return 0; + } + } + + static sleep(time: number): Promise { + return new Promise((resolve) => { + let tid = setTimeout(() => { + resolve(); + clearTimeout(tid); + }, time); + }); + } + + static excerpt(text: string, maxLength: number, ellipsis: string = '……'): string { + if (text.length > maxLength) { + return text.substring(0, maxLength) + ellipsis; + } else { + return text; + } + } +} diff --git a/src/base/provider/BaseProvider.ts b/src/base/provider/BaseProvider.ts new file mode 100644 index 0000000..3861d7a --- /dev/null +++ b/src/base/provider/BaseProvider.ts @@ -0,0 +1,67 @@ +import App from "../../App"; +import { ChannelConfig } from "../../Config"; +import { Generator } from "../../generator/Generator"; + +export type MultipleMessage = { [type: string]: string }; + +export class BaseProvider { + public static providerName: string = ""; + public static defaultConfig: any = {}; + + protected app: App; + protected channelId: string; + protected config: ChannelConfig; + protected generator!: Generator; + + constructor(app: App, channelId: string, config: ChannelConfig) { + this.app = app; + this.channelId = channelId; + this.config = config; + } + + async initialize() { + this.generator = new Generator(this.app, this.config); + await this.generator.initialize(); + } + + async destory() { + if (this.generator) { + await this.generator.destory(); + } + } + + static checkConfig(config: any) { + if (typeof config.source !== "object") { + return false; + } + + return true; + } + + /** + * 发送消息 + */ + sendMessage(messages: MultipleMessage) { + this.app.sendMessage(this.channelId, messages) + .then(() => {}) + .catch((err) => { + this.error('无法发送消息', err); + }); + } + + /** + * 发送消息(异步) + */ + sendMessageAsync(messages: MultipleMessage): Promise { + return this.app.sendMessage(this.channelId, messages); + } + + /** + * 输出错误信息 + * @param {string} message + * @param {Error|undefined} err + */ + error(message: string, err?: Error) { + console.error(`[${this.channelId}] ${message}`, err); + } +} diff --git a/src/base/robot/BaseRobot.ts b/src/base/robot/BaseRobot.ts new file mode 100644 index 0000000..17ecd0b --- /dev/null +++ b/src/base/robot/BaseRobot.ts @@ -0,0 +1,3 @@ +export class BaseRobot { + +} \ No newline at end of file diff --git a/src/error/ConfigCheckError.ts b/src/error/ConfigCheckError.ts new file mode 100644 index 0000000..4d6ea91 --- /dev/null +++ b/src/error/ConfigCheckError.ts @@ -0,0 +1 @@ +export class ConfigCheckError extends Error { } \ No newline at end of file diff --git a/src/generator/Generator.ts b/src/generator/Generator.ts new file mode 100644 index 0000000..7844b05 --- /dev/null +++ b/src/generator/Generator.ts @@ -0,0 +1,75 @@ +import App from "../App"; +import { MultipleMessage } from "../base/provider/BaseProvider"; +import { GeneratorConfig } from "../Config"; +import { JsonFilter } from "./JsonFilter"; +import { RegexFilter } from "./RegexFilter"; +import { TemplateFilter } from "./TemplateFilter"; + +export interface MessageFilter { + initialize(): Promise; + destory(): Promise; + parse(data: any): Promise; +} + +/** + * 用于给推送生成文本内容 + */ +export class Generator { + private app: App; + private config: GeneratorConfig; + private filters: MessageFilter[]; + + constructor(app: App, config: GeneratorConfig) { + this.app = app; + this.config = config; + this.filters = []; + } + + async initialize() { + let filter: MessageFilter; + // 解析下载的json + if ('json' in this.config && this.config.json) { + filter = new JsonFilter(this.app, this.config.json); + await filter.initialize(); + this.filters.push(filter); + } + // 正则匹配内容,用于提取字符串内容 + if ('match' in this.config) { + filter = new RegexFilter(this.app, this.config.match); + await filter.initialize(); + this.filters.push(filter); + } + // 通过模板生成最终文本 + if ('tpl' in this.config) { + filter = new TemplateFilter(this.app, this.config.tpl); + await filter.initialize(); + this.filters.push(filter); + } + } + + async destory() { + for (let i = 0; i < this.filters.length; i ++) { + let filter = this.filters[i]; + try { + await filter.destory(); + delete this.filters[i]; + } catch(e) { + console.error(e); + } + } + } + + /** + * 生成模板 + */ + async generate(data: any): Promise { + let retData = data; + for (let filter of this.filters) { + let newData = await filter.parse(retData); + if (newData) { + retData = newData; + } + } + return retData; + } +} diff --git a/src/generator/JsonFilter.ts b/src/generator/JsonFilter.ts new file mode 100644 index 0000000..61bbfaa --- /dev/null +++ b/src/generator/JsonFilter.ts @@ -0,0 +1,36 @@ +import App from "../App"; +import { MultipleMessage } from "../base/provider/BaseProvider"; +import { MessageFilter } from "./Generator"; + +export type JsonFilterConfig = string[]; + +export class JsonFilter implements MessageFilter { + private app: App; + private config: JsonFilterConfig; + private keys!: string[]; + + constructor(app: App, config: JsonFilterConfig) { + /** @type {App} */ + this.app = app; + + /** @type {string[]} */ + this.config = config; + } + + async initialize() { + this.keys = this.config; + } + + async destory() { + + } + + async parse(data: any): Promise { + for (let key of this.keys) { + if (key in data && typeof data[key] === "string"){ + data[key] = JSON.parse(data[key]); + } + } + return data; + } +} diff --git a/src/generator/LuaFilter.ts b/src/generator/LuaFilter.ts new file mode 100644 index 0000000..89a297a --- /dev/null +++ b/src/generator/LuaFilter.ts @@ -0,0 +1,32 @@ +import App from "../App"; +import { MultipleMessage } from "../base/provider/BaseProvider"; +import { MessageFilter } from "./Generator"; + +export type LuaFilterConfig = { + +} + +/** + * 使用Lua过滤信息 + */ +export class LuaFilter implements MessageFilter { + private app: App; + private config: LuaFilterConfig; + + constructor(app: App, config: LuaFilterConfig) { + this.app = app; + this.config = config; + } + + async initialize() { + + } + + async destory() { + + } + + async parse(data: any): Promise { + return null; + } +} diff --git a/src/generator/RegexFilter.ts b/src/generator/RegexFilter.ts new file mode 100644 index 0000000..d10c91f --- /dev/null +++ b/src/generator/RegexFilter.ts @@ -0,0 +1,52 @@ +import App from "../App"; +import { MultipleMessage } from "../base/provider/BaseProvider"; +import { MessageFilter } from "./Generator"; + +export type RegexFilterConfig = { [key: string]: string | string[] }; + +export class RegexFilter implements MessageFilter { + private app: App; + private config: RegexFilterConfig; + private regexList: { [key: string]: RegExp[] }; + + constructor(app: App, config: RegexFilterConfig) { + this.app = app; + this.config = config; + this.regexList = {}; + } + + async initialize() { + for (let key in this.config) { + let patternList = this.config[key]; + if (typeof patternList === "string") { + patternList = [patternList]; + } + let regexList: RegExp[] = []; + patternList.forEach((one) => { + regexList.push(new RegExp(one)); + }); + this.regexList[key] = regexList; + } + } + + async destory() { + + } + + async parse(data: any): Promise { + for (let key in this.regexList) { + if (typeof data[key] !== "string") continue; + let str: string = data[key]; + let matchedGroup: { [key: string]: string } = {}; + let regexList = this.regexList[key]; + for (let regex of regexList) { + let matches = str.match(regex); + if (matches?.groups) { + matchedGroup = { ...matchedGroup, ...matches.groups }; + } + } + data[key] = matchedGroup; + } + return data; + } +} diff --git a/src/generator/TemplateFilter.ts b/src/generator/TemplateFilter.ts new file mode 100644 index 0000000..09e34bb --- /dev/null +++ b/src/generator/TemplateFilter.ts @@ -0,0 +1,107 @@ +import App from "../App"; +import { MultipleMessage } from "../base/provider/BaseProvider"; +import { ConfigCheckError } from "../error/ConfigCheckError"; + +// 请勿删除这些没有使用的导入,模板中会用到 +const { Utils } = require('../Utils'); + +export type TemplateFilterConfig = { [key: string]: string }; +export type TemplateRenderFunction = (data: any) => string; + +export class TemplateFilter { + private app: App; + private config: TemplateFilterConfig; + private renderFunctionList: { [target: string]: TemplateRenderFunction }; + + constructor(app: App, config: TemplateFilterConfig) { + this.app = app; + this.config = config; + this.renderFunctionList = {}; + + this.checkConfig(); + } + + async initialize() { + for (let key in this.config) { + let template = this.config[key]; + if (key === "default") { + key = "base"; + } + if (typeof template === "string") { + this.renderFunctionList[key] = this.buildTemplateCallback(template); + } + } + } + + async destory() { + for (let key in this.renderFunctionList){ + delete this.renderFunctionList[key]; + } + } + + checkConfig() { + if (!('base' in this.config) && !('default' in this.config)) { + throw new ConfigCheckError('Unset template.base or template.default'); + } + } + + /** + * 解析模板 + */ + parseTemplate(template: string): string { + template = template.replace(/\\/g, "\\\\").replace(/\r\n/g, "\n").replace(/\n/g, "\\n").replace(/'/g, "\\'"); + template = template.replace(/\{\{(.*?)\}\}/g, (str, token) => { + if (token) { + return "' + (" + (token.replace(/\\'/g, "'")) + ") + '"; + } else { + return str; + } + }); + + if(template.indexOf("' + (") == 0){ //开头是{{ + template = template.substr(4); + } else { + template = "'" + template; + } + + if(template.lastIndexOf(") + '") == template.length - 5){ //结尾是}} + template = template.substr(0, template.length - 4); + } else { + template = template + "'"; + } + + return template; + } + + /** + * 构建callback + * @param {string} template + * @returns {Function} + */ + buildTemplateCallback(template: string): TemplateRenderFunction { + const renderTpl = eval('(function(){ return ' + this.parseTemplate(template) + '; })') + return (data: any): string => { + let overridedKeys: string[] = []; + for (let key in data) { + if (!(key in global)) { + overridedKeys.push(key); + (global as any)[key] = data[key]; + } + } + let result = renderTpl(); + for (let key of overridedKeys) { + delete (global as any)[key]; + } + return result; + }; + } + + async parse(data: any): Promise { + let result: MultipleMessage = {}; + for (let target in this.renderFunctionList) { + let renderFunction = this.renderFunctionList[target]; + result[target] = renderFunction(data); + } + return result; + } +} diff --git a/src/module.d.ts b/src/module.d.ts new file mode 100644 index 0000000..bc131a2 --- /dev/null +++ b/src/module.d.ts @@ -0,0 +1 @@ +declare module 'yaml'; \ No newline at end of file diff --git a/src/provider/PusherProvider.ts b/src/provider/PusherProvider.ts new file mode 100644 index 0000000..fc1aef8 --- /dev/null +++ b/src/provider/PusherProvider.ts @@ -0,0 +1,82 @@ +import App from "../App"; +import { BaseProvider, MultipleMessage } from "../base/provider/BaseProvider"; +import { ChannelConfig } from "../Config"; +import { ConfigCheckError } from "../error/ConfigCheckError"; +import PusherService from "../service/PusherService"; + +const { string, optional, object, guard } = require("decoders"); + +export type PusherProviderConfig = { + source: { + service: string; + channel: string; + type: string; + } +} + +export default class PusherProvider extends BaseProvider { + static providerName = "pusher"; + static defaultConfig = { + source: { + service: "pusher" + } + }; + + protected config: PusherProviderConfig; + + private service: PusherService; + + /** + * @param {App} app + * @param {any} config + */ + constructor(app: App, channelId: string, config: ChannelConfig) { + super(app, channelId, config); + this.config = config; + + if (!this.checkConfig()) { + throw new ConfigCheckError("配置文件错误"); + } + + let service = app.getService(config.source.service); + this.service = service; + } + + checkConfig() { + let checkType = guard( + object({ + source: object({ + service: optional(string), + channel: string, + type: string, + }) + }) + ); + + return checkType(this.config); + } + + async initialize() { + await super.initialize(); + + // 绑定事件 + let srcConf = this.config.source; + this.service.on(srcConf.channel, srcConf.type, this.onData.bind(this)); + } + + async destory() { + let srcConf = this.config.source; + this.service.off(srcConf.channel, srcConf.type); + await super.destory(); + } + + async onData(data: any) { + let messages: MultipleMessage = {}; + try { + messages = await this.generator.generate(data); + this.sendMessage(messages); + } catch(err: any) { + this.error('无法解析数据', err); + } + } +} diff --git a/src/robot/QQRobot.ts b/src/robot/QQRobot.ts new file mode 100644 index 0000000..7c3973b --- /dev/null +++ b/src/robot/QQRobot.ts @@ -0,0 +1,109 @@ +import App from "../App"; +import { Robot } from "../RobotManager"; +import { Target } from "../SubscribeManager"; +import request from "request-promise"; +import { Utils } from "../Utils"; + +export type QQRobotConfig = { + user: string; + host: string; + baseId?: string; +} + +export default class QQRobot implements Robot { + private robotId: string; + private endpoint: string; + private botQQ: number; + + public baseId?: string; + + constructor(app: App, robotId: string, config: QQRobotConfig) { + this.robotId = robotId; + this.endpoint = 'http://' + config.host; + this.botQQ = parseInt(config.user); + + this.baseId = config.baseId; + } + + async initialize() { + + } + + /** + * 发送私聊消息 + * @param {int|int[]} user - QQ号 + * @param {string} message - 消息 + * @returns {Promise} 回调 + */ + async sendToUser(user: number|number[], message: string) { + if(Array.isArray(user)){ //发送给多个用户的处理 + for (let one of user) { + await this.sendToUser(one, message); + await Utils.sleep(100); + } + return; + } + + return await this.doApiRequest('send_msg', { + bot: this.botQQ, + type: 1, + qq: user, + msg: message, + }); + } + + /** + * 发送群消息 + */ + async sendToGroup(group: number|number[], message: string) { + if(Array.isArray(group)){ //发送给多个群组的处理 + for (let one of group) { + await this.sendToGroup(one, message); + await Utils.sleep(100); + } + return; + } + + return await this.doApiRequest('send_msg', { + bot: this.botQQ, + type: 2, + group: group, + msg: message, + }); + } + + /** + * 发送消息 + */ + async sendMessage(targets: Target[], message: string) { + let groupList: number[] = []; + let userList: number[] = []; + for (let target of targets) { + if (target.type === "group") { + groupList.push(parseInt(target.identity)); + } else if (target.type === "user") { + userList.push(parseInt(target.identity)); + } + } + + if (groupList.length > 0) { + await this.sendToGroup(groupList, message); + } + if (userList.length > 0) { + await this.sendToUser(userList, message); + } + } + + /** + * 执行API调用 + */ + async doApiRequest(method: string, data: any): Promise { + return await request({ + method: 'POST', + uri: this.endpoint + '/' + method, + body: data, + json: true, + timeout: 10000 + }); + } +} diff --git a/src/service/Cron.js b/src/service/Cron.js new file mode 100644 index 0000000..c3edcd0 --- /dev/null +++ b/src/service/Cron.js @@ -0,0 +1,3 @@ +class Cron { + +} \ No newline at end of file diff --git a/src/service/HttpQueue.js b/src/service/HttpQueue.js new file mode 100644 index 0000000..f06f847 --- /dev/null +++ b/src/service/HttpQueue.js @@ -0,0 +1,5 @@ +class HttpQueue { + +} + +module.exports = HttpQueue; \ No newline at end of file diff --git a/src/service/PusherService.ts b/src/service/PusherService.ts new file mode 100644 index 0000000..d205332 --- /dev/null +++ b/src/service/PusherService.ts @@ -0,0 +1,93 @@ +import App from "../App"; +import { Service } from "../ServiceManager"; +import Pusher, { Channel } from 'pusher-js'; +import { Utils } from "../Utils"; + +export type PusherServiceConfig = { + app_id: string; + key: string; + secret: string; + cluster: string; +}; + +export default class PusherService implements Service { + private app: App; + private config: PusherServiceConfig; + private channelList: { [name: string]: Channel }; + private callbackList: { [name: string]: { [type: string]: Function } } + + public pusher!: Pusher; + + constructor(app: App, config: PusherServiceConfig) { + this.app = app; + this.config = config; + + this.channelList = {}; + this.callbackList = {}; + } + + async initialize() { + this.pusher = new Pusher(this.config.key, { + cluster: this.config.cluster, + forceTLS: true, + }); + if(this.app.config.debug){ + Pusher.logToConsole = true; + } + } + + // 好像不需要 + async destory() { + + } + + /** + * 获取channel + */ + getChannel(channelName: string): Channel { + if (!(channelName in this.channelList)) { + this.channelList[channelName] = this.pusher.subscribe(channelName); + this.callbackList[channelName] = {}; + } + return this.channelList[channelName]; + } + + /** + * 解除订阅channel + */ + public unsubscribeChannel(channelName: string) { + this.pusher.unsubscribe(channelName); + if (channelName in this.channelList) { + delete this.channelList[channelName]; + delete this.callbackList[channelName]; + } + } + + /** + * 绑定事件 + */ + public on(channelName: string, eventName: string, callback: Function) { + // 先解除绑定之前的 + this.off(channelName, eventName); + + let channel = this.getChannel(channelName); + channel.bind(eventName, callback); + this.callbackList[channelName][eventName] = callback; + } + + /** + * 解除绑定事件 + */ + public off(channelName: string, eventName: string) { + if (!(channelName in this.channelList)) return; + let channel = this.channelList[channelName]; + let callback = this.callbackList[channelName][eventName]; + if (callback) { + channel.unbind(eventName); + delete this.callbackList[channelName][eventName]; + } + if (Utils.count(this.callbackList[channelName]) === 0) { + this.unsubscribeChannel(channelName); + } + } +} diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..65b876a --- /dev/null +++ b/start.bat @@ -0,0 +1,3 @@ +@echo off +cd /D %~dp0 +node index.js diff --git a/subscribe-example.yml b/subscribe-example.yml new file mode 100644 index 0000000..3c237b9 --- /dev/null +++ b/subscribe-example.yml @@ -0,0 +1,9 @@ +qq: + user: + 1234567890: + - some_channel_group/* + - some_channel_group2/some_channel + group: + 1234567890: + - some_channel_group/* + - some_channel_group2/some_channel diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..de02cb4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,101 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ + // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + "rootDir": "./src", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "resolveJsonModule": true, /* Enable importing .json files */ + // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ + // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ + // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}