├── .editorconfig ├── .gitignore ├── README.md ├── assets └── main.css ├── bot ├── config.js ├── index.js ├── package.json └── plugin │ ├── auto-reply.js │ ├── friends-pass.js │ ├── room-invite.js │ ├── room-join.js │ └── room-remove.js ├── components └── Say.vue ├── config.js ├── layouts ├── admin.vue ├── default.vue └── error.vue ├── nuxt.config.js ├── package.json ├── pages ├── admin │ ├── enume.js │ ├── friend │ │ └── index.vue │ ├── group │ │ └── index.vue │ ├── index.vue │ ├── reply │ │ ├── index.vue │ │ └── modal.vue │ └── task │ │ ├── index.vue │ │ └── modal.vue ├── auth │ └── login.vue └── index.vue ├── plugins ├── antd-ui.js └── axios.js ├── pm2.config.js ├── server ├── bot │ ├── index.js │ └── lib │ │ ├── FriendShip.js │ │ ├── Login.js │ │ ├── Message.js │ │ ├── Room.js │ │ └── Task.js ├── config │ ├── db.js │ └── log4js.js ├── controller │ ├── robot.js │ └── sys.js ├── index.js ├── middleware │ ├── botLogin.js │ ├── getUser.js │ └── resformat.js ├── models │ ├── auth.js │ ├── friend.js │ ├── group.js │ ├── memory.js │ ├── reply.js │ ├── robot.js │ └── task.js ├── routes │ └── api.js └── util │ ├── index.js │ └── logger.js ├── static └── favicon.ico └── store ├── index.js └── module └── robot.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | /logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (https://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Optional npm cache directory 42 | .npm 43 | 44 | # Optional eslint cache 45 | .eslintcache 46 | 47 | # Optional REPL history 48 | .node_repl_history 49 | 50 | # Output of 'npm pack' 51 | *.tgz 52 | 53 | # Yarn Integrity file 54 | .yarn-integrity 55 | 56 | # dotenv environment variables file 57 | .env 58 | 59 | # parcel-bundler cache (https://parceljs.org/) 60 | .cache 61 | 62 | # next.js build output 63 | .next 64 | 65 | # nuxt.js build output 66 | .nuxt 67 | 68 | # Nuxt generate 69 | dist 70 | 71 | # vuepress build output 72 | .vuepress/dist 73 | 74 | # Serverless directories 75 | .serverless 76 | 77 | # IDE / Editor 78 | .idea 79 | 80 | # Service worker 81 | sw.* 82 | 83 | # macOS 84 | .DS_Store 85 | 86 | # Vim swap files 87 | *.swp 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wechat-robot 2 | 基于 nodejs,nuxt, wechaty 开发的个人微信号机器人平台,现代化 UI 和用户体验 3 | 4 | [![Powered by Wechaty](https://img.shields.io/badge/Powered%20By-Wechaty-green.svg)](https://github.com/chatie/wechaty) 5 | [![Wechaty开源激励计划](https://img.shields.io/badge/Wechaty-开源激励计划-green.svg)](https://github.com/juzibot/Welcome/wiki/Everything-about-Wechaty) 6 | ## 界面预览 7 | * 首页 8 | ![首页](http://pic.loveyh.com/wxbot-1.png) 9 | * 后台管理 10 | ![控制台](http://pic.666up.cn/wxbot/1.png) 11 | ![自动回复](http://pic.666up.cn/wxbot/2.png) 12 | ![我的好友](http://pic.666up.cn/wxbot/3.png) 13 | ![我的群聊](http://pic.666up.cn/wxbot/4.png) 14 | ![定时任务](http://pic.666up.cn/wxbot/5.png) 15 | 16 | ## 在线实例 17 | [http://94.191.126.174:8081](http://94.191.126.174:8081) 18 | 用户名:guest 密码:111111 19 | #### 实现功能 20 | 21 | + 控制台 22 | - 绑定机器人 23 | - 登录 24 | - 自动通过好友验证关键词设置,当有人添加机器人时,关键词匹配后直接通过 25 | - 好友验证通过自动回复 26 | - 退出 27 | + 自动回复 28 | + 普通消息 29 | - 针对好友/某个群聊/所有群聊 设置关键词自动回复 30 | + 加群邀请 31 | - 机器人回复群聊列表,好友可以选择性进群 32 | + 踢人指令 33 | - 机器人识别指令,自动把成员移出群聊 34 | + 我的好友 35 | - 单独对某个好友送消息 36 | + 我的群聊 37 | - 群聊列表,管理所有群聊 38 | - 设置群聊名称,发布公告,发送群消息 39 | - 设置群聊基本信息,入群欢迎语,成员违规次数上限,是否受机器人控制 40 | + 定时任务 41 | - 针对好友/某个群聊/所有群聊设置定时任务,机器人在指定时间会触发消息推送 42 | + 智能聊天 43 | - 低智商对话 44 | - 成语接龙,查天气,查酒店,歇后语... 45 | 46 | ## 实际效果 47 | ![首页](http://pic.666up.cn/wxbot/chat.png) 48 | 49 | ## 技术构成 50 | * 服务端 [Node.js](https://nodejs.org/) 51 | * SSR框架 [NuxtJS](https://nuxtjs.org/) 52 | * 前端框架 [Vue](https://vuejs.org/) 53 | * UI组件 [Ant Design of Vue](https://www.antdv.com/docs/vue/introduce-cn/) 54 | * 持久化 [MongoDB](https://www.mongodb.org/) 55 | * ipad协议 [wechaty-puppet-padlocal](https://github.com/padlocal/wechaty-puppet-padlocal/) 56 | 57 | ## 快速开始 58 | 59 | ### 准备条件 60 | 61 | 安装 [Node.js](https://nodejs.org/en/download/) (v10 以上版本)、[MongoDB](https://www.mongodb.org/downloads/)。 62 | 推荐安装 [cnpm](https://cnpmjs.org/) 63 | 64 | ### 安装依赖 65 | ```Shell 66 | $ cnpm i 67 | ``` 68 | ## 本地单机插件版本 69 | 70 | `直接进入bot目录,也可将此目录单独移出至其他地方,修改配置文件config.js,再node index 启动即可` 71 | 72 | ## web版本 73 | 74 | ### 启动站点 75 | 76 | * 开发模式 77 | 78 | ```Shell 79 | $ npm run dev 80 | ``` 81 | 82 | 打开浏览器,访问 [http://localhost:3000/](http://localhost:3000) 83 | 用户名密码: admin / 111111 84 | 85 | * 生产模式 86 | 87 | 先编译项目 88 | ```shell 89 | $ npm run build 90 | ``` 91 | 92 | 再启动站点 93 | ```shell 94 | $ npm start 95 | ``` 96 | 97 | 98 | 99 | ## 系统设置 100 | 101 | 根据实际情况修改 `config.js` 配置文件,修改后需要重启服务器才能生效。 102 | 参数说明: 103 | 104 | #### host 105 | `String` 类型,主机名,配置为 `0.0.0.0` 表示监听任意主机。 106 | 107 | #### port 108 | `Number` 类型,端口号。 109 | 110 | #### mongoUrl 111 | `String` 类型,MongoDB 链接。 112 | 113 | #### secret 114 | `String` 类型,[JWT](https://github.com/auth0/node-jsonwebtoken) 秘钥。 115 | 116 | #### tianApiKey 117 | `String` 天行api秘钥 118 | 119 | ## 线上部署 120 | 121 | ### 使用PM2 122 | 推荐使用 [pm2](https://pm2.keymetrics.io/) 进行 Node.js 的进程管理和持久运行。 123 | 124 | #### 安装 125 | ```Shell 126 | $ cnpm i -g pm2 127 | ``` 128 | #### 启动 129 | ```Shell 130 | $ npm start 131 | ``` 132 | 133 | ## 最后 134 | 135 | ##### 有兴趣的朋友可以赏个star 136 | 137 | 还有很多待完善的功能,欢迎大家多给意见,一起学习。 138 | 139 | 好玩的东西总要先体验一把,扫码加我的小助手,验证消息写 `机器人` 即可直接通过啦,欢迎加群交流。 140 | 141 | ![WechatIMG127](http://pic.666up.cn/wxbot/qrcode.png) -------------------------------------------------------------------------------- /assets/main.css: -------------------------------------------------------------------------------- 1 | .page-enter-active, .page-leave-active { 2 | transition: opacity .3s; 3 | } 4 | .page-enter, .page-leave-active { 5 | opacity: 0; 6 | } 7 | .container{padding: 10px;} 8 | @media (max-width: 576px) { 9 | .container { 10 | padding-left: 5px; 11 | padding-right: 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /bot/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | PUPPET_PADLOCAL_TOKEN: "协议Token", 3 | BOT_NAME: "机器人名字", 4 | } -------------------------------------------------------------------------------- /bot/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 本地插件版 3 | * @Author: lwp 4 | * @Date: 2020-08-13 15:20:49 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-08-13 17:15:06 7 | */ 8 | 9 | const { Wechaty } = require('wechaty') 10 | const { PuppetPadlocal } = require('wechaty-puppet-padlocal') 11 | const { PUPPET_PADLOCAL_TOKEN, BOT_NAME } = require('./config') 12 | const FriendPass = require('./plugin/friends-pass') 13 | const RoomJoin = require('./plugin/room-join') 14 | const RoomInvite = require('./plugin/room-invite') 15 | const RoomRemove = require('./plugin/room-remove') 16 | const AutoReply = require('./plugin/auto-reply') 17 | 18 | const { 19 | QRCodeTerminal, 20 | EventLogger 21 | } = require('wechaty-plugin-contrib') //官方插件 22 | // 初始化 23 | const bot = new Wechaty({ 24 | puppet: new PuppetPadlocal({ 25 | token: PUPPET_PADLOCAL_TOKEN, 26 | }), 27 | name: BOT_NAME, 28 | }) 29 | //登录二维码 30 | bot.use(QRCodeTerminal({ small: false })) 31 | //日志输出 32 | bot.use(EventLogger()) 33 | //好友自动通过 34 | bot.use(FriendPass({ 35 | keyword: [ 36 | '加群', 37 | '机器人', 38 | ], 39 | reply: `你好,我是机器人${BOT_NAME} \n\n 加入技术交流群请回复【加群】`, 40 | blackId: [], 41 | }) 42 | ) 43 | // 加入房间欢迎 44 | bot.use(RoomJoin({ 45 | reply: [ 46 | { 47 | name: '机器人测试', 48 | roomId: 'xxx@chatroom', 49 | reply: `\n 你好,欢迎加入`, 50 | }, 51 | ], 52 | }) 53 | ) 54 | //加入房间邀请 55 | bot.use(RoomInvite({ 56 | keyword: ['加群', '入群'], 57 | reply: '', 58 | roomList: [ 59 | { 60 | name: '机器人测试', 61 | roomId: 'xxx@chatroom', 62 | code: 'A',//群编号 63 | label: '标签', 64 | }, 65 | ], 66 | }) 67 | ) 68 | // 指令踢人 69 | bot.use(RoomRemove({ 70 | keyword: ['👊', '踢', '👎'], 71 | adminList: [ 72 | { 73 | name: '小小', 74 | id: 'wxid_xxxxx', //管理员id 75 | }, 76 | ], 77 | time: 3000, 78 | replyDone: 'done', 79 | replyNoPermission: '没有踢人权限哦', 80 | }) 81 | ) 82 | //关键词自动回复 83 | bot.use(AutoReply({ 84 | keywords: [{ keyword: '测试', content: 'test' }] 85 | }) 86 | ) 87 | 88 | bot.on('error', (error) => { 89 | console.error(error) 90 | }).start() -------------------------------------------------------------------------------- /bot/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxbot-plugin", 3 | "version": "0.0.1", 4 | "description": "微信机器人插件版", 5 | "author": "lwp", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/beclass/wxbot" 9 | }, 10 | "scripts": { 11 | "start": "node index" 12 | }, 13 | "dependencies": { 14 | "wechaty": "^0.47.7", 15 | "wechaty-puppet-padlocal": "^0.7.34", 16 | "wechaty-plugin-contrib": "^0.14.12" 17 | } 18 | } -------------------------------------------------------------------------------- /bot/plugin/auto-reply.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc:关键词自动回复 3 | * @Author: lwp 4 | * @Date: 2020-08-13 16:41:56 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-08-13 17:11:07 7 | */ 8 | let DEFAULT_CONFIG = { 9 | unknownSay: "你在说什么,我听不懂呀", 10 | keywords: [{ keyword: '测试', content: 'test' }] 11 | } 12 | 13 | module.exports = function AutoReply(config = {}) { 14 | DEFAULT_CONFIG = Object.assign(DEFAULT_CONFIG, config) 15 | return (bot) => { 16 | // 消息监听 17 | bot.on("message", async (msg) => { 18 | if (msg.self()) return 19 | console.log("=============================") 20 | let str = `msg : ${msg}` 21 | str += ` from: ${msg.from() ? msg.from().name() : 'null'}: ${msg.from() ? msg.from().id : 'null' 22 | }` 23 | console.log(str) 24 | // console.log(`to: ${msg.to()}`) 25 | // console.log(`text: ${msg.text()}`) 26 | // console.log(`isRoom: ${msg.room()}`) 27 | // console.log(`isRoomId: ${msg.room()?msg.room().id:null}`) 28 | // console.log("=============================") 29 | // 校验消息类型为文本 30 | if (msg.type() === bot.Message.Type.Text) { 31 | if (msg.room()) { //来自群聊 32 | let room = await msg.room() 33 | if (await msg.mentionSelf()) { //@自己 34 | let self = await msg.from() 35 | self = '@' + self.name() 36 | let sendText = msg.text().replace(self, '') 37 | sendText = sendText.trim() 38 | // 获取需要回复的内容 39 | const content = _keyWordReply(sendText) 40 | room.say(content) 41 | return 42 | } 43 | //@成员或者没@人 44 | let sendText = msg.text() 45 | let person = false 46 | if (sendText.indexOf('@') == 0) { 47 | const str = sendText.replace('@', '').split(' ') 48 | if (!str[1]) return 49 | person = str[0].trim() 50 | sendText = str[1].trim() 51 | } 52 | let content = _keyWordReply(sendText) 53 | if (content) { 54 | if (person) { 55 | content = `@${person} ${content}` 56 | } else { 57 | content = `「${msg.from().name()}:${msg.text()}」\n- - - - - - - - - - - - - - -\n${content}` 58 | } 59 | room.say(content) 60 | } 61 | } 62 | //私聊 63 | const content = _keyWordReply(msg.text()) 64 | msg.say(content) 65 | return 66 | } 67 | console.log('不是文本消息,不处理') 68 | }) 69 | } 70 | } 71 | 72 | function _keyWordReply(keyword) { 73 | const res = DEFAULT_CONFIG.keywords.find((v) => v.keyword == keyword) 74 | if (res) return res.content 75 | return DEFAULT_CONFIG.unknownSay 76 | } 77 | -------------------------------------------------------------------------------- /bot/plugin/friends-pass.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 好友申请自动通过 3 | * @Author: lwp 4 | * @Date: 2020-08-13 15:56:57 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-08-13 16:09:52 7 | */ 8 | const DEFAULT_CONFIG = { 9 | blackId: [],//黑名单 10 | keyword: [],//关键字 11 | reply: "",//通过后回复 12 | } 13 | const PASSALL = false 14 | 15 | module.exports = function FriendPass(config = {}) { 16 | config = Object.assign({}, DEFAULT_CONFIG, config) 17 | if (config.keyword === "*") PASSALL = true 18 | if (typeof config.keyword === "string") config.keyword = [config.keyword] 19 | if (typeof config.blackId === "string") config.blackId = [config.blackId] 20 | return (bot) => { 21 | // 好友添加监听 22 | bot.on("friendship", async (friendship) => { 23 | // 校验是否存在黑名单中 24 | if (config.blackId.some((v) => v == friendship.payload.contactId)) return 25 | let logMsg 26 | switch (friendship.type()) { 27 | // 新的好友请求 28 | case bot.Friendship.Type.Receive: 29 | if (config.keyword.some((v) => v == friendship.hello()) || PASSALL) { 30 | logMsg = `自动通过验证,因为验证消息是"${friendship.hello()}"` 31 | // 通过验证 32 | await friendship.accept() 33 | } else { 34 | logMsg = "不自动通过,因为验证消息是: " + friendship.hello() 35 | } 36 | break 37 | // 友谊确认 38 | case bot.Friendship.Type.Confirm: 39 | logMsg = "已通过好友申请:" + friendship.contact().name() 40 | if (config.reply) { 41 | friendship.contact().say(config.reply) 42 | } 43 | break 44 | } 45 | console.log(logMsg) 46 | }) 47 | } 48 | } -------------------------------------------------------------------------------- /bot/plugin/room-invite.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 加群邀请 3 | * @Author: lwp 4 | * @Date: 2020-08-13 16:18:43 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-08-13 16:20:16 7 | */ 8 | const DEFAULT_CONFIG = { 9 | keyword: ["加群"], 10 | reply: "", 11 | roomList: [], 12 | } 13 | 14 | module.exports = function RoomInvite(config = {}) { 15 | config = Object.assign({}, DEFAULT_CONFIG, config) 16 | if (typeof config.keyword === "string") config.keyword = [config.keyword] 17 | return (bot) => { 18 | // 消息监听 19 | bot.on("message", async (msg) => { 20 | if (msg.self()) return 21 | for (let i = 0; i < config.roomList.length; i++) { 22 | if ( 23 | config.roomList[i].name == msg.text() || 24 | config.roomList[i].code == msg.text() 25 | ) { 26 | if (config.roomList[i].close == true) { 27 | msg.say("此群已关闭自动拉取功能") 28 | return 29 | } 30 | await roomInvite(bot, msg, config.roomList[i].roomId) 31 | return 32 | } 33 | } 34 | if (msg.type() === bot.Message.Type.Text && !msg.room()) { 35 | // 校验关键字 36 | if (config.keyword.some((c) => c == msg.text())) { 37 | if (config.roomList.length == 1) { 38 | await roomInvite(bot, msg, config.roomList[0].roomId) 39 | return 40 | } 41 | let info 42 | if (config.reply) { 43 | info = config.reply 44 | } else { 45 | info = `${bot.options.name}管理的群聊有${config.roomList.length}个,回复群聊名或编号(【】中为编号)即可加入哦\n\n` 46 | config.roomList.map((item) => { 47 | info += `【${item.alias}】${item.name} (${item.label})\n` 48 | }) 49 | } 50 | msg.say(info) 51 | } 52 | } 53 | }) 54 | } 55 | } 56 | 57 | /** 58 | * @description 房间邀请 59 | * @param {Object} bot 实例对象 60 | * @param {Object} msg 消息实例 61 | * @param {Object} roomId 房间id 62 | * @return {} 63 | */ 64 | async function roomInvite(bot, msg, roomId) { 65 | // 通过群聊id获取到该群聊实例 66 | const room = await bot.Room.find({ id: roomId }) 67 | // 判断是否在房间中 在-提示并结束 68 | if (await room.has(msg.from())) { 69 | await msg.say("您已经在房间中了") 70 | return 71 | } 72 | // 发送群邀请 73 | await room.add(msg.from()) 74 | await msg.say("已发送群邀请") 75 | return 76 | } -------------------------------------------------------------------------------- /bot/plugin/room-join.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 入群欢迎 3 | * @Author: lwp 4 | * @Date: 2020-08-13 16:17:31 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-08-13 16:39:35 7 | */ 8 | 9 | const DEFAULT_CONFIG = { 10 | reply: "你好,欢迎加入!", 11 | } 12 | module.exports = function RoomJoin(config = {}) { 13 | config = Object.assign({}, DEFAULT_CONFIG, config) 14 | return (bot) => { 15 | bot.on("room-join", async (room, inviteeList, inviter) => { 16 | if (!config.reply) return 17 | if (typeof config.reply === "string") 18 | inviteeList.map((c) => room.say(config.reply, c)) 19 | if (Array.isArray(config.reply)) { 20 | config.reply.map((item) => { 21 | if (item.roomId == room.id) { 22 | inviteeList.map((c) => { 23 | room.say(item.reply, c) 24 | }) 25 | } 26 | }) 27 | } 28 | }) 29 | } 30 | } -------------------------------------------------------------------------------- /bot/plugin/room-remove.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 移出群聊 3 | * @Author: lwp 4 | * @Date: 2020-08-13 16:22:37 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-08-13 16:35:24 7 | */ 8 | 9 | const DEFAULT_CONFIG = { 10 | keyword: ["👊", "踢"], 11 | adminList: [], 12 | time: 3000, 13 | replyInfo: function (msg) { 14 | return `您一定是违反了群的相关规则,${this.time / 1000}s后您将被移出本群,操作管理员:${msg.from().name()}` 15 | }, 16 | replyDone: "done", 17 | replyNoPermission: "", 18 | } 19 | 20 | module.exports = function RoomRemove(config = {}) { 21 | config = Object.assign({}, DEFAULT_CONFIG, config) 22 | if (typeof config.keyword === "string") config.keyword = [config.keyword] 23 | if (typeof config.replyInfo === "string") { 24 | let info = config.replyInfo 25 | config.replyInfo = () => info 26 | } 27 | return (bot) => { 28 | // 消息监听 29 | bot.on("message", async (msg) => { 30 | if (msg.self()) return 31 | // 校验消息类型为文本 且 来自群聊 32 | if (msg.type() === bot.Message.Type.Text && msg.room()) { 33 | // 获取群聊实例 34 | const room = await msg.room() 35 | // 是否为@的用户列表 36 | if (msg.mentionList()) { 37 | // 获取在群中@的用户列表 38 | let contactList = await msg.mentionList() 39 | let sendText = msg.text(), 40 | aite = "" 41 | for (let i = 0; i < contactList.length; i++) { 42 | // 获取@ + 群聊别称 || 名字 43 | let name = 44 | (await room.code(contactList[i])) || contactList[i].name() 45 | aite = "@" + name 46 | // 匹配删除名字信息 47 | sendText = sendText.replace(aite, "") 48 | } 49 | // 删除首尾空格 50 | sendText = sendText.replace(/(^\s*)|(\s*$)/g, "") 51 | if (config.keyword.some((v) => v === sendText)) { 52 | if (config.adminList.some((v) => v.id == msg.from().id)) { 53 | room.say(config.replyInfo(msg), ...contactList) 54 | setTimeout(async () => { 55 | contactList.map(async (item) => { 56 | try { 57 | await room.del(item) 58 | } catch (e) { 59 | console.error(e) 60 | } 61 | room.say(config.replyDone) 62 | }) 63 | }, config.time) 64 | } else { 65 | if (config.replyNoPermission) { 66 | room.say(config.replyNoPermission, msg.from()) 67 | } 68 | } 69 | } 70 | } 71 | } 72 | }) 73 | } 74 | } -------------------------------------------------------------------------------- /components/Say.vue: -------------------------------------------------------------------------------- 1 | 19 | 46 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const local = { 2 | port: 3000, 3 | host: '0.0.0.0', 4 | mongoUrl: 'mongodb://localhost:27017/wxrobot', 5 | secret: '123456', 6 | tianApiKey:'' 7 | } 8 | const development = { 9 | } 10 | const production = { 11 | port: 8081, 12 | mongoUrl:'mongodb://username:password@ip:port/wxrobot', 13 | } 14 | let config = Object.assign(local, development) 15 | if (process.env.NODE_ENV == 'production') { 16 | config = Object.assign(local, production) 17 | } 18 | module.exports = config -------------------------------------------------------------------------------- /layouts/admin.vue: -------------------------------------------------------------------------------- 1 | 74 | 75 | 138 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /layouts/error.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 17 | 18 | -------------------------------------------------------------------------------- /nuxt.config.js: -------------------------------------------------------------------------------- 1 | 2 | const { host, port } = require('./config') 3 | const server = { host, port } 4 | module.exports = { 5 | mode: 'spa', 6 | /* 7 | ** Headers of the page 8 | */ 9 | head: { 10 | title: process.env.npm_package_name || '', 11 | meta: [ 12 | { charset: 'utf-8' }, 13 | { name: 'viewport', content: 'width=device-width, initial-scale=1' }, 14 | { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } 15 | ], 16 | link: [ 17 | { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } 18 | ] 19 | }, 20 | server, 21 | /* 22 | ** Customize the progress-bar color 23 | */ 24 | loading: { color: '#f80' }, 25 | /* 26 | ** Global CSS 27 | */ 28 | css: [ 29 | 'assets/main.css', 30 | 'ant-design-vue/dist/antd.css' 31 | ], 32 | /* 33 | ** Plugins to load before mounting the App 34 | */ 35 | plugins: [ 36 | '@/plugins/antd-ui', 37 | '@/plugins/axios', 38 | ], 39 | /* 40 | ** Nuxt.js dev-modules 41 | */ 42 | buildModules: [ 43 | ], 44 | /* 45 | ** Nuxt.js modules 46 | */ 47 | modules: [ 48 | '@nuxtjs/axios', 49 | '@nuxtjs/auth' 50 | ], 51 | router: { 52 | //middleware: 'stats' 53 | }, 54 | auth: { 55 | strategies: { 56 | local: { 57 | endpoints: { 58 | login: { 59 | url: '/auth/login', 60 | method: 'post', 61 | propertyName: 'token' 62 | }, 63 | logout: { url: '/auth/logout', method: 'post' }, 64 | user: { url: '/auth/user', method: 'get', propertyName: 'user' } 65 | } 66 | } 67 | }, 68 | redirect: { 69 | login: '/auth/login', 70 | logout: '/', 71 | callback: '/auth/login', 72 | home: '/' 73 | } 74 | }, 75 | /* 76 | ** Axios module configuration 77 | ** See https://axios.nuxtjs.org/options 78 | */ 79 | axios: { 80 | proxy: true, // 表示开启代理 81 | prefix: '/api', // 表示给请求url加个前缀 /api 82 | credentials: true // 表示跨域请求时是否需要使用凭证 83 | }, 84 | /* 85 | ** Build configuration 86 | */ 87 | build: { 88 | /* 89 | ** You can extend webpack config here 90 | */ 91 | extend(config, ctx) { 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wxbot", 3 | "version": "2.0.2", 4 | "description": "微信机器人", 5 | "author": "lwp", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/beclass/wxbot" 9 | }, 10 | "scripts": { 11 | "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server", 12 | "build": "nuxt build", 13 | "start": "pm2 start pm2.config.js --env production", 14 | "generate": "nuxt generate" 15 | }, 16 | "dependencies": { 17 | "@nuxtjs/auth": "^4.9.1", 18 | "@nuxtjs/axios": "^5.9.7", 19 | "ant-design-vue": "^1.1.10", 20 | "core-js": "^2.6.11", 21 | "cross-env": "^5.2.0", 22 | "jsonwebtoken": "^8.5.1", 23 | "koa": "^2.6.2", 24 | "koa-bodyparser": "^4.3.0", 25 | "koa-jwt": "^3.6.0", 26 | "koa-router": "^8.0.8", 27 | "log4js": "^6.2.1", 28 | "moment": "^2.24.0", 29 | "mongoose": "^5.9.10", 30 | "node-schedule": "^1.3.2", 31 | "node-uuid": "^1.4.8", 32 | "nuxt": "^2.0.0", 33 | "qrcodejs2": "^0.0.2", 34 | "urllib": "^2.34.2", 35 | "wechaty": "^0.56.6", 36 | "wechaty-puppet-padlocal": "^0.4.0" 37 | }, 38 | "devDependencies": { 39 | "node-sass": "^4.9.4", 40 | "nodemon": "^1.18.9", 41 | "sass-loader": "^7.1.0" 42 | } 43 | } -------------------------------------------------------------------------------- /pages/admin/enume.js: -------------------------------------------------------------------------------- 1 | const replyTypes = ['普通消息', '发送群邀请', '踢人指令'] 2 | const factorsList = ['通用', '私聊', '群聊','通用群聊'] 3 | const statusList = ['停用','启用'] 4 | const units = ['每分钟','每小时','每天'] 5 | const taskFactors=['个人','群聊','通用群聊'] 6 | const taskTypes=['普通消息'] 7 | module.exports = { replyTypes, factorsList,statusList,units,taskFactors,taskTypes} 8 | -------------------------------------------------------------------------------- /pages/admin/friend/index.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 132 | -------------------------------------------------------------------------------- /pages/admin/group/index.vue: -------------------------------------------------------------------------------- 1 | 64 | 148 | 149 | -------------------------------------------------------------------------------- /pages/admin/index.vue: -------------------------------------------------------------------------------- 1 | 68 | 190 | -------------------------------------------------------------------------------- /pages/admin/reply/index.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 168 | -------------------------------------------------------------------------------- /pages/admin/reply/modal.vue: -------------------------------------------------------------------------------- 1 | 49 | 102 | -------------------------------------------------------------------------------- /pages/admin/task/index.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 174 | -------------------------------------------------------------------------------- /pages/admin/task/modal.vue: -------------------------------------------------------------------------------- 1 | 69 | 143 | -------------------------------------------------------------------------------- /pages/auth/login.vue: -------------------------------------------------------------------------------- 1 | 25 | 44 | 63 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 96 | -------------------------------------------------------------------------------- /plugins/antd-ui.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Antd from 'ant-design-vue/lib' 3 | 4 | Vue.use(Antd) 5 | -------------------------------------------------------------------------------- /plugins/axios.js: -------------------------------------------------------------------------------- 1 | 2 | import { message } from 'ant-design-vue' 3 | export default function ({ store, redirect, app: { $axios } }) { 4 | // request拦截器 5 | $axios.onRequest(config => { 6 | }) 7 | $axios.onError(error => { 8 | console.log('axios请求错误') 9 | console.log(error) 10 | }) 11 | //response拦截器,数据返回后,你可以先在这里进行一个简单的判断 12 | $axios.interceptors.response.use(response => { 13 | const { success, errcode } = response.data 14 | if (typeof (errcode) !== 'undefined' && !success) { 15 | if (response.config.url != '/auth/user') message.error(response.data.errmsg) 16 | if (errcode === 401) redirect('/auth/login') 17 | return false 18 | } 19 | return response.data 20 | }, error => { 21 | message.error('网络连接失败,请检查网络状态和系统代理设置') 22 | }) 23 | 24 | } -------------------------------------------------------------------------------- /pm2.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [{ 3 | name: "wxbot", 4 | script: "./server/index.js", 5 | env: { 6 | NODE_ENV: "development", 7 | }, 8 | env_production: { 9 | NODE_ENV: "production", 10 | } 11 | }] 12 | } -------------------------------------------------------------------------------- /server/bot/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: robot 3 | * @Author: lwp 4 | * @Date: 2020-04-29 19:03:52 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-14 15:34:40 7 | */ 8 | const logger = require('../util/logger') 9 | const { Wechaty } = require('wechaty') 10 | // const { PuppetPadplus } = require('wechaty-puppet-padplus') 11 | const { PuppetPadlocal } = require('wechaty-puppet-padlocal') 12 | const { Robot } = require('../models/robot') 13 | const {onLogin,onLogout} = require('./lib/Login') 14 | const onFriendShip = require('./lib/FriendShip') 15 | const onMessage = require('./lib/Message') 16 | const {onRoomJoin,onRoomLeave} = require('./lib/Room') 17 | class Bot { 18 | constructor(_id, debug = false) { 19 | this._id = _id 20 | } 21 | log(...args) { 22 | if (this.debug) console.log(...args) 23 | } 24 | //启动 25 | async start() { 26 | const robot = await Robot.findOne({_id:this._id}, { token: 1, nickName: 1, id: 1 }) 27 | if(!robot) throw {message:'机器人不存在'} 28 | if(!robot.token) throw {message:'缺少协议token'} 29 | let bot = new Wechaty({ 30 | puppet: new PuppetPadlocal({ 31 | token: robot.token 32 | }), 33 | name: robot.nickName, 34 | }) 35 | const res = await new Promise((resolve, reject) => { 36 | bot.on('scan', (qrcode) => { 37 | resolve({qrcode}) 38 | }).on('login', async (user)=>{ 39 | const res = await onLogin(bot,this._id,user) 40 | resolve(res) 41 | }) 42 | .on('message', onMessage) 43 | .on('friendship', onFriendShip) 44 | .on('room-join', onRoomJoin) 45 | .on('room-leave', onRoomLeave) 46 | .on('error', error => { 47 | logger.error('机器故障,error:' + error) 48 | }) 49 | .on('logout', onLogout) 50 | .start() 51 | }); 52 | return res 53 | } 54 | } 55 | module.exports = Bot -------------------------------------------------------------------------------- /server/bot/lib/FriendShip.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 友谊关系 3 | * @Author: lwp 4 | * @Date: 2020-04-29 17:31:10 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-14 15:34:06 7 | */ 8 | const logger = require('../../util/logger') 9 | const { Friendship } = require('wechaty') 10 | const { Robot } = require('../../models/robot') 11 | const onFriendShip = async (friendship) => { 12 | let log; 13 | try { 14 | log = '添加好友' + friendship.contact().name(); 15 | logger.info(log) 16 | const robot = await Robot.findOne({ id: bot.id }, {addFriendKeywords: 1, addFriendReply: 1 }) 17 | switch (friendship.type()) { 18 | /** 19 | * 1.新的好友请求 20 | * 通过'request.hello()'获取验证消息 21 | * 通过'request.accept()'接受请求 22 | */ 23 | case Friendship.Type.Receive: 24 | if (robot.addFriendKeywords.some(str => str === friendship.hello())) { 25 | log = `自动添加好友成功,因为验证消息是"${friendship.hello()}"`; 26 | //通过验证 27 | await friendship.accept(); 28 | } else { 29 | log = `没有通过验证:因为关键词"${friendship.hello()}"不匹配`; 30 | } 31 | break; 32 | /** 33 | * 确认添加 34 | */ 35 | case Friendship.Type.Confirm: 36 | log = `${friendship.contact().name()}已经添加你为好友`; 37 | //发个提示 38 | bot.say(`${friendship.contact().name()}添加了你为好友`); 39 | if (robot.addFriendReply) await friendship.contact().say(robot.addFriendReply); 40 | break; 41 | } 42 | } catch (e) { 43 | log = e.message; 44 | } 45 | logger.info(log) 46 | } 47 | module.exports = onFriendShip -------------------------------------------------------------------------------- /server/bot/lib/Login.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 登录 3 | * @Author: lwp 4 | * @Date: 2020-04-29 18:51:49 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-15 18:15:23 7 | */ 8 | const logger = require('../../util/logger') 9 | const { Robot } = require('../../models/robot') 10 | const { Group } = require('../../models/group') 11 | const { Friend } = require('../../models/friend') 12 | /** 13 | * 登录 14 | * @param {object} bot 15 | * @param {string} _id 16 | * @param {object} user 17 | */ 18 | const onLogin = async (bot, robot_id, user) => { 19 | const robot = await Robot.findOne({ _id:robot_id }, { startSay: 1, nickName: 1 }) 20 | logger.info(`机器人${robot.nickName} 登陆啦!!!`) 21 | console.log(`机器人${robot.nickName} 登陆啦!!!`) 22 | await Robot.updateOne({_id:robot_id},{ 23 | status: 1, lastLoginT: new Date(), name: user.payload.name, 24 | id: user.id, weixin: user.payload.weixin, avatar: user.payload.avatar 25 | }) 26 | bot.id = user.id 27 | //初始化群聊 28 | let roomList = await bot.Room.findAll() 29 | for (let i = 0; i < roomList.length; i++) { 30 | const group = await Group.findOne({ id: roomList[i].id },{_id:1}) 31 | if (!group) { 32 | roomList[i].payload.robotId = user.id 33 | await Group.create(roomList[i].payload) 34 | }else{ 35 | await Group.updateOne({_id:group._id},roomList[i].payload) 36 | } 37 | } 38 | //初始化好友 39 | const friends = await bot.Contact.findAll() 40 | let friendsA =[] 41 | const notids=['filehelper','fmessage',user.id] 42 | friends.forEach(item => { 43 | if(item.payload.friend&¬ids.indexOf(item.payload.id)<0){ 44 | item.payload.robotId = user.id 45 | friendsA.push(item.payload) 46 | } 47 | }) 48 | for(let j=0;j { 137 | content += `${item.joinCode}:【${item.topic}】\n` 138 | }) 139 | content += '\n回复字母即可加入对应的群哦,比如发送 ' + roomList[0].joinCode 140 | return content 141 | } 142 | if (res.factor == 0 || res.factor == 1) return res.content 143 | return false 144 | } catch (err) { return false } 145 | } 146 | 147 | /** 148 | * @description 机器人回复内容 149 | * @param {String} 收到消息 150 | * @return {String} 响应内容 151 | */ 152 | async function getReply(keyword) { 153 | let url = TXHOST + 'robot/'; 154 | const pkg = { 155 | method: 'get', 156 | headers: { 157 | 'Content-Type': 'application/json' 158 | }, 159 | data: { 160 | key: tianApiKey, 161 | question: keyword, 162 | mode: 1, 163 | datatype: 0, 164 | userid: uniqueId, 165 | limit: 1 166 | }, 167 | encoding: null, 168 | timeout: 5000, 169 | } 170 | let { status, data } = await urllib.request(url, pkg) 171 | if (status !== 200) return '不好意思,我断网了' 172 | data = JSON.parse(data.toString()) 173 | if (data.code != 200) return '我累啦,等我休息好再来哈' 174 | return data.newslist[0].reply 175 | } 176 | module.exports = onMessage -------------------------------------------------------------------------------- /server/bot/lib/Room.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: room 3 | * @Author: lwp 4 | * @Date: 2020-04-30 19:33:58 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-14 12:34:25 7 | */ 8 | 9 | /** 10 | * 进入房间 11 | * @param {String} room 群聊 12 | * @param {*} inviteeList 受邀者名单 13 | * @param {*} inviter 邀请者 14 | */ 15 | const { Group } = require('../../models/group') 16 | async function onRoomJoin(room, inviteeList, inviter) { 17 | const group = await Group.findOne({ id: room.id }, { roomJoinReply: 1 }) 18 | if (!group) { 19 | room.payload.robotId = bot.id 20 | await Group.create(room.payload) 21 | room.say(`大家好,我是机器人${bot.options.name}\n欢迎大家找我聊天或者玩游戏哦。比如 @${bot.options.name} 成语接龙`) 22 | return 23 | } 24 | inviteeList.map(c => { 25 | room.say('\n' + group.roomJoinReply, c) 26 | }) 27 | } 28 | /** 29 | * 踢出房间,此功能仅限于bot踢出房间,如果房间用户自己退出不会触发 30 | * @param {*} room 31 | * @param {*} leaverList 32 | */ 33 | async function onRoomLeave(room, leaverList) { 34 | const isrobot = leaverList.find((item) => item.id == bot.id) 35 | if (isrobot) { 36 | await Group.deleteOne({ id: room.id }) 37 | return 38 | } 39 | const group = Group.findOne({ id: room.id }, { id: 1 }) 40 | if (group) { 41 | leaverList.map(c => { 42 | room.say(`「${c.name()}」离开了群聊`) 43 | }) 44 | } 45 | } 46 | module.exports = { onRoomJoin, onRoomLeave } -------------------------------------------------------------------------------- /server/bot/lib/Task.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 定时任务 3 | * @Author: lwp 4 | * @Date: 2020-05-09 17:03:09 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-15 16:56:40 7 | */ 8 | const logger = require('../../util/logger') 9 | const schedule = require('node-schedule') 10 | const { Task } = require('../../models/task') 11 | const { Group } = require('../../models/group') 12 | let tasks = {} 13 | /** 14 | * 初始化任务列表 15 | */ 16 | const init = async () => { 17 | logger.info('初始化任务...') 18 | const list = await Task.find({ status: 1, robotId: bot.id }) 19 | for (let i = 0; i < list.length; i++) { 20 | await start(list[i]) 21 | } 22 | logger.info(`已初始化${list.length}个任务`) 23 | } 24 | /** 25 | * 重启任务 26 | * @param {object} data 27 | */ 28 | const restart = async (data) => { 29 | try { 30 | if (!global.bot) throw { message: '机器人已掉线,请重新登录' } 31 | const task = tasks[data._id] 32 | if (task) { 33 | stop(data._id) 34 | if (data.status == 0) logger.info(`${data.name} 定时任务已关闭------`); return 35 | } 36 | await start(data) 37 | } catch (err) { throw err } 38 | } 39 | /** 40 | * 开启任务 41 | * @param {object} data 42 | */ 43 | const start = async (data) => { 44 | try { 45 | let rule = { 46 | second: data.second 47 | } 48 | if (data.minute || data.minute == 0) rule.minute = data.minute 49 | if (data.hour || data.hour == 0) rule.hour = data.hour 50 | if (data.dayOfWeek) rule.dayOfWeek = data.dayOfWeek 51 | if (data.dayOfMonth) rule.dayOfMonth = data.dayOfMonth 52 | const sc = schedule.scheduleJob(rule, async () => { 53 | logger.info(`${data.name} 定时任务已启动------`) 54 | if (data.factor == 0) { 55 | const contact = await bot.Contact.find({ id: data.friendId }) 56 | await contact.say(data.content) 57 | } 58 | if (data.factor == 1) { 59 | const room = await bot.Room.find({ id: data.roomId }) 60 | await room.say(data.content) 61 | } 62 | if (data.factor == 2) { 63 | const groups = await Group.find({ control: true }, { id: 1 }) 64 | for (let i = 0, len = groups.length; i < len; i++) { 65 | const room = await bot.Room.find({ id: groups[i].id }) 66 | await room.say(data.content) 67 | } 68 | } 69 | }) 70 | tasks[data._id] = sc 71 | } catch (err) { 72 | throw err 73 | } 74 | } 75 | /** 76 | * 停止任务 77 | * @param {string} id 78 | */ 79 | const stop = async (id) => { 80 | try { 81 | tasks[id].cancel() 82 | delete tasks[id] 83 | } catch (err) { 84 | console.log(err) 85 | } 86 | } 87 | module.exports = { 88 | init, start, restart, stop 89 | } -------------------------------------------------------------------------------- /server/config/db.js: -------------------------------------------------------------------------------- 1 | const { mongoUrl } = require('../../config') 2 | const mongoose = require('mongoose') 3 | module.exports = { 4 | connect: () => { 5 | mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true }) 6 | mongoose.set('useFindAndModify', false) 7 | mongoose.set('useCreateIndex', true) 8 | let db = mongoose.connection 9 | mongoose.Promise = global.Promise 10 | db.on('error', function (err) { 11 | console.log('数据库连接出错', err) 12 | }) 13 | db.on('open', function () { 14 | console.log('数据库连接成功') 15 | }) 16 | db.on('disconnected', function () { 17 | console.log('数据库连接断开') 18 | }) 19 | }, 20 | mongoose 21 | } -------------------------------------------------------------------------------- /server/config/log4js.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | //日志根目录 3 | const baseLogPath = path.resolve(__dirname, '../../logs') 4 | //错误日志目录 5 | const errorPath = "/error"; 6 | //错误日志文件名 7 | const errorFileName = "error"; 8 | //错误日志输出完整路径 9 | const errorLogPath = baseLogPath + errorPath + "/" + errorFileName; 10 | //var errorLogPath = path.resolve(__dirname, "../logs/error/error"); 11 | //响应日志目录 12 | const responsePath = "/response"; 13 | //响应日志文件名 14 | const responseFileName = "response"; 15 | //响应日志输出完整路径 16 | const responseLogPath = baseLogPath + responsePath + "/" + responseFileName; 17 | //var responseLogPath = path.resolve(__dirname, "../logs/response/response"); 18 | //响应日志目录 19 | const infoPath = "/info"; 20 | //响应日志文件名 21 | const infoFileName = "info"; 22 | //响应日志输出完整路径 23 | const infoLogPath = baseLogPath + infoPath + "/" + infoFileName; 24 | module.exports = { 25 | //日志格式等设置 26 | appenders: 27 | { 28 | "rule-console": { "type": "console" }, 29 | "errorLogger": { 30 | "type": "dateFile", 31 | "filename": errorLogPath, 32 | "pattern": "-yyyy-MM-dd.log", 33 | "alwaysIncludePattern": true, 34 | "encoding": "utf-8", 35 | "maxLogSize": 10000, 36 | "numBackups": 3, 37 | "path": errorPath 38 | }, 39 | "resLogger": { 40 | "type": "dateFile", 41 | "filename": responseLogPath, 42 | "pattern": "-yyyy-MM-dd.log", 43 | "alwaysIncludePattern": true, 44 | "encoding": "utf-8", 45 | "maxLogSize": 10000, 46 | "numBackups": 3, 47 | "path": responsePath 48 | }, 49 | "infoLogger": { 50 | "type": "dateFile", 51 | "filename": infoLogPath, 52 | "pattern": "-yyyy-MM-dd.log", 53 | "alwaysIncludePattern": true, 54 | "encoding": "utf-8", 55 | "maxLogSize": 10000, 56 | "numBackups": 3, 57 | "path": infoPath 58 | }, 59 | }, 60 | //供外部调用的名称和对应设置定义 61 | categories: { 62 | "default": { "appenders": ["rule-console"], "level": "all" }, 63 | "resLogger": { "appenders": ["resLogger"], "level": "info" }, 64 | "errorLogger": { "appenders": ["errorLogger"], "level": "error" }, 65 | "infoLogger": { "appenders": ["infoLogger"], "level": "info" }, 66 | "http": { "appenders": ["resLogger"], "level": "info" } 67 | }, 68 | pm2: true, 69 | //replaceConsole: true, 70 | "baseLogPath": baseLogPath 71 | } -------------------------------------------------------------------------------- /server/controller/robot.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: robot 3 | * @Author: lwp 4 | * @Date: 2020-04-24 21:09:47 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-12 15:49:06 7 | */ 8 | const Bot = require('../bot') 9 | const { Robot } = require('../models/robot') 10 | const {Group} = require('../models/group') 11 | module.exports = { 12 | login: async (ctx) => { 13 | try { 14 | if (!ctx.request.body.id) throw { message: '缺少id' } 15 | const bot = new Bot(ctx.request.body.id) 16 | const result = await bot.start() 17 | ctx.body = result 18 | } catch (err) { throw err } 19 | }, 20 | loginOut: async (ctx) => { 21 | try { 22 | if (!global.bot) { 23 | await Robot.updateOne({ _id: ctx.request.body.id }, { status: 0 }) 24 | delete global.bot 25 | return ctx.body = {} 26 | } 27 | await bot.logout() 28 | ctx.body = {} 29 | } catch (err) { throw err } 30 | }, 31 | friendSay: async (ctx) => { 32 | try { 33 | const contact = await bot.Contact.find({ id: ctx.request.body.id }) 34 | await contact.say(ctx.request.body.content) 35 | ctx.body = {} 36 | } catch (err) { throw err } 37 | }, 38 | roomSay: async (ctx) => { 39 | try { 40 | const room = await bot.Room.find({ id: ctx.request.body.id }) 41 | await room.say(ctx.request.body.content) 42 | ctx.body = {} 43 | } catch (err) { throw err } 44 | }, 45 | getRoom: async (ctx) => { 46 | try { 47 | const room = await bot.Room.find({ id: ctx.params.id }) 48 | const topic = await room.topic() 49 | const announce = await room.announce() 50 | ctx.body = {topic,announce} 51 | } catch (err) { throw err } 52 | }, 53 | updateRoom: async (ctx) => { 54 | try { 55 | const room = await bot.Room.find({ id: ctx.params.id }) 56 | if(ctx.request.body.topic) { 57 | await room.topic(ctx.request.body.topic) 58 | await Group.updateOne({id:ctx.params.id},{topic:ctx.request.body.topic}) 59 | } 60 | if(ctx.request.body.announce) { 61 | await room.announce(ctx.request.body.announce) 62 | } 63 | ctx.body = {} 64 | } catch (err) {throw {message:'没有权限,不是群主或者管理员'} } 65 | }, 66 | roomQuit: async (ctx) => { 67 | try { 68 | const room = await bot.Room.find({ id: ctx.request.body.id }) 69 | await room.quit() 70 | ctx.body = {} 71 | } catch (err) { throw err } 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /server/controller/sys.js: -------------------------------------------------------------------------------- 1 | const authDB = require('../models/auth') 2 | const groupDB = require('../models/group') 3 | const friendDB = require('../models/friend') 4 | const replyDB = require('../models/reply') 5 | const taskDB = require('../models/task') 6 | module.exports = { 7 | login: async (ctx) => { 8 | try { 9 | const result = await authDB.Dao.login(ctx.request.body) 10 | ctx.body = result 11 | } catch (err) { throw err } 12 | }, 13 | getUser: async (ctx) => { 14 | try { 15 | const result = await authDB.Dao.getUser(ctx.query.user) 16 | ctx.body = { user: result } 17 | } catch (err) { throw err } 18 | }, 19 | getRobot: async (ctx) => { 20 | try { 21 | const result = await authDB.Dao.getRobot(ctx.params.id) 22 | ctx.body = result 23 | } catch (err) { throw err } 24 | }, 25 | addRobot: async (ctx) => { 26 | try { 27 | const result = await authDB.Dao.addRobot(ctx.request.body) 28 | ctx.body = result 29 | } catch (err) { throw err } 30 | }, 31 | updateRobot: async (ctx) => { 32 | try { 33 | const result = await authDB.Dao.updateRobot(ctx.params.id, ctx.request.body) 34 | ctx.body = result 35 | } catch (err) { throw err } 36 | }, 37 | getGroups: async (ctx) => { 38 | try { 39 | const result = await groupDB.Dao.myGroups(ctx.query.id) 40 | ctx.body = result 41 | } catch (err) { throw err } 42 | }, 43 | updateGroup: async (ctx) => { 44 | try { 45 | const result = await groupDB.Dao.update(ctx.params.id, ctx.request.body) 46 | ctx.body = result 47 | } catch (err) { throw err } 48 | }, 49 | getFriends: async (ctx) => { 50 | try { 51 | const result = await friendDB.Dao.list(ctx.query) 52 | ctx.body = result 53 | } catch (err) { throw err } 54 | }, 55 | getReplys: async (ctx) => { 56 | try { 57 | const result = await replyDB.Dao.list(ctx.query) 58 | ctx.body = result 59 | } catch (err) { throw err } 60 | }, 61 | addReply: async (ctx) => { 62 | try { 63 | if(!ctx.request.body.robotId) throw {message:'未绑定机器人'} 64 | const result = await replyDB.Dao.add(ctx.request.body) 65 | ctx.body = result 66 | } catch (err) { throw err } 67 | }, 68 | updateReply: async (ctx) => { 69 | try { 70 | const result = await replyDB.Dao.update(ctx.params.id, ctx.request.body) 71 | ctx.body = result 72 | } catch (err) { throw err } 73 | }, 74 | deleteReply: async (ctx) => { 75 | try { 76 | const result = await replyDB.Dao.delete(ctx.request.body.ids) 77 | ctx.body = result 78 | } catch (err) { throw err } 79 | }, 80 | getTasks: async (ctx) => { 81 | try { 82 | const result = await taskDB.Dao.list(ctx.query) 83 | ctx.body = result 84 | } catch (err) { throw err } 85 | }, 86 | addTask: async (ctx) => { 87 | try { 88 | if(!ctx.request.body.robotId) throw {message:'未绑定机器人'} 89 | const result = await taskDB.Dao.add(ctx.request.body) 90 | ctx.body = result 91 | } catch (err) { throw err } 92 | }, 93 | updateTask: async (ctx) => { 94 | try { 95 | const result = await taskDB.Dao.update(ctx.params.id, ctx.request.body) 96 | ctx.body = result 97 | } catch (err) { throw err } 98 | }, 99 | deleteTask: async (ctx) => { 100 | try { 101 | const result = await taskDB.Dao.delete(ctx.request.body.ids) 102 | ctx.body = result 103 | } catch (err) { throw err } 104 | }, 105 | } 106 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa') 2 | const consola = require('consola') 3 | const { Nuxt, Builder } = require('nuxt') 4 | const bodyParser = require('koa-bodyparser') 5 | const jwtKoa = require('koa-jwt') 6 | const logger = require('./util/logger') 7 | const app = new Koa() 8 | const config = require('../nuxt.config.js') 9 | config.dev = app.env !== 'production' 10 | app.use(bodyParser({ extendTypes: ['json', 'text', 'form'] })) 11 | async function start() { 12 | // Instantiate nuxt.js 13 | const nuxt = new Nuxt(config) 14 | const { 15 | host = process.env.HOST || '127.0.0.1', 16 | port = process.env.PORT || 3001 17 | } = nuxt.options.server 18 | await nuxt.ready() 19 | app.use(require('./middleware/resformat')('^/api')) 20 | const api = require('./routes/api') 21 | app.use(api.routes(), api.allowedMethods()) 22 | if (config.dev) { 23 | const builder = new Builder(nuxt) 24 | await builder.build() 25 | } 26 | app.use((ctx) => { 27 | ctx.status = 200 28 | ctx.respond = false 29 | ctx.req.ctx = ctx 30 | nuxt.render(ctx.req, ctx.res) 31 | }) 32 | app.listen(port, host) 33 | consola.ready({ 34 | message: `Server listening on http://${host}:${port}`, 35 | badge: true 36 | }) 37 | } 38 | //error 39 | app.use(async (ctx, next) => { 40 | const startT = new Date() 41 | let ms 42 | try { 43 | await next().catch(err => { 44 | if (err.status === 401) { 45 | ctx.body = { errcode: 401, errmsg: 'Authentication' } 46 | } else { throw err } 47 | }) 48 | ms = new Date() - startT; 49 | } catch (error) { 50 | console.log(error) 51 | ms = new Date() - startT 52 | logger.logError(ctx, error, ms) 53 | } 54 | }) 55 | app.use(jwtKoa({ secret: require('../config').secret }).unless({ 56 | path: [ 57 | /^\/api\/auth\/login/, 58 | /^\/api\/auth\/logout/, 59 | /^\/api\/robot\/login/, 60 | /^((?!\/api).)*$/ 61 | ] 62 | })); 63 | require('./config/db').connect() 64 | const { baseLogPath, appenders } = require('./config/log4js') 65 | const fs = require('fs'); 66 | const confirmPath = function (pathStr) { 67 | if (!fs.existsSync(pathStr)) fs.mkdirSync(pathStr) 68 | } 69 | /** 70 | * init log 71 | */ 72 | const initLogPath = function () { 73 | if (baseLogPath) { 74 | confirmPath(baseLogPath) 75 | for (var i = 0, len = appenders.length; i < len; i++) { 76 | if (appenders[i].path) { 77 | confirmPath(baseLogPath + appenders[i].path); 78 | } 79 | } 80 | } 81 | } 82 | start() 83 | initLogPath() 84 | -------------------------------------------------------------------------------- /server/middleware/botLogin.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return async function (ctx, next) { 3 | if (!global.bot) throw {message:'机器人已掉线,请重新登录'} 4 | await next(); 5 | } 6 | } -------------------------------------------------------------------------------- /server/middleware/getUser.js: -------------------------------------------------------------------------------- 1 | const { verifyToken } = require('../util') 2 | module.exports = function () { 3 | return async function (ctx, next) { 4 | const res = verifyToken(ctx.header.authorization) 5 | if (ctx.method == 'GET') { 6 | ctx.query.user = res.id 7 | } else { 8 | ctx.request.body.user = res.id 9 | } 10 | await next(); 11 | } 12 | } -------------------------------------------------------------------------------- /server/middleware/resformat.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 格式化响应数据 3 | * @Author: lwp 4 | * @Date: 2020-04-20 19:13:19 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-04 23:15:59 7 | */ 8 | const url_filter = function (pattern) { 9 | return async (ctx, next) => { 10 | const reg = new RegExp(pattern); 11 | try { 12 | await next(); 13 | } catch (error) { 14 | ctx.status = 200; 15 | ctx.body = { 16 | errcode: error.code ? error.code : 1, 17 | errmsg: error.message, 18 | } 19 | throw error; 20 | } 21 | if (reg.test(ctx.originalUrl)) { 22 | if (ctx.body && ctx.body.original) return ctx.body = ctx.body.body 23 | ctx.body = { 24 | success: true, 25 | data: ctx.body 26 | } 27 | } 28 | } 29 | } 30 | module.exports = url_filter -------------------------------------------------------------------------------- /server/models/auth.js: -------------------------------------------------------------------------------- 1 | const { mongoose } = require('../config/db') 2 | const Schema = mongoose.Schema 3 | const schema = new Schema({ 4 | username: { 5 | type: String, 6 | required: true 7 | }, 8 | password: String, 9 | salt: String, 10 | createTime: { type: Date, default: new Date() }, 11 | lastLoginT: Date, 12 | loginIp: String, 13 | }) 14 | 15 | const Auth = mongoose.model('auth', schema, 'auth') 16 | const { encryptPassword, createToken } = require('../util') 17 | const { Robot } = require('./robot') 18 | module.exports = { 19 | Auth, 20 | Dao: { 21 | login: async (params) => { 22 | try { 23 | const user = await Auth.findOne({ username: params.username }) 24 | if (!user) throw { message: '用户不存在' } 25 | if (user.password != encryptPassword(user.salt, params.password)) throw { message: '密码有误' } 26 | return { token: createToken({ id: user.id }) } 27 | } catch (err) { throw err } 28 | }, 29 | getUser: async (userId) => { 30 | try { 31 | const user = await Auth.findOne({ _id: userId }, { username: 1 }) 32 | const robot = await Robot.findOne({ user: user._id }, { id: 1 }) 33 | return { username: user.username, robotId: robot && robot.id || null, robot_id: robot && robot._id || null } 34 | } catch (err) { throw err } 35 | }, 36 | getRobot: async (_id) => { 37 | try { 38 | const result = await Robot.findOne({ _id }) 39 | return result 40 | } catch (err) { throw err } 41 | }, 42 | addRobot: async (params) => { 43 | try { 44 | let result = await Robot.create(params) 45 | return result 46 | } catch (err) { throw err } 47 | }, 48 | updateRobot: async (_id, params) => { 49 | try { 50 | const result = await Robot.updateOne({_id},params); 51 | return result 52 | } catch (err) { throw err } 53 | }, 54 | } 55 | } 56 | 57 | const init = async () => { 58 | const exists = await Auth.exists({}) 59 | if (!exists) { 60 | await Auth.create({ username: 'admin', salt: '123456', password: encryptPassword('123456', '111111') }) 61 | await Auth.create({ username: 'guest', salt: '123456', password: encryptPassword('123456', '111111') }) 62 | } 63 | } 64 | init() -------------------------------------------------------------------------------- /server/models/friend.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 好友 3 | * @Author: lwp 4 | * @Date: 2020-04-30 15:39:55 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-06 18:45:47 7 | */ 8 | const { mongoose } = require('../config/db') 9 | const Schema = mongoose.Schema 10 | const schema = new Schema({ 11 | id: { type: String }, //唯一 12 | name: String, //昵称 13 | alias: String, //备注 14 | avatar: String,//头像 15 | province: String, //省份 16 | city: String,//城市 17 | gender: Number, //性别 18 | weixin: String, //微信 19 | robotId: String, //机器人id 20 | }) 21 | 22 | const Friend = mongoose.model('friend', schema, 'friend') 23 | const { parseSearch } = require('../util') 24 | module.exports = { 25 | Friend, 26 | Dao: { 27 | list: async (params) => { 28 | try { 29 | const page = Number(params.page || 1) 30 | const limit = Number(params.pageSize || 10) 31 | const start = (page - 1) * limit 32 | const condition = parseSearch(params) 33 | const sortF = { skip: start, limit: limit, sort: { '_id': 1 } } 34 | const fields = {}; 35 | const total = await Friend.countDocuments(condition) 36 | const list = await Friend.find(condition, fields, sortF) 37 | return { total, list } 38 | } catch (err) { throw err } 39 | } 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /server/models/group.js: -------------------------------------------------------------------------------- 1 | const { mongoose } = require('../config/db') 2 | const Schema = mongoose.Schema 3 | const schema = new Schema({ 4 | id: String, //群id 5 | adminIdList:Array, 6 | avatar:String, 7 | ownerId:String, 8 | topic:String, 9 | memberIdList:{ type: Array }, 10 | robotId:String, //机器人id 11 | roomJoinReply:{type:String,default:'你好,欢迎加入!'}, 12 | autojoin: { type: Boolean, default: false }, 13 | joinCode:String, 14 | maxFoul:{ type: Number, default: 3 }, 15 | control: { type: Boolean, default: false }, 16 | }) 17 | const Group = mongoose.model('group', schema, 'group') 18 | module.exports = { 19 | Group, 20 | Dao:{ 21 | myGroups:async(id)=>{ 22 | try { 23 | const result = await Group.find({robotId:id}) 24 | return result 25 | } catch (err) { throw err } 26 | }, 27 | update:async(id,params)=>{ 28 | try { 29 | const result = await Group.findByIdAndUpdate(id, params, { 30 | new: true 31 | }).exec(); 32 | return result 33 | } catch (err) { throw err } 34 | }, 35 | } 36 | } -------------------------------------------------------------------------------- /server/models/memory.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 机器人记忆 3 | * @Author: lwp 4 | * @Date: 2020-05-08 19:10:38 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-08 19:42:52 7 | */ 8 | const { mongoose } = require('../config/db') 9 | const Schema = mongoose.Schema 10 | const schema = new Schema({ 11 | person:String, //联系人 12 | cmd:String, //指令 13 | roomId: String, //群id 14 | remark: String //备注 15 | }) 16 | 17 | const Memory = mongoose.model('memory', schema, 'memory') 18 | module.exports = { 19 | Memory, 20 | } -------------------------------------------------------------------------------- /server/models/reply.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 自动回复 3 | * @Author: lwp 4 | * @Date: 2020-05-06 18:24:06 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-15 11:08:25 7 | */ 8 | const { mongoose } = require('../config/db') 9 | const Schema = mongoose.Schema 10 | const schema = new Schema({ 11 | keyword: String,//关键词 12 | content: String, //回复内容 13 | type: { type: Number, default: 0 },//类型 0:普通消息,1:发送群邀请(仅在私聊触发) 2:踢人指令(仅在群聊触发) 14 | factor: { type: Number, default: 0 },//触发场景 0:通用,1:私聊 2:群聊 3:通用群聊 15 | status: { type: Number, default: 1 }, //状态 0停用 1启用 16 | roomId: String, //群id 17 | robotId: String, //机器人id 18 | remark: String //备注 19 | }) 20 | 21 | const Reply = mongoose.model('reply', schema, 'reply') 22 | const { parseSearch } = require('../util') 23 | module.exports = { 24 | Reply, 25 | Dao: { 26 | list: async (params) => { 27 | try { 28 | const page = Number(params.page || 1) 29 | const limit = Number(params.pageSize || 10) 30 | const start = (page - 1) * limit 31 | const condition = parseSearch(params) 32 | const sortF = { skip: start, limit: limit, sort: { '_id': 1 } } 33 | const fields = {}; 34 | const total = await Reply.countDocuments(condition) 35 | const list = await Reply.find(condition, fields, sortF) 36 | return { total, list } 37 | } catch (err) { throw err } 38 | }, 39 | add: async (params) => { 40 | try { 41 | let query = {keyword:params.keyword,factor:params.factor,robotId:params.robotId} 42 | if(params.factor==2){ 43 | query.roomId = params.roomId 44 | } 45 | const ishave = await Reply.findOne(query,{_id:1}) 46 | if(ishave) throw {message:'同一关键字同一场景只能存在1个'} 47 | const result = await Reply.create(params) 48 | return result 49 | } catch (err) { throw err } 50 | }, 51 | update: async (_id, params) => { 52 | try { 53 | const result = await Reply.updateOne({ _id }, params) 54 | return result 55 | } catch (err) { throw err } 56 | }, 57 | delete: async (ids) => { 58 | try { 59 | const result = await Reply.deleteMany({ _id: { $in: ids } }) 60 | return result 61 | } catch (err) { throw err } 62 | } 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /server/models/robot.js: -------------------------------------------------------------------------------- 1 | const { mongoose } = require('../config/db') 2 | const Schema = mongoose.Schema 3 | const schema = new Schema({ 4 | nickName: String, 5 | startSay: String, 6 | unknownSay: String, 7 | addFriendKeywords: Array, 8 | addFriendReply: String, 9 | name: String, 10 | avatar: String, 11 | id: String, 12 | weixin: String, 13 | status: { type: Number, default: 0 }, //状态 0未启动 1已启动 14 | user: { type: Schema.Types.ObjectId, ref: 'auth' }, 15 | createTime: { type: Date, default: new Date() }, 16 | modifyTime: { type: Date, default: new Date() }, 17 | token: String, 18 | lastLoginT: Date, 19 | lastLoginIp: String, 20 | }) 21 | const Robot = mongoose.model('robot', schema, 'robot') 22 | module.exports = { 23 | Robot, 24 | } 25 | 26 | -------------------------------------------------------------------------------- /server/models/task.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: 定时消息 3 | * @Author: lwp 4 | * @Date: 2020-05-06 19:06:00 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-15 11:08:16 7 | */ 8 | const { mongoose } = require('../config/db') 9 | const Schema = mongoose.Schema 10 | const schema = new Schema({ 11 | name: String, //名称 12 | type: { type: Number, default: 0 },//类型 0:普通消息 13 | content: String, //发送内容 14 | factor: { type: Number, default: 0 },//触发场景 0:个人,1:群聊 15 | status: { type: Number, default: 1 }, //状态 0停用 1启用 16 | friendId: String, //联系人 17 | roomId: String, //群id 18 | robotId: String, //机器人id 19 | 20 | unit: Number, //时间单位 21 | dayOfWeek: Number, 22 | month: Number, 23 | dayOfMonth: Number, 24 | hour: Number, 25 | minute: Number, 26 | second: Number, 27 | 28 | }) 29 | const Task = mongoose.model('task', schema, 'task') 30 | const { parseSearch } = require('../util') 31 | module.exports = { 32 | Task, 33 | Dao: { 34 | list: async (params) => { 35 | try { 36 | const page = Number(params.page || 1) 37 | const limit = Number(params.pageSize || 10) 38 | const start = (page - 1) * limit 39 | const condition = parseSearch(params) 40 | const sortF = { skip: start, limit: limit, sort: { '_id': 1 } } 41 | const fields = {}; 42 | const total = await Task.countDocuments(condition) 43 | const list = await Task.find(condition, fields, sortF) 44 | return { total, list } 45 | } catch (err) { throw err } 46 | }, 47 | add: async (params) => { 48 | try { 49 | const result = await Task.create(params) 50 | await require('../bot/lib/Task').restart(result) 51 | return result 52 | } catch (err) { throw err } 53 | }, 54 | update: async (_id, params) => { 55 | try { 56 | const result = await Task.findByIdAndUpdate({ _id }, params, { new: true }) 57 | await require('../bot/lib/Task').restart(result) 58 | return result 59 | } catch (err) { throw err } 60 | }, 61 | delete: async (ids) => { 62 | try { 63 | const result = await Task.deleteMany({ _id: { $in: ids } }) 64 | ids.forEach((item) => require('../bot/lib/Task').stop(item)) 65 | return result 66 | } catch (err) { throw err } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /server/routes/api.js: -------------------------------------------------------------------------------- 1 | 2 | const router = require('koa-router')() 3 | const getUser = require('../middleware/getUser') 4 | const sysCtrl = require('../controller/sys') 5 | const botLogin = require('../middleware/botLogin') 6 | const robotCtrl = require('../controller/robot') 7 | router.prefix('/api') 8 | //登录 9 | router.post('/auth/login', sysCtrl.login) 10 | router.get('/auth/user', getUser(), sysCtrl.getUser) 11 | router.post('/auth/logout', async (ctx) => { 12 | ctx.body = null 13 | }) 14 | router.get('/admin/robot/:id', sysCtrl.getRobot) 15 | router.post('/admin/robot', getUser(), sysCtrl.addRobot) 16 | router.put('/admin/robot/:id', sysCtrl.updateRobot) 17 | router.get('/admin/group', sysCtrl.getGroups) 18 | router.put('/admin/group/:id', sysCtrl.updateGroup) 19 | router.get('/admin/friend', sysCtrl.getFriends) 20 | router.get('/admin/reply', sysCtrl.getReplys) 21 | router.post('/admin/reply', sysCtrl.addReply) 22 | router.put('/admin/reply/:id', sysCtrl.updateReply) 23 | router.delete('/admin/reply', sysCtrl.deleteReply) 24 | router.get('/admin/task', sysCtrl.getTasks) 25 | router.post('/admin/task', sysCtrl.addTask) 26 | router.put('/admin/task/:id', sysCtrl.updateTask) 27 | router.delete('/admin/task', sysCtrl.deleteTask) 28 | 29 | 30 | 31 | router.post('/robot/login', robotCtrl.login) 32 | router.post('/robot/loginOut', robotCtrl.loginOut) 33 | 34 | router.post('/robot/friend/say', botLogin(), robotCtrl.friendSay) 35 | router.post('/robot/room/say', botLogin(), robotCtrl.roomSay) 36 | router.get('/robot/room/:id', botLogin(), robotCtrl.getRoom) 37 | router.put('/robot/room/:id', botLogin(), robotCtrl.updateRoom) 38 | router.post('/robot/room/quit', botLogin(), robotCtrl.roomQuit) 39 | 40 | 41 | module.exports = router -------------------------------------------------------------------------------- /server/util/index.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | const jwt = require('jsonwebtoken') 3 | const { secret } = require('../../config') 4 | const encryptPassword = (salt, password) => { 5 | const str = salt + password; 6 | const md5 = crypto.createHash('sha1'); 7 | md5.update(str); 8 | return md5.digest('hex'); 9 | } 10 | const generateStr = (len, charType) => { 11 | len = len || 6; 12 | charType = charType || 'number'; 13 | const chars1 = 'ABCDEFGHJKMNPQRSTUVWXYabcdefghjkmnpqrstuvwxy'; 14 | const chars2 = '0123456789'; 15 | let chars = charType === 'string' ? chars1 : chars2; 16 | const maxPos = chars.length; 17 | let str = ''; 18 | for (var i = 0; i < len; i++) { 19 | str += chars.charAt(Math.floor(Math.random() * maxPos)); 20 | } 21 | return str; 22 | } 23 | const createToken = (payload = {}, expiresIn) => { 24 | return jwt.sign(payload, secret, { expiresIn: expiresIn || '24h' }); 25 | } 26 | const verifyToken = (token) => { 27 | return jwt.verify(token.split(' ')[1], secret); 28 | } 29 | const parseSearch = function (params) { 30 | let result = {}; 31 | for (let key in params) { 32 | if (key.indexOf('search') == 0 && key.split('$$').length >= 3 && params[key] != '') { 33 | let field = key.split('$$')[1]; 34 | //搜索字段 35 | let sType = key.split('$$')[2]; 36 | //搜索方式 37 | let value = params[key] || ''; 38 | //值 39 | if (value != "undefined" && value != "" && value != "null") { 40 | switch (sType) { 41 | case "all": 42 | value = { 43 | "$all": [new RegExp(".*" + value + ".*", "gi")] 44 | }; 45 | for (var i = 0; i < field.split('|').length; i++) { 46 | result[field.split('|')[i]] = value; 47 | } 48 | break; 49 | case "orAndall": 50 | let tempRegExp; 51 | let params = []; 52 | for (var i = 0; i < field.split('|').length; i++) { 53 | for (var j = 0; j < value.split('|').length; j++) { 54 | var item = {}; 55 | tempRegExp = { 56 | "$all": [new RegExp(".*" + value.split('|')[j] + ".*", "gi")] 57 | }; 58 | item[field.split('|')[i]] = tempRegExp; 59 | params.push(item); 60 | } 61 | } 62 | result["$or"] = params; 63 | break; 64 | case "or": 65 | for (let i = 0; i < field.split('|').length; i++) { 66 | let params = []; 67 | for (let j = 0; j < value.split('|').length; j++) { 68 | let item = {}; 69 | params.push(value.split('|')[j]); 70 | } 71 | result[field.split('|')[i]] = { 72 | '$in': params 73 | } 74 | } 75 | break; 76 | case "gte": 77 | for (let i = 0; i < field.split('|').length; i++) { 78 | result[field.split('|')[i]] = { 79 | "$gte": value 80 | }; 81 | } 82 | break; 83 | case "lte": 84 | for (let i = 0; i < field.split('|').length; i++) { 85 | result[field.split('|')[i]] = { 86 | "$lte": value 87 | }; 88 | } 89 | break; 90 | case "between": 91 | let values = value.split('|'); 92 | if (values.length == 2) { 93 | for (let i = 0; i < field.split('|').length; i++) { 94 | if (values[0] != "" && values[1] != "") 95 | result[field.split('|')[i]] = { 96 | "$gte": values[0], 97 | "$lte": values[1] 98 | }; 99 | else if (values[0] != "") 100 | result[field.split('|')[i]] = { 101 | "$gte": values[0] 102 | }; 103 | else if (values[1] != "") 104 | result[field.split('|')[i]] = { 105 | "$lte": values[1] 106 | }; 107 | } 108 | } 109 | break; 110 | default: 111 | for (let i = 0; i < field.split('|').length; i++) { 112 | result[field.split('|')[i]] = value; 113 | } 114 | break; 115 | } 116 | } 117 | } 118 | } 119 | return result; 120 | }; 121 | module.exports = { encryptPassword, generateStr, createToken, verifyToken, parseSearch } 122 | 123 | -------------------------------------------------------------------------------- /server/util/logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @Desc: logger 3 | * @Author: lwp 4 | * @Date: 2020-04-26 14:28:26 5 | * @LastEditors: lwp 6 | * @LastEditTime: 2020-05-15 18:29:05 7 | */ 8 | const log4js = require('log4js'); 9 | let log_config = require('../config/log4js'); 10 | //加载配置文件 11 | log4js.configure(log_config); 12 | let logUtil = {}; 13 | //调用预先定义的日志名称 14 | const resLogger = log4js.getLogger("resLogger"); 15 | const errorLogger = log4js.getLogger("errorLogger"); 16 | const infoLogger = log4js.getLogger("infoLogger"); 17 | const consoleLogger = log4js.getLogger(); 18 | //封装错误日志 19 | logUtil.logError = function (ctx, error, resTime) { 20 | if (ctx && error) { 21 | errorLogger.error(formatError(ctx, error, resTime)); 22 | } 23 | }; 24 | logUtil.error = function (err) { 25 | errorLogger.error(err); 26 | }; 27 | logUtil.info = function (info) { 28 | infoLogger.info(info); 29 | }; 30 | //封装响应日志 31 | logUtil.logResponse = function (ctx, resTime) { 32 | if (ctx) { 33 | resLogger.info(formatRes(ctx, resTime)); 34 | } 35 | }; 36 | logUtil.logInfo = function (info) { 37 | if (info) { 38 | consoleLogger.info(formatInfo(info)); 39 | } 40 | }; 41 | const formatInfo = function (info) { 42 | let logText = new String(); 43 | logText += "\n" + "***************info log start ***************" + "\n"; 44 | logText += "info detail: " + "\n" + JSON.stringify(info) + "\n"; 45 | logText += "*************** info log end ***************" + "\n"; 46 | return logText; 47 | } 48 | //格式化响应日志 49 | const formatRes = function (ctx, resTime) { 50 | let logText = new String(); 51 | logText += "\n" + "*************** response log start ***************" + "\n"; 52 | logText += formatReqLog(ctx.request, resTime); 53 | logText += "response status: " + ctx.status + "\n"; 54 | logText += "response body: " + "\n" + JSON.stringify(ctx.body) + "\n"; 55 | logText += "*************** response log end ***************" + "\n"; 56 | return logText; 57 | } 58 | //格式化错误日志 59 | const formatError = function (ctx, err, resTime) { 60 | let logText = new String(); 61 | logText += "\n" + "*************** error log start ***************" + "\n"; 62 | logText += formatReqLog(ctx.request, resTime); 63 | logText += "err name: " + err.name + "\n"; 64 | logText += "err message: " + err.message + "\n"; 65 | logText += "err stack: " + err.stack + "\n"; 66 | logText += "*************** error log end ***************" + "\n"; 67 | return logText; 68 | }; 69 | //格式化请求日志 70 | const formatReqLog = function (req, resTime) { 71 | let logText = new String(); 72 | const method = req.method; 73 | logText += "request method: " + method + "\n"; 74 | logText += "request originalUrl: " + req.originalUrl + "\n"; 75 | logText += "request client ip: " + req.ip + "\n"; 76 | if (method === 'GET') { 77 | logText += "request query: " + JSON.stringify(req.query) + "\n"; 78 | } else { 79 | logText += "request body: " + "\n" + JSON.stringify(req.body) + "\n"; 80 | } 81 | //服务器响应时间 82 | logText += "response time: " + resTime + "\n"; 83 | return logText; 84 | } 85 | module.exports = logUtil; -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beclass/wxbot/e483cd5ae85c19aaaa4dff91f3a59c16f1d2fe99/static/favicon.ico -------------------------------------------------------------------------------- /store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | import robot from './module/robot'; 4 | 5 | Vue.use(Vuex); 6 | 7 | const store = () => new Vuex.Store({ 8 | state: { 9 | }, 10 | mutations: { 11 | }, 12 | modules: { 13 | robot 14 | } 15 | }); 16 | 17 | export default store -------------------------------------------------------------------------------- /store/module/robot.js: -------------------------------------------------------------------------------- 1 | const state = ({ 2 | appName:'wxbot', 3 | }) 4 | export default { 5 | state, 6 | } --------------------------------------------------------------------------------