├── .env.example ├── .gitignore ├── README.md ├── bot.js ├── demo └── demo.png └── package.json /.env.example: -------------------------------------------------------------------------------- 1 | PORT=3001 2 | DOMAIN=http://localhost:3001 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | pnpm-lock.yaml 3 | node_modules 4 | .env 5 | users.db 6 | rooms.db 7 | bot.memory-card.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # push-bot 2 | 3 | 推送精灵 - 微信推送机器人 4 | 5 | 关注机器人即可获得推送 API 地址 6 | 7 | ## 重要更新:切换至wechat4u,免token可运行 [有bug](https://github.com/tans/push-bot/issues/10) 8 | 9 | ## 特点 10 | - 支持推送到个人微信和群 11 | - 代码少,单文件实现 12 | - 请求限制, 防止机器人账号被封,也避免消息骚扰 13 | - 自动通过好友,自动生成接口地址 14 | 15 | ## 安装运行 16 | 17 | 1. 安装依赖 `npm install` 18 | 19 | 2. 配置参数,编辑 WECHATY_TOEKN `cp .env.example .env` 20 | 21 | 3. 运行 `node bot.js` 22 | 23 | ## 发送到个人接口 24 | 25 | 该接口通过关注机器人获得(主动私聊发送 `webhook` 或者 `推送地址` 可获得) 26 | 27 | 28 | ``` 29 | GET /send/:token?msg=xxx 30 | ``` 31 | ``` 32 | 33 | POST /send/:token 34 | 35 | { 36 | "msg": { 37 | "type": "image", 38 | "url": "https://example.com/1.png" // 图片 url 仅支持 https 39 | } 40 | } 41 | 42 | ``` 43 | 44 | 45 | 46 | ## 发送到群接口 47 | 48 | 邀请机器人入群即可获得推送接口地址 49 | 50 | 51 | ``` 52 | 53 | GET /room/:token?msg=xxx 54 | 55 | ``` 56 | 57 | 58 | ## demo 59 | 60 | 61 | 62 | ### 更多 63 | 64 | 开发者微信 tianshe00 65 | -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | let Datastore = require("nedb-promises"); 4 | 5 | const { WechatyBuilder } = require("wechaty"); 6 | let { FileBox } = require("file-box"); 7 | let { v1: uuid } = require("uuid"); 8 | 9 | let _ = require("lodash"); 10 | 11 | let fastify = require("fastify")({ 12 | logger: true, 13 | }); 14 | 15 | let UserDB = Datastore.create("./users.db"); 16 | let RoomDB = Datastore.create("./rooms.db"); 17 | 18 | // let { PuppetWechat4u } = require("wechaty-puppet-wechat4u"); 19 | 20 | // let { EventLogger } = require("wechaty-plugin-contrib"); 21 | 22 | let sleep = function () { 23 | return new Promise(function (resolve) { 24 | return setTimeout(resolve, _.random(1.2, 3.2) * 1000); 25 | }); 26 | }; 27 | 28 | bot = WechatyBuilder.build({ 29 | name: "bot", // generate xxxx.memory-card.json and save login data for the next login 30 | puppet: "wechaty-puppet-wechat4u", 31 | }); 32 | 33 | // let bot = new PuppetWechat4u(); 34 | bot.on("scan", function (qrcode) { 35 | console.log(qrcode); 36 | if (qrcode) { 37 | return require("qrcode-terminal").generate(qrcode, { 38 | small: true, 39 | }); 40 | } 41 | }) 42 | .on("friendship", async function (friendship) { 43 | var contact; 44 | // 自动通过好友, 并发送拉入群提醒 45 | await sleep(); 46 | switch (friendship.type()) { 47 | case bot.Friendship.Type.Receive: 48 | return await friendship.accept(); 49 | case bot.Friendship.Type.Confirm: 50 | contact = friendship.contact(); 51 | return await sendWebhook(contact); 52 | } 53 | }) 54 | .on("room-join", async function (room, inviteeList, inviter) { 55 | var i, invitee, len, results; 56 | results = []; 57 | for (i = 0, len = inviteeList.length; i < len; i++) { 58 | invitee = inviteeList[i]; 59 | if (invitee.self()) { 60 | // unless room.payload.ownerId is inviter.id 61 | // return inviter.say "仅限群主邀请才可获得推送地址" 62 | await room.say( 63 | "大家好,我是推送精灵, 通过接口可以控制我发送消息到群上." 64 | ); 65 | results.push(await sendRoomWebHook(inviter, room)); 66 | } else { 67 | results.push(void 0); 68 | } 69 | } 70 | return results; 71 | }) 72 | .on("room-invite", async function (roomInvitation) { 73 | return await roomInvitation.accept(); 74 | }) 75 | .on("message", async function (message) { 76 | var text; 77 | text = message.text(); 78 | if (text === "webhook" || text === "推送地址") { 79 | return await sendWebhook(message.talker()); 80 | } 81 | }) 82 | .on("error", console.error); 83 | 84 | let sendWebhook = async function (contact) { 85 | var _send, token, user; 86 | user = await UserDB.findOne({ 87 | contactid: contact.id, 88 | }); 89 | _send = async function (token) { 90 | return await contact.say( 91 | `发送地址: ${process.env.DOMAIN}/send/${token}?msg=xxx` 92 | ); 93 | }; 94 | if (user) { 95 | return await _send(user.token); 96 | } 97 | token = uuid(); 98 | await UserDB.insert({ 99 | contactid: contact.id, 100 | token: token, 101 | }); 102 | return await _send(token); 103 | }; 104 | 105 | let sendRoomWebHook = async function (contact, room) { 106 | var _send, r, token; 107 | _send = async function (token) { 108 | return await room.say( 109 | `发送地址: ${process.env.DOMAIN}/room/${token}?msg=xxx` 110 | ); 111 | }; 112 | r = await RoomDB.findOne({ 113 | contactid: contact.id, 114 | roomid: room.id 115 | }); 116 | if (r) { 117 | return await _send(r.token); 118 | } 119 | token = uuid(); 120 | await RoomDB.insert({ 121 | roomid: room.id, 122 | token: token, 123 | contactid: contact.id, 124 | }); 125 | return await _send(token); 126 | }; 127 | 128 | fastify.register(require("@fastify/rate-limit"), { 129 | max: 100, 130 | global: false, 131 | }); 132 | 133 | fastify.get( 134 | "/send/:token", 135 | { 136 | config: { 137 | rateLimit: { 138 | max: 10, 139 | keyGenerator: function (req) { 140 | return req.params.token; 141 | }, 142 | }, 143 | }, 144 | }, 145 | async function (request, reply) { 146 | var contact, msg, token, user; 147 | ({ msg } = request.query); 148 | ({ token } = request.params); 149 | user = await UserDB.findOne({ 150 | token: token, 151 | }); 152 | if (!user) { 153 | return { 154 | status: false, 155 | msg: "token not exists", 156 | }; 157 | } 158 | contact = bot.Contact.load(user.contactid); 159 | contact.say(msg); 160 | return { 161 | status: true, 162 | }; 163 | } 164 | ); 165 | 166 | fastify.get( 167 | "/room/:token", 168 | { 169 | config: { 170 | rateLimit: { 171 | max: 10, 172 | keyGenerator: function (req) { 173 | return req.params.token; 174 | }, 175 | }, 176 | }, 177 | }, 178 | async function (request, reply) { 179 | var msg, room, token; 180 | ({ msg } = request.query); 181 | ({ token } = request.params); 182 | room = await RoomDB.findOne({ 183 | token: token, 184 | }); 185 | if (!room) { 186 | return { 187 | status: false, 188 | msg: "room token not exists", 189 | }; 190 | } 191 | room = bot.Room.load(room.roomid); 192 | room.say(msg); 193 | return { 194 | status: true, 195 | }; 196 | } 197 | ); 198 | 199 | fastify.post( 200 | "/send/:token", 201 | { 202 | config: { 203 | rateLimit: { 204 | max: 10, 205 | keyGenerator: function (req) { 206 | return req.params.token; 207 | }, 208 | }, 209 | }, 210 | }, 211 | async function (request, reply) { 212 | var contact, image, msg, token, user; 213 | ({ msg } = request.body); 214 | ({ token } = request.params); 215 | user = await UserDB.findOne({ 216 | token: token, 217 | }); 218 | if (!user) { 219 | return { 220 | status: false, 221 | msg: "token not exists", 222 | }; 223 | } 224 | contact = bot.Contact.load(user.contactid); 225 | if (typeof msg === "string") { 226 | await contact.say(msg); 227 | return { 228 | status: true, 229 | }; 230 | } 231 | if (msg.type === "image") { 232 | image = FileBox.fromUrl(msg.url); 233 | await contact.say(image); 234 | return { 235 | status: true, 236 | }; 237 | } 238 | return { 239 | status: false, 240 | msg: "unsupported msg type", 241 | }; 242 | } 243 | ); 244 | 245 | let start = async function () { 246 | await bot.start(); 247 | await fastify.listen({port : process.env.PORT || 3000}); 248 | return console.log("listen " + process.env.PORT || 3000); 249 | }; 250 | 251 | start(); 252 | -------------------------------------------------------------------------------- /demo/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tans/push-bot/be8c31a5f8691d297dc54ed83c5a22bafcfc1d73/demo/demo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "push-bot", 3 | "version": "1.0.0", 4 | "description": "微信消息推送机器人", 5 | "main": "bot.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "tans", 10 | "license": "ISC", 11 | "dependencies": { 12 | "dotenv": "^10.0.0", 13 | "fastify": "^4.7.0", 14 | "@fastify/rate-limit": "^7.6.0", 15 | "file-box": "^0.16.8", 16 | "hot-import": "^0.2.14", 17 | "lodash": "^4.17.21", 18 | "nedb-promises": "^5.0.0", 19 | "qrcode-terminal": "^0.12.0", 20 | "uuid": "^8.3.2", 21 | "wechaty": "^1.20.2", 22 | "wechaty-puppet": "^1.10.2", 23 | "wechaty-puppet-wechat4u": "^1.10.2" 24 | } 25 | } 26 | --------------------------------------------------------------------------------