├── .gitignore ├── index.js ├── screenshot ├── demo.webp └── groupqr.jpeg ├── lib ├── utils.js ├── request.js ├── state.js ├── loop.js ├── task.js └── bot.js ├── package.json ├── README.md └── .github └── workflows └── build.yml /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | let bot = require("./lib/bot"); 2 | 3 | bot.start(); 4 | -------------------------------------------------------------------------------- /screenshot/demo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tans/pug/HEAD/screenshot/demo.webp -------------------------------------------------------------------------------- /screenshot/groupqr.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tans/pug/HEAD/screenshot/groupqr.jpeg -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sleep: function (ms){ 3 | return new Promise( (resolve) =>setTimeout(() =>resolve(), ms)); 4 | } 5 | } -------------------------------------------------------------------------------- /lib/request.js: -------------------------------------------------------------------------------- 1 | const fetch = require("node-fetch"); 2 | const state = require("./state"); 3 | 4 | module.exports = async (route, body = {}) => { 5 | body.botId = state.get("botId"); 6 | let host = state.get("host"); 7 | // console.log(body) 8 | let res = await fetch(host + route, { 9 | method: "POST", 10 | body: JSON.stringify(body), 11 | }); 12 | 13 | return res.json(); 14 | }; 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pugai-bot-lite", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "dependencies": { 11 | "node-fetch": "^2.7.0", 12 | "wechaty": "^1.20.2", 13 | "wechaty-puppet-xp": "1.13.12" 14 | }, 15 | "license": "ISC" 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微信机器人 2 | 3 | ## 免开发运行 4 | 5 | 1. 下载release中的压缩包,解压后点击 `启动.cmd` 运行 6 | 2. 运行前启动登录微信 7 | 8 | [详细教程](https://pug.minapp.xin/doc/help) 9 | 10 | ## 功能介绍 11 | 12 | 1. 群统计 - 发言统计,邀请统计 13 | 2. 群积分(开发中) 14 | 3. 群收藏 15 | 4. 群接口 免开发使用,可用于对接简集云等连接器。 16 | 17 | 18 | 19 | ## 更多使用技巧 20 | 21 | https://minapp.xin/pug 22 | 23 | ## 赞助支持 24 | 25 | [wechatsdk](https://wechatsdk.com/) 提供强大稳定的个人微信接口 26 | 27 | ## Roadmap 28 | 29 | 1. 信息聚合 30 | 2. 推送管理 31 | 3. 小游戏 32 | 33 | ## 注意事项 34 | 35 | 本项目为客户端实现,使用还需要服务器端实现。 36 | 37 | 本项目仅供学习参考! 38 | 39 | 微信 tianshe00 40 | 41 | ![screenshot/groupqr.jpeg](screenshot/groupqr.jpeg) 42 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build/release 2 | 3 | on: 4 | # push: 5 | # tags: 6 | # - "v*.*.*" 7 | 8 | jobs: 9 | release: 10 | permissions: write-all 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [windows-latest] 15 | steps: 16 | - name: Check out Git repository 17 | uses: actions/checkout@v3 18 | - name: Install Node.js, NPM and Yarn 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 18 22 | 23 | - name: Install 24 | run: npm install 25 | 26 | - run: npm install --global pkg 27 | - name: Build 28 | run: pkg index.js --output lite-bot --targets win 29 | - name: Release 30 | uses: softprops/action-gh-release@v2 31 | if: startsWith(github.ref, 'refs/tags/') 32 | with: 33 | files: lite-bot.exe 34 | -------------------------------------------------------------------------------- /lib/state.js: -------------------------------------------------------------------------------- 1 | let _data = { 2 | sended: 0, 3 | received: 0, 4 | messages: [], 5 | name: "", 6 | account: null, 7 | botId: "", 8 | host: process.env.BOT_HOST || "https://pug.minapp.xin", 9 | }; 10 | 11 | const state = { 12 | log: () => { 13 | console.log( 14 | `[${_data.name}] sended:${_data.sended} received:${_data.received}`, 15 | ); 16 | }, 17 | data: _data, 18 | init: (account) => { 19 | state.set("botId", account.id); 20 | state.set("account", account); 21 | state.set("name", account.name()); 22 | }, 23 | get: (attr) => { 24 | return _data[attr]; 25 | }, 26 | set: (attr, value) => { 27 | _data[attr] = value; 28 | return _data; 29 | }, 30 | 31 | incSended: (num) => { 32 | _data["sended"] = _data["sended"] + num; 33 | state.log(); 34 | return _data["sended"]; 35 | }, 36 | incReceived: (num) => { 37 | _data["received"] = _data["received"] + num; 38 | state.log(); 39 | return _data["received"]; 40 | }, 41 | }; 42 | 43 | module.exports = state; 44 | -------------------------------------------------------------------------------- /lib/loop.js: -------------------------------------------------------------------------------- 1 | const state = require("./state"); 2 | const Task = require("./task"); 3 | const request = require("./request"); 4 | 5 | let taskLoop = null; 6 | let messageLoop = null; 7 | module.exports = { 8 | loop: async (bot) => { 9 | // 执行发送任务 10 | 11 | if (!taskLoop) { 12 | taskLoop = setInterval(function () { 13 | (async () => { 14 | try { 15 | let { task } = await request("/api/task"); 16 | await Task.send(bot, task); 17 | } catch (error) { 18 | console.error(error); 19 | } 20 | })(); 21 | }, 5000); 22 | } 23 | 24 | if (!messageLoop) { 25 | // 上传消息 26 | messageLoop = setInterval(function () { 27 | (async () => { 28 | try { 29 | let messages = state.get("messages"); 30 | if (messages.length > 0) { 31 | let __messages = [...messages]; 32 | state.set("messages", []); 33 | await request("/api/messages", { messages: __messages }); 34 | state.incReceived(__messages.length); 35 | } 36 | } catch (error) { 37 | console.error(error); 38 | } 39 | })(); 40 | }, 3000); 41 | } 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /lib/task.js: -------------------------------------------------------------------------------- 1 | const { incSended } = require("./state"); 2 | const request = require("./request"); 3 | const state = require("./state"); 4 | module.exports = { 5 | send: async (bot, task) => { 6 | if (!task) { 7 | return; 8 | } 9 | if (task.type == "msg") { 10 | if (task.rid) { 11 | let room = await bot.Room.find({ id: task.rid }); 12 | await room?.say(task.text); 13 | } else { 14 | let contact = await bot.Contact.find({ id: task.toId }); 15 | await contact?.say(task.text); 16 | } 17 | incSended(1); 18 | } 19 | 20 | if (task.type == "sync-room") { 21 | await bot.Room.findAll(); 22 | let room = await bot.Room.find({ id: task.rid }); 23 | let owner = room.owner(); 24 | await request("/api/sync/room", { 25 | room: { 26 | rid: room.id, 27 | name: await room.topic(), 28 | ownerId: owner.id, 29 | ownerName: owner.name(), 30 | }, 31 | }); 32 | } 33 | 34 | if (task.type == "sync-rooms") { 35 | let rooms = []; 36 | for (let room of await bot.Room.findAll()) { 37 | let owner = room.owner(); 38 | rooms.push({ 39 | rid: room.id, 40 | name: await room.topic(), 41 | ownerId: owner.id, 42 | ownerName: owner.name(), 43 | }); 44 | } 45 | await request("/api/sync/rooms", { rooms }); 46 | } 47 | if (task.type == "sync-members") { 48 | let room = await bot.Room.find({ id: task.rid }); 49 | let contacts = await room.memberAll(); 50 | let members = contacts.map((c) => { 51 | return { 52 | rid: task.rid, 53 | wid: c.id, 54 | name: c.name(), 55 | }; 56 | }); 57 | await request("/api/sync/members", { 58 | members, 59 | }); 60 | } 61 | 62 | if (task.type == "sync-member") { 63 | await bot.Contact.findAll(); 64 | let contact = await bot.Contact.find({ id: task.wid }); 65 | if (contact.name()) { 66 | await request("/api/sync/member", { 67 | member: { 68 | wid: task.wid, 69 | rid: task.rid, 70 | name: contact.name(), 71 | }, 72 | }); 73 | } 74 | } 75 | }, 76 | }; 77 | -------------------------------------------------------------------------------- /lib/bot.js: -------------------------------------------------------------------------------- 1 | const Wechaty = require("wechaty"); 2 | const { PuppetXp } = require("wechaty-puppet-xp"); 3 | const state = require("./state"); 4 | const { loop } = require("./loop"); 5 | const request = require("./request"); 6 | const puppet = new PuppetXp(); 7 | const { WechatyBuilder } = Wechaty; 8 | 9 | const utils = require("./utils"); 10 | const task = require("./task"); 11 | 12 | const bot = WechatyBuilder.build({ 13 | puppet: puppet, 14 | name: "bot", 15 | }); 16 | 17 | state.set("bot", bot); 18 | bot.on("login", async (account) => { 19 | state.init(account); 20 | await loop(bot); 21 | 22 | try { 23 | await request("/api/sync/bot", { 24 | bot: { name: account.name(), wid: account.id }, 25 | }); 26 | 27 | utils.sleep(5000); 28 | 29 | let rooms = []; 30 | for (let room of await bot.Room.findAll()) { 31 | let owner = room.owner(); 32 | rooms.push({ 33 | rid: room.id, 34 | name: await room.topic(), 35 | ownerId: owner.id, 36 | ownerName: owner.name(), 37 | }); 38 | } 39 | await request("/api/sync/rooms", { rooms }); 40 | } catch (err) { 41 | console.error(err); 42 | } 43 | }); 44 | 45 | bot.on("message", async (msg) => { 46 | let text = msg.text(); 47 | if (text == "ping") { 48 | await msg.say("pong - local"); 49 | } 50 | 51 | let from = msg.talker(); 52 | let to = msg.listener(); 53 | let room = msg.room(); 54 | 55 | state.get("messages").push({ 56 | wid: state.get("account").id, 57 | text: text, 58 | rid: room?.id, 59 | toId: to?.id, 60 | fromId: from?.id, 61 | type: msg.type().toString(), 62 | fromName: from?.name(), 63 | date: msg.date(), 64 | }); 65 | }); 66 | 67 | bot.on("room-join", async (room, inviteeList, inviter) => { 68 | try { 69 | // todo , only bot join 70 | await task.send(bot, "sync-room"); 71 | 72 | await request("/api/sync/room-join", { 73 | room: { rid: room.id }, 74 | inviteeList: inviteeList.map((invitee) => { 75 | return { 76 | wid: invitee.id, 77 | name: invitee.name(), 78 | }; 79 | }), 80 | inviter: { 81 | wid: inviter.id, 82 | name: inviter.name(), 83 | }, 84 | }); 85 | } catch (err) { 86 | console.error(err); 87 | } 88 | }); 89 | 90 | module.exports = { 91 | start: () => { 92 | bot.start().catch(console.error); 93 | }, 94 | }; 95 | --------------------------------------------------------------------------------