commit ac42f0fbdd167fb190d426be531a2c86b215e9ac Author: 量子复合态 Date: Fri Apr 17 22:24:23 2020 +0800 初次提交 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aaf4567 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +config.yml +node_modules/ +package-lock.json \ No newline at end of file diff --git a/BroadcastChannel.js b/BroadcastChannel.js new file mode 100644 index 0000000..6b01f0e --- /dev/null +++ b/BroadcastChannel.js @@ -0,0 +1,31 @@ +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 new file mode 100644 index 0000000..23addec --- /dev/null +++ b/Channel.js @@ -0,0 +1,101 @@ +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 new file mode 100644 index 0000000..1969f9f --- /dev/null +++ b/ChannelManager.js @@ -0,0 +1,37 @@ +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 new file mode 100644 index 0000000..9405f57 --- /dev/null +++ b/QQRobot.js @@ -0,0 +1,67 @@ +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 new file mode 100644 index 0000000..0730946 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Pusher2CoolQ +将Pusher的推送消息转换为酷Q消息发送(通过CoolQ Http) +方便配合服务器可用性监测系统,提供比邮件更加实时的提示功能 +仅支持单向推送 \ No newline at end of file diff --git a/Utils.js b/Utils.js new file mode 100644 index 0000000..e9135f1 --- /dev/null +++ b/Utils.js @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000..f182c3a --- /dev/null +++ b/channels/debug.yml @@ -0,0 +1,8 @@ +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 new file mode 100644 index 0000000..72c5fe5 --- /dev/null +++ b/channels/feed.yml @@ -0,0 +1,12 @@ +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/serverStatus.yml b/channels/serverStatus.yml new file mode 100644 index 0000000..19dab50 --- /dev/null +++ b/channels/serverStatus.yml @@ -0,0 +1,10 @@ +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/config-example.yml b/config-example.yml new file mode 100644 index 0000000..e1d3c63 --- /dev/null +++ b/config-example.yml @@ -0,0 +1,9 @@ +robot: + host: "192.168.0.14:5700" +pusher: + app_id: "" + key: "" + secret: "" + cluster: "ap1" +channel_config_path: "./channels" +debug: false \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..174b882 --- /dev/null +++ b/index.js @@ -0,0 +1,37 @@ +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 diff --git a/package.json b/package.json new file mode 100644 index 0000000..1a34535 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "isekai-pusher2coolq", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "run": "node index.js" + }, + "author": "hyperzlib", + "license": "MIT", + "dependencies": { + "pusher": "^3.0.1", + "pusher-js": "^5.1.1", + "request": "^2.88.2", + "request-promise": "^4.2.5", + "yaml": "^1.8.3" + } +} diff --git a/push-test/index.js b/push-test/index.js new file mode 100644 index 0000000..2675bf3 --- /dev/null +++ b/push-test/index.js @@ -0,0 +1,10 @@ +var Pusher = require('pusher'); + +var pusher = new Pusher({ + appId: '', + key: '', + secret: '', + cluster: '' +}); + +pusher.trigger('debug', 'message', {"message": "Isekai Puser远端推送测试 (二周目)"}); \ No newline at end of file