├── .github └── workflows │ ├── generate-json-index.yml │ ├── generate_index.js │ └── package.json ├── Adapter ├── HumanTG.js ├── pgm.js ├── qq.js ├── ssh.js ├── tgBot.js ├── web.js ├── wechatFerry.js ├── wechaty.js ├── wxKeAImao.js ├── wxMP.js ├── wxQianxun.js ├── wxWork.js ├── wxWorkKF.js └── wxXyo.js ├── README.md ├── plugins └── xinz │ ├── Bncr_ChatGPT.js │ ├── ChatGPT.js │ ├── Gemini.js │ ├── IKUN.js │ ├── QBittorrent管家 │ ├── SSH终端管家Pro.js │ ├── bncr_ssh2.0.js │ ├── cd.js │ ├── dify.js │ ├── mod │ ├── CryptoJS.js │ ├── USER_AGENTS.js │ └── prompts.json │ ├── ping.js │ ├── pixiv.js │ ├── pixiv完整版.js │ ├── qbittorent操作.js │ ├── tgstickstoqq.js │ ├── 二维码生成.js │ ├── 博客文章搜索.js │ ├── 各种小姐姐视频.js │ ├── 命令大全.js │ ├── 天气.js │ ├── 定时消息推送.js │ ├── 容器操作.js │ ├── 微信好友申请、拉群.js │ ├── 懒人菜单.js │ ├── 手动黑名单.js │ ├── 摸鱼.js │ ├── 星座运势.js │ ├── 每天60s.js │ ├── 消息转发.js │ ├── 热点趋势.js │ ├── 爱快重拨.js │ ├── 状态查询.js │ ├── 疯狂星期四.js │ ├── 联系管理员助手.js │ ├── 自动回复.js │ ├── 自定义菜单.js │ ├── 自定义通知人.js │ ├── 舔狗日记.js │ ├── 菜单.js │ └── 运行状态美化版.js ├── publicFileIndex.json └── quantumult_xinz.conf /.github/workflows/generate-json-index.yml: -------------------------------------------------------------------------------- 1 | name: 生成 插件市场所需 索引 2 | 3 | on: 4 | push: 5 | paths: 6 | - 'Adapter/**' 7 | - 'plugins/**' 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: 检出代码 15 | uses: actions/checkout@v2 16 | 17 | - name: 设置 Node.js 18 | uses: actions/setup-node@v2 19 | with: 20 | node-version: '14' 21 | 22 | - name: 安装依赖 23 | run: npm install 24 | 25 | - name: 由Actions自动生成插件市场所需JSON索引 26 | run: node .github/workflows/generate_index.js 27 | 28 | - name: 提交并推送更改 29 | run: | 30 | git config --global user.name 'github-actions[bot]' 31 | git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com' 32 | git add publicFileIndex.json 33 | git commit -m '由Actions自动生成插件市场所需JSON索引' 34 | git push 35 | env: 36 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/generate_index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const crypto = require('crypto'); 3 | const fs = require('fs'); 4 | 5 | const PluginCloudStorage = {}; 6 | 7 | const directories = ['./Adapter', './plugins']; 8 | 9 | directories.forEach(dir => { 10 | function readDirRecursive(currentDir) { 11 | const files = fs.readdirSync(currentDir); 12 | files.forEach(file => { 13 | const fullPath = path.join(currentDir, file); 14 | const stats = fs.statSync(fullPath); 15 | if (stats.isDirectory()) { 16 | readDirRecursive(fullPath); 17 | } else if (!file.startsWith('.') && !file.endsWith('.json')) { 18 | const code = fs.readFileSync(fullPath, 'utf8'); 19 | const isPublic = execParam(code, 'public'); 20 | isPublic === 'true' && RecordIndexInformation(code, fullPath); 21 | } 22 | }); 23 | } 24 | 25 | readDirRecursive(dir); 26 | }); 27 | 28 | writeJson(); 29 | 30 | function RecordIndexInformation(code, dir) { 31 | const senderPluginsInfos = { 32 | type: 'github', 33 | name: path.basename(dir, path.extname(dir)), 34 | author: execParam(code, 'author'), 35 | team: execParam(code, 'team'), 36 | version: execParam(code, 'version'), 37 | description: execParam(code, 'description'), 38 | classification: execParam(code, 'classification'), 39 | filename: path.basename(dir), 40 | fileDir: '/' + dir.replace(/\\/g, '/'), 41 | systemVersionRange: execParam(code, 'systemVersionRange'), 42 | isCron: execParam(code, 'cron') === 'true', 43 | isAdapter: execParam(code, 'adapter') === 'true', 44 | isMod: execParam(code, 'module') === 'true', 45 | isService: execParam(code, 'service') === 'true', 46 | isAuthentication: false, 47 | isEncPlugin: code.includes('/** Code Encryption Block'), 48 | id: '后续生成', 49 | }; 50 | senderPluginsInfos.id = GetPluginsID(`${senderPluginsInfos.author}:${senderPluginsInfos.team}:${senderPluginsInfos.fileDir}`); 51 | 52 | for (const key of Object.keys(senderPluginsInfos)) { 53 | if (['systemVersionRange'].includes(key)) { 54 | continue; 55 | } 56 | const val = senderPluginsInfos[key]; 57 | if (typeof val === 'string' && !val) { 58 | console.log('空值跳过!', key); 59 | return; 60 | } 61 | } 62 | 63 | if ([senderPluginsInfos.isAdapter, senderPluginsInfos.isMod, senderPluginsInfos.isService].includes(true)) { 64 | senderPluginsInfos.isChatPlugin = false; 65 | } else { 66 | senderPluginsInfos.isChatPlugin = true; 67 | } 68 | 69 | try { 70 | senderPluginsInfos.classification = JSON.parse(senderPluginsInfos.classification); 71 | if (!Array.isArray(senderPluginsInfos.classification)) { 72 | console.log('不是数组跳过!', senderPluginsInfos.classification); 73 | return; 74 | } 75 | } catch (error) { 76 | console.log('error', error); 77 | return; 78 | } 79 | PluginCloudStorage[senderPluginsInfos.id] = senderPluginsInfos; 80 | } 81 | 82 | function execParam(pluginStr, param) { 83 | const regex = new RegExp(`(?<=@${param} )[^\r\n]+`, 'g'); 84 | const tempVal = pluginStr.match(regex); 85 | return tempVal ? tempVal[0] : ''; 86 | } 87 | 88 | function GetPluginsID(str) { 89 | return crypto.createHmac('sha1', 'GetPluginsID').update(str).digest('base64'); 90 | } 91 | 92 | function writeJson() { 93 | fs.writeFile( 94 | './publicFileIndex.json', 95 | JSON.stringify( 96 | { 97 | annotation: '该文件由系统自动生成. 用于插件市场索引,请勿编辑该文件中的任何内容', 98 | ...PluginCloudStorage, 99 | }, 100 | null, 101 | 2 102 | ), 103 | 'utf8', 104 | err => { 105 | if (err) { 106 | console.log(err); 107 | } else { 108 | console.log('JSON 索引文件已生成'); 109 | } 110 | } 111 | ); 112 | } 113 | -------------------------------------------------------------------------------- /.github/workflows/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-index-generator", 3 | "version": "1.0.0", 4 | "main": "generate_index.js", 5 | "scripts": { 6 | "start": "node generate_index.js" 7 | }, 8 | "dependencies": {} 9 | } 10 | -------------------------------------------------------------------------------- /Adapter/pgm.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the App project. 3 | * @author YuanKK 4 | * @name pgm 5 | * team xinz 6 | * @version 1.0.0 7 | * @description pgm适配器 8 | * @adapter true 9 | * @public true 10 | * @disable false 11 | * @priority 200 12 | */ 13 | 14 | module.exports = async () => { 15 | if (!sysMethod.config?.pgm?.enable) return sysMethod.startOutLogs('未启用pgm 退出.'); 16 | const pgmDB = new BncrDB('pgm'); 17 | const pgm = new Adapter('pgm'); 18 | const events = require('events'); 19 | const eventS = new events.EventEmitter(); 20 | const { randomUUID } = require('crypto'); 21 | const listArr = []; 22 | router.ws('/api/bot/pgmws', ws => { 23 | ws.on('message', async msg => { 24 | // console.log("msg"+msg) 25 | let body = JSON.parse(msg); 26 | let chat_id = body.chat.id; 27 | let msg_id = body.id; 28 | if (body.echo) { 29 | for (const e of listArr) { 30 | if (body.echo !== e.uuid) continue; 31 | if (body.status === 'ok') 32 | return e.eventS.emit(e.uuid, msg_id + ":" + chat_id); 33 | else return e.eventS.emit(e.uuid, ''); 34 | } 35 | } 36 | // 忽略编辑的消息 37 | if (body.edit_date) { 38 | return; 39 | } 40 | 41 | let reply_to = body.id; 42 | let reply_to_sender_id = 0; 43 | let sender_id = body.from_user?.id || 0; 44 | let user_name = ""; 45 | let chat_name = body.chat.title || ""; 46 | let botId = body.bot_id || "0"; 47 | let isGroup = body.is_group || ""; 48 | let msgText = body.text || ''; 49 | user_name = body.from_user?.first_name || ""; 50 | user_name += body.from_user?.last_name || ""; 51 | 52 | if (body?.reply_to_message_id) 53 | reply_to = body.reply_to_message_id 54 | if (body?.reply_to_message) 55 | reply_to = body.reply_to_message.id 56 | if (body?.reply_to_message?.text) { 57 | let ignoreWords = await pgmDB.get("ignoreWords"); 58 | if (!(ignoreWords?.split("&") || [",id", ",re"]).includes(msgText)) { 59 | msgText += body?.reply_to_message?.text; 60 | } 61 | } 62 | if (body?.reply_to_message?.from_user) 63 | reply_to_sender_id = body.reply_to_message.from_user.id 64 | let msgInfo = { 65 | userId: sender_id.toString() || '', 66 | userName: user_name || '', 67 | groupId: isGroup === 'True' ? chat_id.toString() : '0', 68 | groupName: chat_name || '', 69 | msg: msgText, 70 | msgId: msg_id + ":" + chat_id, 71 | isGroup: isGroup || "", 72 | replyTo: reply_to || "", 73 | replyToSenderId: reply_to_sender_id, 74 | botId: botId.toString(), 75 | friendId: chat_id.toString(), 76 | }; 77 | // console.log(msgInfo); 78 | pgm.receive(msgInfo); 79 | }); 80 | ws.on("connection", ws => { 81 | console.log("客户端链接成功: " + ws._ultron.id) 82 | }); 83 | 84 | /* 发送消息方法 */ 85 | pgm.reply = async function (replyInfo){ 86 | try { 87 | let uuid = randomUUID(); 88 | let body = { 89 | action: 'send_msg', 90 | params: {}, 91 | echo: uuid, 92 | }; 93 | body.params.chat_id = +replyInfo.groupId || +this?.msgInfo?.friendId || +replyInfo.userId; 94 | if (replyInfo.msgId) 95 | body.params.reply_to_message_id = parseInt(replyInfo.msgId.split(":")[0]); 96 | else 97 | body.params.reply_to_message_id = parseInt(replyInfo.toMsgId.split(":")[0]); 98 | body.params.message = replyInfo.msg; 99 | if (!replyInfo.type || replyInfo.type === "text") { 100 | body.params.type = "text"; 101 | // console.log("msgInfo: " + JSON.stringify(this.msgInfo)) 102 | body.params.do_edit = replyInfo.msgId && replyInfo?.botId === replyInfo.userId ? 103 | !replyInfo.dontEdit : false; 104 | } else if (replyInfo.type === "image" || replyInfo.type === "video" || replyInfo.type === "audio") { 105 | body.params.path = replyInfo.path; 106 | body.params.type = replyInfo.type; 107 | } else { 108 | body.params.type = replyInfo.type; 109 | body.params.do_edit = replyInfo.msgId && replyInfo?.botId === replyInfo.userId ? 110 | !replyInfo.dontEdit : false; 111 | } 112 | 113 | // console.log('推送消息运行了', body); 114 | ws.send(JSON.stringify(body)); 115 | return new Promise((resolve, reject) => { 116 | listArr.push({ uuid, eventS }); 117 | let timeoutID = setTimeout(() => { 118 | delListens(uuid); 119 | eventS.emit(uuid, ''); 120 | }, 60 * 1000); 121 | eventS.once(uuid, res => { 122 | try { 123 | delListens(uuid); 124 | clearTimeout(timeoutID); 125 | resolve(res || ''); 126 | } catch (e) { 127 | console.error(e); 128 | } 129 | }); 130 | }); 131 | } catch (e) { 132 | console.error('pgm:发送消息失败', e, replyInfo); 133 | } 134 | }; 135 | 136 | /* 推送消息 */ 137 | pgm.push = async function (replyInfo) { 138 | return await this.reply(replyInfo); 139 | }; 140 | 141 | /* 删除消息 */ 142 | pgm.delMsg = async function (argsArr) { 143 | try { 144 | argsArr.forEach(e => { 145 | if (!e && typeof e !== 'string' && typeof e !== 'number') return false; 146 | let [msgId, chatId] = e.split(":") 147 | if (!isNaN(msgId) && !isNaN(chatId)) { 148 | ws.send( 149 | JSON.stringify({ 150 | action: 'delete_msg', 151 | params: { message_id: parseInt(msgId), chat_id: parseInt(chatId)}, 152 | }) 153 | ); 154 | } else { 155 | console.log("pgm撤回消息异常", e); 156 | } 157 | }); 158 | return true; 159 | } catch (e) { 160 | console.log('pgm撤回消息异常', e); 161 | return false; 162 | } 163 | }; 164 | 165 | pgm.Bridge= { 166 | editMsgMedia: async function (replyInfo, msgInfo) { 167 | if (Object.prototype.toString.call(replyInfo) === '[object Object]') { 168 | let [msgId, chatId] = replyInfo.msgId.split(":"); 169 | if (msgInfo.botId === msgInfo.userId) { 170 | ws.send( 171 | JSON.stringify({ 172 | action: 'edit_message_media', 173 | params: { 174 | message_id: parseInt(msgId), 175 | chat_id: parseInt(chatId), 176 | type: replyInfo.type, 177 | path: replyInfo.path, 178 | message: replyInfo.msg 179 | }, 180 | }) 181 | ); 182 | } else console.log("没有权限编辑!!!") 183 | } 184 | return replyInfo.msgId; 185 | } 186 | }; 187 | 188 | function delListens(id) { 189 | listArr.forEach((e, i) => e.uuid === id && listArr.splice(i, 1)); 190 | } 191 | }); 192 | return pgm; 193 | }; 194 | -------------------------------------------------------------------------------- /Adapter/qq.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the App project. 3 | * @author Aming 4 | * @name qq 5 | * @team Bncr团队 6 | * @version 1.0.1 7 | * @description 外置qq机器人适配器 8 | * @adapter true 9 | * @public false 10 | * @disable false 11 | * @priority 10000 12 | * @classification ["官方适配器"] 13 | * @Copyright ©2023 Aming and Anmours. All rights reserved 14 | * Unauthorized copying of this file, via any medium is strictly prohibited 15 | */ 16 | const he = require('he'); 17 | /* 配置构造器 */ 18 | const jsonSchema = BncrCreateSchema.object({ 19 | enable: BncrCreateSchema.boolean().setTitle('是否开启适配器').setDescription(`设置为关则不加载该适配器`).setDefault(false), 20 | mode: BncrCreateSchema.string().setTitle('适配器模式').setDescription(`填入ws或http,\n如果是ws模式,无界ws接收地址为ws://bncrip:9090/api/bot/qqws`).setDefault('ws'), 21 | sendUrl: BncrCreateSchema.string() 22 | .setTitle('http交互发送地址') 23 | .setDescription(`如果是http模式,则需要设置 sendUrl,改地址为远端qq机器人的监听地址:端口\n,无界接受地址为:http://bncrip:9090/api/bot/qqHttp`) 24 | .setDefault('http://192.168.31.192:9696'), 25 | }); 26 | 27 | /* 配置管理器 */ 28 | const ConfigDB = new BncrPluginConfig(jsonSchema); 29 | 30 | module.exports = async () => { 31 | await ConfigDB.get(); 32 | /* 如果用户未配置,userConfig则为空对象{} */ 33 | if (!Object.keys(ConfigDB.userConfig).length) { 34 | sysMethod.startOutLogs('未配置qq适配器,退出.'); 35 | return; 36 | } 37 | if (!ConfigDB?.userConfig?.enable) return sysMethod.startOutLogs('未启用外置qq 退出.'); 38 | let qq = new Adapter('qq'); 39 | if (ConfigDB.userConfig.mode === 'ws') await ws(qq); 40 | else if (ConfigDB.userConfig.mode === 'http') await http(qq); 41 | // console.log('qq适配器..', qq); 42 | return qq; 43 | }; 44 | 45 | async function ws(qq) { 46 | const events = require('events'); 47 | const eventS = new events.EventEmitter(); 48 | const { randomUUID } = require('crypto'); 49 | const listArr = []; 50 | /* ws监听地址 ws://192.168.31.192:9090/api/qq/qqws */ 51 | router.ws('/api/bot/qqws', ws => { 52 | ws.on('message', msg => { 53 | const body = JSON.parse(msg); 54 | /* 拒绝心跳链接消息 */ 55 | // console.log('收到ws请求', body); 56 | if (body.post_type === 'meta_event') return; 57 | // console.log('收到ws请求', body); 58 | if (body.echo) { 59 | for (const e of listArr) { 60 | if (body.echo !== e.uuid) continue; 61 | if (body.status && body.status === 'ok') 62 | return e.eventS.emit(e.uuid, body.data.message_id.toString()); 63 | else return e.eventS.emit(e.uuid, ''); 64 | } 65 | } 66 | /* 不是消息退出 */ 67 | if (!body.post_type || body.post_type !== 'message') return; 68 | let msgInfo = { 69 | userId: body.sender.user_id + '' || '', 70 | userName: body.sender.nickname || '', 71 | groupId: body.group_id ? body.group_id + '' : '0', 72 | groupName: body.group_name || '', 73 | msg: he.decode(body.raw_message) || '', 74 | msgId: body.message_id + '' || '', 75 | }; 76 | // console.log('最终消息:', msgInfo); 77 | qq.receive(msgInfo); 78 | }); 79 | 80 | // console.log('qq适配器..', qq); 81 | 82 | /* 发送消息方法 */ 83 | qq.reply = async function (replyInfo) { 84 | try { 85 | let uuid = randomUUID(); 86 | let body = { 87 | action: 'send_msg', 88 | params: {}, 89 | echo: uuid, 90 | }; 91 | +replyInfo.groupId 92 | ? (body.params.group_id = replyInfo.groupId) 93 | : (body.params.user_id = replyInfo.userId); 94 | if (replyInfo.type === 'text') { 95 | body.params.message = replyInfo.msg; 96 | } else if (replyInfo.type === 'image') { 97 | body.params.message = `[CQ:image,file=${replyInfo.path}]`; 98 | } else if (replyInfo.type === 'video') { 99 | body.params.message = `[CQ:video,file=${replyInfo.path}]`; 100 | } else if (replyInfo.type === 'music') { 101 | body.params.message = { 102 | "type": "music", 103 | "data": { 104 | "type": "custom", 105 | "url": replyInfo.path, 106 | "audio": replyInfo.path, 107 | "title": replyInfo.title, 108 | "image": replyInfo.image, 109 | "content": replyInfo.description 110 | } 111 | }; 112 | } else if (replyInfo.type === 'data') { 113 | body.params.message = `${replyInfo.msg}`; 114 | } 115 | // console.log('推送消息运行了', body); 116 | ws.send(JSON.stringify(body)); 117 | return new Promise((resolve, reject) => { 118 | listArr.push({ uuid, eventS }); 119 | let timeoutID = setTimeout(() => { 120 | delListens(uuid); 121 | eventS.emit(uuid, ''); 122 | }, 60 * 1000); 123 | eventS.once(uuid, res => { 124 | try { 125 | delListens(uuid); 126 | clearTimeout(timeoutID); 127 | resolve(res || ''); 128 | } catch (e) { 129 | console.error(e); 130 | } 131 | }); 132 | }); 133 | } catch (e) { 134 | console.error('qq:发送消息失败', e); 135 | } 136 | }; 137 | 138 | /* 推送消息 */ 139 | qq.push = async function (replyInfo) { 140 | // console.log('replyInfo', replyInfo); 141 | return await this.reply(replyInfo); 142 | }; 143 | 144 | /* 注入删除消息方法 */ 145 | qq.delMsg = async function (argsArr) { 146 | try { 147 | argsArr.forEach(e => { 148 | if (typeof e !== 'string' && typeof e !== 'number') return false; 149 | ws.send( 150 | JSON.stringify({ 151 | action: 'delete_msg', 152 | params: { message_id: e }, 153 | }) 154 | ); 155 | }); 156 | return true; 157 | } catch (e) { 158 | console.log('qq撤回消息异常', e); 159 | return false; 160 | } 161 | }; 162 | }); 163 | 164 | /**向/api/系统路由中添加路由 */ 165 | router.get('/api/bot/qqws', (req, res) => 166 | res.send({ msg: '这是Bncr 外置qq Api接口,你的get请求测试正常~,请用ws交互数据' }) 167 | ); 168 | router.post('/api/bot/qqws', async (req, res) => 169 | res.send({ msg: '这是Bncr 外置qq Api接口,你的post请求测试正常~,请用ws交互数据' }) 170 | ); 171 | 172 | function delListens(id) { 173 | listArr.forEach((e, i) => e.uuid === id && listArr.splice(i, 1)); 174 | } 175 | } 176 | 177 | async function http(qq) { 178 | const request = require('util').promisify(require('request')); 179 | /* 上报地址(gocq监听地址) */ 180 | let senderUrl = ConfigDB?.userConfig?.sendUrl; 181 | if (!senderUrl) { 182 | console.log('qq:配置文件未设置sendUrl'); 183 | qq = null; 184 | return; 185 | } 186 | 187 | /* 接受消息地址为: http://bncrip:9090/api/bot/qqHttp */ 188 | router.post('/api/bot/qqHttp', async (req, res) => { 189 | res.send('ok'); 190 | const body = req.body; 191 | // console.log('req', req.body); 192 | /* 心跳消息退出 */ 193 | if (body.post_type === 'meta_event') return; 194 | // console.log('收到qqHttp请求', body); 195 | /* 不是消息退出 */ 196 | if (!body.post_type || body.post_type !== 'message') return; 197 | let msgInfo = { 198 | userId: body.sender['user_id'] + '' || '', 199 | userName: body.sender['nickname'] || '', 200 | groupId: body.group_id ? body.group_id + '' : '0', 201 | groupName: body.group_name || '', 202 | msg: body['raw_message'] || '', 203 | msgId: body.message_id + '' || '', 204 | }; 205 | qq.receive(msgInfo); 206 | }); 207 | 208 | /**向/api/系统路由中添加路由 */ 209 | router.get('/api/bot/qqHttp', (req, res) => 210 | res.send({ msg: '这是Bncr 外置qq Api接口,你的get请求测试正常~,请用ws交互数据' }) 211 | ); 212 | router.post('/api/bot/qqHttp', async (req, res) => 213 | res.send({ msg: '这是Bncr 外置qq Api接口,你的post请求测试正常~,请用ws交互数据' }) 214 | ); 215 | 216 | /* 回复 */ 217 | qq.reply = async function (replyInfo) { 218 | try { 219 | let action = '/send_msg', 220 | body = {}; 221 | +replyInfo.groupId ? (body['group_id'] = replyInfo.groupId) : (body['user_id'] = replyInfo.userId); 222 | if (replyInfo.type === 'text') { 223 | body.message = replyInfo.msg; 224 | } else if (replyInfo.type === 'image') { 225 | body.message = `[CQ:image,file=${replyInfo.msg}]`; 226 | } else if (replyInfo.type === 'video') { 227 | body.message = `[CQ:video,file=${replyInfo.msg}]`; 228 | } else if (replyInfo.type === 'audio') { 229 | body.params.message = `[CQ:record,file=${replyInfo.path}]`; 230 | } else if (replyInfo.type === 'music') { 231 | body.params.message = { 232 | "type": "music", 233 | "data": { 234 | "type": "custom", 235 | "url": replyInfo.path, 236 | "audio": replyInfo.path, 237 | "title": replyInfo.title, 238 | "image": replyInfo.image, 239 | "content": replyInfo.description 240 | } 241 | }; 242 | console.log(`1: ${body.params.message}`) 243 | } 244 | let sendRes = await requestPost(action, body); 245 | return sendRes ? sendRes.message_id : '0'; 246 | } catch (e) { 247 | console.error('qq:发送消息失败', e); 248 | } 249 | }; 250 | /* 推送消息 */ 251 | qq.push = async function (replyInfo) { 252 | return await this.reply(replyInfo); 253 | }; 254 | 255 | /* 注入删除消息方法 */ 256 | qq.delMsg = async function (argsArr) { 257 | try { 258 | argsArr.forEach(e => { 259 | if (typeof e === 'string' || typeof e === 'number') { 260 | requestPost('/delete_msg', { message_id: e }); 261 | } 262 | }); 263 | return true; 264 | } catch (e) { 265 | console.log('qq撤回消息异常', e); 266 | return false; 267 | } 268 | }; 269 | 270 | /* 请求 */ 271 | async function requestPost(action, body) { 272 | return ( 273 | await request({ 274 | url: senderUrl + action, 275 | method: 'post', 276 | headers: { 'Content-Type': 'application/json' }, 277 | body: body, 278 | json: true, 279 | }) 280 | ).body; 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /Adapter/ssh.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the App project. 3 | * @author Aming 4 | * @name ssh 5 | * @team 官方 6 | * @version 1.0.3 7 | * @description ssh控制台适配器 8 | * @adapter true 9 | * @public false 10 | * @disable false 11 | * @priority 0 12 | * @Copyright ©2023 Aming and Anmours. All rights reserved 13 | * @classification ["官方适配器"] 14 | * Unauthorized copying of this file, via any medium is strictly prohibited 15 | */ 16 | 17 | module.exports = async () => { 18 | const { randomUUID } = require('crypto'); 19 | const ssh = new Adapter('ssh'); 20 | const sshDb = new BncrDB('ssh'); 21 | const readline = require('readline'); 22 | 23 | let userId = await sshDb.get('admin', ''); 24 | if (!userId) { 25 | userId = randomUUID().split('-')[4]; 26 | await sshDb.set('admin', userId); 27 | } 28 | const rl = readline.createInterface({ 29 | input: process.stdin, 30 | output: process.stdout, 31 | }); 32 | rl.on('SIGINT', () => { 33 | console.log('Ctrl+C 主动退出'); 34 | process.exit(1); 35 | }); 36 | rl.on('line', async input => { 37 | if (!input) return; 38 | const msg = input.split(' ')[0]; 39 | switch (msg) { 40 | case 'cls': 41 | console.clear(); 42 | break; 43 | case 'bye': 44 | process.exit(0); 45 | default: 46 | ssh.receive({ 47 | userId, 48 | userName: 'ssh@Admin', 49 | groupId: '0', 50 | groupName: '', 51 | msg: input, 52 | msgId: randomUUID().split('-')[4], 53 | type: 'ssh', 54 | }); 55 | break; 56 | } 57 | }); 58 | 59 | ssh.reply = function (msgInfo) { 60 | console.log(' '); 61 | console.log('ssh@Admin: '); 62 | console.log('', msgInfo.msg); 63 | console.log(' '); 64 | }; 65 | ssh.push = function (msgInfo) { 66 | return this.reply(msgInfo); 67 | }; 68 | return ssh; 69 | }; 70 | -------------------------------------------------------------------------------- /Adapter/tgBot.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the App project. 3 | * @author Aming 4 | * @name tgBot 5 | * @team Bncr团队 6 | * @version 1.0.3 7 | * @description tgBot适配器 8 | * @adapter true 9 | * @public true 10 | * @disable false 11 | * @priority 3 12 | * @classification ["官方适配器"] 13 | * @Copyright ©2023 Aming and Anmours. All rights reserved 14 | * Unauthorized copying of this file, via any medium is strictly prohibited 15 | */ 16 | /* 配置构造器 */ 17 | const jsonSchema = BncrCreateSchema.object({ 18 | enable: BncrCreateSchema.boolean().setTitle('是否开启适配器').setDescription(`设置为关则不加载该适配器`).setDefault(false), 19 | token: BncrCreateSchema.string().setTitle('tgBot的Token').setDescription(`你的telegarmBot的apiToken`).setDefault(''), 20 | proxyHost: BncrCreateSchema.string().setTitle('自建tg反代').setDescription(`你的telegarm自建反代`).setDefault(''), 21 | }); 22 | 23 | /* 配置管理器 */ 24 | const ConfigDB = new BncrPluginConfig(jsonSchema); 25 | 26 | module.exports = async () => { 27 | /* 读取用户配置 */ 28 | await ConfigDB.get(); 29 | /* 如果用户未配置,userConfig则为空对象{} */ 30 | if (!Object.keys(ConfigDB.userConfig).length) { 31 | sysMethod.startOutLogs('未配置tgBot适配器,退出.'); 32 | return; 33 | } 34 | 35 | if (!ConfigDB.userConfig.enable) return sysMethod.startOutLogs('未启用tgBot 退出.'); 36 | /* 补全依赖 */ 37 | await sysMethod.testModule(['node-telegram-bot-api'], { install: true }); 38 | const TelegramBot = require(`node-telegram-bot-api`); 39 | const Token = ConfigDB.userConfig.token; 40 | const opt = { 41 | polling: true, 42 | baseApiUrl: ConfigDB.userConfig.proxyHost 43 | }; 44 | 45 | const tgBot = new TelegramBot(Token, opt); 46 | const tg = new Adapter('tgBot'); 47 | /* 注入发送消息方法 */ 48 | tg.reply = async function (replyInfo, send = '') { 49 | try { 50 | let sendId = +replyInfo.groupId || +replyInfo.userId; 51 | if (replyInfo.type === 'text') { 52 | send = await tgBot.sendMessage(sendId, replyInfo.msg); 53 | } else if (replyInfo.type === 'image') { 54 | if (replyInfo.msg) { 55 | send = await tgBot.sendPhoto(sendId, replyInfo.path, {caption: replyInfo.msg}); 56 | } else { 57 | send = await tgBot.sendPhoto(sendId, replyInfo.path); 58 | } 59 | } else if (replyInfo.type === 'video') { 60 | send = await tgBot.sendVideo(sendId, replyInfo.path); 61 | } else if (replyInfo.type === 'audio') { 62 | send = await tgBot.sendAudio(sendId, replyInfo.path, { 63 | title: replyInfo?.name || '', 64 | performer: replyInfo?.singer || '' 65 | }); 66 | } else if (replyInfo.type === 'markdown') { 67 | send = await tgBot.sendMessage(sendId, replyInfo.msg, { 68 | parse_mode: 'Markdown' 69 | }); 70 | } else if (replyInfo.type === 'html') { 71 | send = await tgBot.sendMessage(sendId, replyInfo.msg, { 72 | parse_mode: 'HTML' 73 | }); 74 | } 75 | return send ? `${send.chat.id}:${send.message_id}` : '0'; 76 | } catch (e) { 77 | console.error('tg发送消息失败....', e.message); 78 | } 79 | }; 80 | /* 推送方法 */ 81 | tg.push = async function (replyInfo) { 82 | try { 83 | return await this.reply(replyInfo); 84 | } catch (e) { 85 | console.error('tgBot push消息失败', e); 86 | } 87 | }; 88 | /* 注入删除消息方法 */ 89 | tg.delMsg = async function (args) { 90 | try { 91 | args.forEach(e => { 92 | if (typeof e === 'string' || typeof e === 'number') { 93 | let [chatid, sendid] = e.split(':'); 94 | // console.log(chatid); 95 | // console.log(sendid); 96 | console.log('撤销:', e); 97 | tgBot.deleteMessage(chatid, sendid); 98 | } 99 | }); 100 | return true; 101 | } catch (e) { 102 | console.log(e); 103 | return false; 104 | } 105 | }; 106 | 107 | tgBot.on('message', req => { 108 | try { 109 | // console.log("data: ", req); 110 | let msgInfo = { 111 | userId: req['from']['id'] + '' || '', 112 | userName: req['from']['username'] || '', 113 | groupId: req['chat']['type'] !== 'private' ? req['chat']['id'] + '' : '0', 114 | groupName: req['group_name'] || '', 115 | msg: req['text'] || '', 116 | msgId: `${req['chat']['id']}:${req['message_id']}` || '', 117 | fromType: `Social`, 118 | }; 119 | // console.log('tg最终消息:', msgInfo); 120 | tg.receive(msgInfo); 121 | } catch (e) { 122 | console.log('tgBot接收器错误:', e); 123 | } 124 | }); 125 | tgBot.on('polling_error', msg => console.log('tgBot轮询错误:', msg.message)); 126 | sysMethod.startOutLogs('链接tgBot 成功.'); 127 | return tg; 128 | }; -------------------------------------------------------------------------------- /Adapter/wechatFerry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the Bncr project. 3 | * @author 3zang 4 | * @name wechatFerry 5 | * @origin 3zang 6 | * @team 3zang 7 | * @version 1.0.2 8 | * @description wechatFerry适配器 wcf微信地址:https://github.com/lich0821/WeChatFerry/releases/tag/v39.0.14 9 | * @adapter true 10 | * @public true 11 | * @disable false 12 | * @priority 2 13 | * @Copyright ©2024 3zang. All rights reserved 14 | * @classification ["娱乐"] 15 | * Unauthorized copying of this file, via any medium is strictly prohibited 16 | */ 17 | /* 配置构造器 */ 18 | const jsonSchema = BncrCreateSchema.object({ 19 | enable: BncrCreateSchema.boolean().setTitle('是否开启适配器').setDescription(`设置为关则不加载该适配器`).setDefault(false), 20 | sendUrl: BncrCreateSchema.string().setTitle('上报地址').setDescription(`wechatFerry的地址`).setDefault('http://192.168.0.134:10010/'), 21 | fileServer: BncrCreateSchema.string().setTitle('文件服务器地址').setDescription(`和微信在同一机器的文件服务器地址,需单独部署`).setDefault('http://192.168.0.134:3000/'), 22 | }); 23 | /* 配置管理器 */ 24 | const ConfigDB = new BncrPluginConfig(jsonSchema); 25 | module.exports = async () => { 26 | /* 读取用户配置 */ 27 | await ConfigDB.get(); 28 | /* 如果用户未配置,userConfig则为空对象{} */ 29 | if (!Object.keys(ConfigDB.userConfig).length) { 30 | sysMethod.startOutLogs('未启用wechatFerry适配器,退出.'); 31 | return; 32 | } 33 | if (!ConfigDB.userConfig.enable) return sysMethod.startOutLogs('未启用wechatFerry 退出.'); 34 | let wechatFerryUrl = ConfigDB.userConfig.sendUrl; 35 | let fileServer = ConfigDB.userConfig.fileServer; 36 | if (!wechatFerryUrl) return console.log('wechatFerry:配置文件未设置sendUrl'); 37 | if (!fileServer) console.log('wechatFerry:配置文件未设置文件服务器地址:fileServer'); 38 | //这里new的名字将来会作为 sender.getFrom() 的返回值 39 | const wechatFerry = new Adapter('wechatFerry'); 40 | // 包装原生require 你也可以使用其他任何请求工具 例如axios 41 | const request = require('util').promisify(require('request')); 42 | // wx数据库 43 | const wxDB = new BncrDB('wechatFerryUrl'); 44 | /**向/api/系统路由中添加路由 */ 45 | router.get('/api/bot/ferry', (req, res) => res.send({ msg: '这是wechatFerryUrl Api接口,你的get请求测试正常~,请用post交互数据' })); 46 | router.post('/api/bot/ferry', async (req, res) => { 47 | try { 48 | const body = req.body; 49 | let msgInfo = null; 50 | //私聊 51 | if (body.is_group == false) { 52 | msgInfo = { 53 | userId: body.sender || '', 54 | userName: body.sender || '', 55 | groupId: '0', 56 | groupName: '', 57 | msg: body.content || '', 58 | msgId: body.id || '', 59 | fromType: `Social`, 60 | }; 61 | //群 62 | } else if (body.is_group == true) { 63 | msgInfo = { 64 | userId: body.sender || '', 65 | userName: body.sender || '', 66 | groupId: body.roomid.replace('@chatroom', '') || '0', 67 | groupName: body.content.group_name || '', 68 | msg: body.content || '', 69 | msgId: body.id || '', 70 | fromType: `Social`, 71 | }; 72 | } 73 | msgInfo && wechatFerry.receive(msgInfo); 74 | res.send({ status: 200, data: '', msg: 'ok' }); 75 | } catch (e) { 76 | console.error('ferry:', e); 77 | res.send({ status: 400, data: '', msg: e.toString() }); 78 | } 79 | }); 80 | wechatFerry.reply = async function (replyInfo) { 81 | //console.log('replyInfo', replyInfo); 82 | let body = null; 83 | const to_Wxid = +replyInfo.groupId ? replyInfo.groupId + '@chatroom' : replyInfo.userId; 84 | switch (replyInfo.type) { 85 | case 'text': 86 | replyInfo.msg = replyInfo.msg.replace(/\n/g, '\r'); 87 | body = { 88 | receiver: to_Wxid, 89 | aters: "", 90 | msg: replyInfo.msg, 91 | api: 'text', 92 | }; 93 | break; 94 | case 'image': 95 | body = { 96 | receiver: to_Wxid, 97 | path: fileServer ? await getLocalPath(replyInfo.path, "img") : replyInfo.path, 98 | api: 'image', 99 | }; 100 | break; 101 | case 'video': 102 | body = { 103 | receiver: to_Wxid, 104 | path: fileServer ? await getLocalPath(replyInfo.path, "video") : replyInfo.path, 105 | suffix: "mp4", 106 | api: 'file', 107 | }; 108 | break; 109 | default: 110 | return; 111 | break; 112 | } 113 | 114 | body && (await requestFerry(body)); 115 | console.log('body',body); 116 | return ''; 117 | }; 118 | /* 推送消息方法 */ 119 | wechatFerry.push = async function (replyInfo) { 120 | return this.reply(replyInfo); 121 | }; 122 | // 伪装消息 123 | wechatFerry.inlinemask = async function (msgInfo) { 124 | return wechatFerry.receive(msgInfo); 125 | }; 126 | /* 发送消息请求体 */ 127 | async function requestFerry(body) { 128 | return ( 129 | await request({ 130 | url: wechatFerryUrl + body.api, 131 | method: 'post', 132 | body: body, 133 | json: true, 134 | }) 135 | ).body; 136 | } 137 | 138 | /* 获取windows文件路径 */ 139 | async function getLocalPath(url, type) { 140 | let req = { url: url, type: type } 141 | console.log("开始下载:",type,"文件:",url) 142 | return ( 143 | await request({ 144 | url: `${fileServer}download`, 145 | method: 'post', 146 | body: req, 147 | json: true, 148 | }) 149 | ).body.path; 150 | } 151 | return wechatFerry; 152 | }; 153 | -------------------------------------------------------------------------------- /Adapter/wechaty.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the App project. 3 | * @author 小寒寒 4 | * @name wechaty 5 | * @team Bncr团队 6 | * @version 1.0.5 7 | * @description wx机器人内置适配器,微信需要实名 8 | * @adapter true 9 | * @public false 10 | * @disable false 11 | * @priority 100 12 | * @Copyright ©2024 Aming and Anmours. All rights reserved 13 | * Unauthorized copying of this file, via any medium is strictly prohibited 14 | */ 15 | 16 | /* 配置构造器 */ 17 | const jsonSchema = BncrCreateSchema.object({ 18 | basic: BncrCreateSchema.object({ 19 | enable: BncrCreateSchema.boolean().setTitle('是否开启适配器').setDescription(`设置为关则不加载该适配器`).setDefault(false), 20 | name: BncrCreateSchema.string().setTitle('机器人标识').setDescription(`设置后后续自动登录,更换微信时请更换标识`).setDefault('wechaty') 21 | }).setTitle('基本设置').setDefault({}), 22 | friend: BncrCreateSchema.object({ 23 | accept: BncrCreateSchema.boolean().setTitle('自动同意好友申请').setDescription(`设置后自动同意微信好友申请`).setDefault(true), 24 | hello: BncrCreateSchema.string().setTitle('好友验证消息').setDescription(`设置后需要验证消息后才会自动同意好友`).setDefault(''), 25 | autoReply: BncrCreateSchema.string().setTitle('通过好友后自动发送的消息').setDescription(`留空则不回复`).setDefault(''), 26 | }).setTitle('好友相关').setDefault({}), 27 | room: BncrCreateSchema.object({ 28 | joinList: BncrCreateSchema.string().setTitle('进群监控').setDescription(`当有人进群后触发消息监控的群,多个用,隔开`).setDefault(""), 29 | joinTips: BncrCreateSchema.string().setTitle('进群提示').setDescription(`当有人进群后触发消息`).setDefault("欢迎加入大家庭~") 30 | }).setTitle('群聊相关').setDefault({}) 31 | }); 32 | 33 | /* 配置管理器 */ 34 | const ConfigDB = new BncrPluginConfig(jsonSchema); 35 | module.exports = async () => { 36 | /* 读取用户配置 */ 37 | await ConfigDB.get(); 38 | /* 如果用户未配置,userConfig则为空对象{} */ 39 | if (!Object.keys(ConfigDB.userConfig).length) { 40 | sysMethod.startOutLogs('未配置wechaty适配器,退出.'); 41 | return; 42 | } 43 | if (!ConfigDB.userConfig.basic?.enable) return sysMethod.startOutLogs('未启用wechaty 退出.'); 44 | const robotName = ConfigDB.userConfig.basic.name || 'wechaty'; 45 | const accept = ConfigDB.userConfig.friend.accept; 46 | const hello = ConfigDB.userConfig.friend.hello || ''; 47 | const autoReply = ConfigDB.userConfig.friend.autoReply || ''; 48 | 49 | const joinList = ConfigDB.userConfig.room.joinList?.split(",") || []; 50 | const joinTips = ConfigDB.userConfig.room.joinTips || '欢迎加入大家庭~'; 51 | 52 | /** 定时器 */ 53 | let timeoutID = setTimeout(() => { 54 | throw new Error('wechaty登录超时,放弃加载该适配器'); 55 | }, 2 * 1000 * 60); 56 | let wx = new Adapter('wechaty'); 57 | /* 补全依赖 */ 58 | await sysMethod.testModule(['wechaty', 'wechaty-plugin-contrib'], { install: true }); 59 | const { WechatyBuilder, types, log } = require('wechaty'); 60 | log.level('error') 61 | const { QRCodeTerminal } = require('wechaty-plugin-contrib'); 62 | const { FileBox } = require('file-box') 63 | const bot = WechatyBuilder.build({ 64 | name: robotName 65 | }); 66 | 67 | // /* 注入发送消息方法 */ 68 | wx.reply = async function (replyInfo, sendRes = '') { 69 | try { 70 | const contact = await bot.Contact.find({ name: Buffer.from(replyInfo.userId, 'hex').toString('utf-8') }); 71 | const room = replyInfo.groupId != "0" ? await bot.Room.find({ topic: Buffer.from(replyInfo.groupId, 'hex').toString('utf-8') }) : null; 72 | if (replyInfo.type === 'text') { 73 | if (room) { 74 | sendRes = contact ? await room.say("\n" + replyInfo.msg, contact) : await room.say(replyInfo.msg) 75 | } 76 | else { 77 | sendRes = await contact.say(replyInfo.msg); 78 | } 79 | } 80 | else if (['image', 'video'].includes(replyInfo.type)) { 81 | const file = FileBox.fromUrl(replyInfo.path); 82 | file['_name'] += replyInfo.type == 'image' ? '.png' : '.mp4'; 83 | sendRes = room ? await room.say(file) : await contact.say(file); 84 | } 85 | return sendRes ? sendRes.id : '0'; 86 | } catch (e) { 87 | log.error('wechaty:发送消息失败', e); 88 | } 89 | }; 90 | wx.push = async function (replyInfo, send = '') { 91 | return this.reply(replyInfo); 92 | }; 93 | 94 | bot.on('scan', (qrcode, status) => { 95 | sysMethod.startOutLogs(`wechaty: 正在登录,${status}\nhttps://wechaty.js.org/qrcode/${encodeURIComponent(qrcode)}`); 96 | }); 97 | 98 | bot.on('login', (user) => { 99 | sysMethod.startOutLogs(`wechaty:${user} 登录成功`); 100 | }); 101 | 102 | bot.on('logout', (user) => { 103 | sysMethod.startOutLogs(`wechaty:${user} 下线了`); 104 | }); 105 | 106 | bot.on("room-join", async (room, inviteeList, invite) => { 107 | log.warn(`wechaty:收到群员进群事件`); 108 | await room.sync(); 109 | const topic = await room.topic(); 110 | const roomId = Buffer.from(topic, 'utf-8').toString('hex'); 111 | if (joinList.includes(roomId) && joinTips) { 112 | await room.say(joinTips); 113 | } 114 | }); 115 | 116 | // 邀请进群 117 | bot.on("room-invite", async (roomInvitation) => { 118 | log.warn(`wechaty:收到邀请机器人进群事件`); 119 | }) 120 | 121 | bot.on("room-topic", async (room, newTopic, oldTopic, changer) => { 122 | log.warn(`wechaty:收到群聊名称修改事件`); 123 | await room.sync(); 124 | //todo:同步修改监听或回复的群id 125 | }); 126 | 127 | bot.on('friendship', async friendship => { 128 | log.warn("wechaty:收到微信好友申请事件"); 129 | try { 130 | if (friendship.type() === types.Friendship.Receive && (friendship.hello() === hello || hello == "") && accept) { 131 | await friendship.accept(); 132 | const contact = friendship.contact(); 133 | await contact.sync(); 134 | await new Promise((resolve) => setTimeout(resolve, 1000)); 135 | autoReply && await contact.say(autoReply); 136 | } 137 | } 138 | catch (e) { } 139 | }); 140 | 141 | // 心跳,防止掉线 142 | bot.on('heartbeat', async data => { 143 | try { 144 | const contact = await bot.Contact.find({ name: "文件传输助手" }); 145 | await contact.say("[爱心]") 146 | } 147 | catch (e) { } 148 | }); 149 | 150 | bot.on('error', async (error) => { 151 | if (error.message.includes('Network error')) { 152 | sysMethod.startOutLogs('wechaty:网络连接错误,尝试重启'); 153 | await bot.stop(); 154 | await new Promise((resolve) => setTimeout(resolve, 5000)); 155 | await bot.start(); 156 | } 157 | }); 158 | 159 | bot.on('message', async message => { 160 | try { 161 | const contact = message.talker(); 162 | // const type = message.type(); 163 | if (contact.self()) return; // 屏蔽自己的消息或非文本消息 164 | const room = message.room(); 165 | let topic = '' 166 | if (room) { 167 | !room.payload.topic && await room.sync(); 168 | topic = await room.topic(); 169 | } 170 | const msg = message.text(); 171 | const name = contact.name(); 172 | let msgInfo = { 173 | userId: Buffer.from(name, 'utf-8').toString('hex') || '', 174 | userName: name || '', 175 | groupId: topic ? Buffer.from(topic, 'utf-8').toString('hex') : '0', 176 | groupName: topic, 177 | msg: msg || '', 178 | msgId: message.payload.id || '', 179 | }; 180 | // console.log(msgInfo); 181 | wx.receive(msgInfo); 182 | } catch (e) { 183 | log.error('wechaty接收器报错:', e); 184 | } 185 | }); 186 | 187 | bot.use(QRCodeTerminal({ small: true })) 188 | bot.start().catch(e => console.log(e)); 189 | 190 | clearTimeout(timeoutID); 191 | return wx; 192 | }; 193 | -------------------------------------------------------------------------------- /Adapter/wxKeAImao.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the Bncr project. 3 | * @author Aming 4 | * @name wxKeAImao 5 | * @team Bncr团队 6 | * @version 1.0.3 7 | * @description wxKeAImao适配器 8 | * @adapter true 9 | * @public true 10 | * @disable false 11 | * @priority 2 12 | * @classification ["官方适配器"] 13 | * @Copyright ©2023 Aming and Anmours. All rights reserved 14 | * Unauthorized copying of this file, via any medium is strictly prohibited 15 | */ 16 | /* 配置构造器 */ 17 | const jsonSchema = BncrCreateSchema.object({ 18 | enable: BncrCreateSchema.boolean().setTitle('是否开启适配器').setDescription(`设置为关则不加载该适配器`).setDefault(false), 19 | sendUrl: BncrCreateSchema.string().setTitle('上报地址').setDescription(`无界收到消息要发送到的url`).setDefault(''), 20 | }); 21 | /* 配置管理器 */ 22 | const ConfigDB = new BncrPluginConfig(jsonSchema); 23 | 24 | module.exports = async () => { 25 | /* 读取用户配置 */ 26 | await ConfigDB.get(); 27 | /* 如果用户未配置,userConfig则为空对象{} */ 28 | if (!Object.keys(ConfigDB.userConfig).length) { 29 | sysMethod.startOutLogs('未配置wxKeAImao适配器,退出.'); 30 | return; 31 | } 32 | 33 | if (!ConfigDB.userConfig.enable) return sysMethod.startOutLogs('未启用KeAImao 退出.'); 34 | let keaimaoUrl = ConfigDB.userConfig.sendUrl; 35 | if (!keaimaoUrl) return console.log('可爱猫:配置文件未设置sendUrl'); 36 | const { randomUUID } = require('crypto'); 37 | //这里new的名字将来会作为 sender.getFrom() 的返回值 38 | const wxKeAImao = new Adapter('wxKeAImao'); 39 | // 包装原生require 你也可以使用其他任何请求工具 例如axios 40 | const request = require('util').promisify(require('request')); 41 | // wx数据库 42 | const wxDB = new BncrDB('wx'); 43 | let token = await wxDB.get('keaimao_token', ''); //自动设置,无需更改 44 | let botId = await wxDB.get('keaimao_botid', ''); //自动设置,无需更改 45 | /**向/api/系统路由中添加路由 */ 46 | router.get('/api/bot/KeAImao', (req, res) => res.send({ msg: '这是Bncr KeAImao Api接口,你的get请求测试正常~,请用post交互数据' })); 47 | router.post('/api/bot/KeAImao', async (req, res) => { 48 | try { 49 | const body = req.body; 50 | /* 拒收自己消息 */ 51 | if (body.final_from_wxid === body.robot_wxid) return `拒收该消息:${body.msg}`; 52 | /* 读取设置鉴权token */ 53 | if (req.header('Authorization') && token !== req.header('Authorization')) { 54 | /** 同步设置数据,成功返回true,否则false 55 | * 不加await为异步,不会等待设置数据库返回数据 */ 56 | await wxDB.set('keaimao_token', req.header('Authorization')); 57 | token = req.header('Authorization'); 58 | } 59 | if (botId !== body.robot_wxid) { 60 | /* 另一中set数据库操作,第三个值必须为一个对象,传入def字段时,设置成功返回def设置的值*/ 61 | botId = await wxDB.set('keaimao_botid', body.robot_wxid, { def: body.robot_wxid }); 62 | } 63 | 64 | // console.log('存储的鉴权头:', token); 65 | // console.log('请求的鉴权头:', req.header('Authorization')); 66 | // console.log('消息类型:', body.type); 67 | /* 群消息数据: 68 | { 69 | event: 'EventGroupMsg', 70 | robot_wxid: 'wxid_9pd', 71 | robot_name: 'test bot', 72 | type: 1, 73 | from_wxid: '195324@chatroom', 74 | from_name: '', 75 | final_from_wxid: '98756488854564', 76 | final_from_name: 'aming', 77 | to_wxid: 'wxid_9pd', 78 | msgid: '3764511392241821329', 79 | msg: '123' 80 | } */ 81 | /* 私聊消息数据 */ 82 | /* { 83 | event: 'EventFriendMsg', 84 | robot_wxid: 'wxid_9pd', 85 | robot_name: 'test bot', 86 | type: 1, 87 | from_wxid: '98756488854564', 88 | from_name: 'aming', 89 | final_from_wxid: '98756488854564', 90 | final_from_name: 'aming', 91 | to_wxid: 'wxid_9pd', 92 | msgid: '2935941486483612223', 93 | msg: 'time', 94 | }; */ 95 | 96 | /** 97 | * 1/文本消息 3/图片消息 34/语音消息 42/名片消息 43/视频 47/动态表情 48/地理位置 98 | * 49/分享链接 2000/转账 2001/红包 2002/小程序 2003/群邀请 99 | * 可以根据自己需求定制功能 100 | */ 101 | if ([1, 49, 2002].indexOf(body.type) === -1) return `拒收该消息:${body.msg}`; 102 | const msgInfo = { 103 | userId: body.final_from_wxid || '', 104 | userName: body.final_from_name || '', 105 | groupId: body.event === 'EventGroupMsg' ? body.from_wxid.replace('@chatroom', '') : '0', 106 | groupName: body.event === 'EventGroupMsg' ? body.from_name : '', 107 | msg: body.msg || '', 108 | msgId: body.msgid || '', 109 | fromType: `Social`, 110 | }; 111 | // console.log('msgInfo:', msgInfo); 112 | wxKeAImao.receive(msgInfo); 113 | res.send({ status: 200, data: '', msg: 'ok' }); 114 | } catch (e) { 115 | console.error('可爱猫消息接收器错误:', e); 116 | res.send({ status: 400, data: '', msg: e.toString() }); 117 | } 118 | }); 119 | 120 | wxKeAImao.reply = async function (replyInfo) { 121 | // console.log('replyInfo', replyInfo); 122 | let body = null; 123 | const to_Wxid = +replyInfo.groupId ? replyInfo.groupId + '@chatroom' : replyInfo.userId; 124 | switch (replyInfo.type) { 125 | case 'text': 126 | body = { 127 | event: 'SendTextMsg', 128 | to_wxid: to_Wxid, 129 | msg: replyInfo.msg, 130 | token: token, 131 | robot_wxid: botId, 132 | }; 133 | break; 134 | case 'image': 135 | body = { 136 | event: 'SendImageMsg', 137 | to_wxid: to_Wxid, 138 | msg: { 139 | // 根据可爱猫api,需要传一个唯一值过去,否则会根据该名在可爱猫服务器下找该文件名发送,找不到才会下载传过去的url图片发送 140 | // 这里我使用的是uuid,你可以调整成其他随机值 141 | name: randomUUID().split('-').join(''), 142 | url: replyInfo.path, 143 | }, 144 | token: token, 145 | robot_wxid: botId, 146 | }; 147 | break; 148 | case 'video': 149 | body = { 150 | event: 'SendVideoMsg', 151 | to_wxid: to_Wxid, 152 | msg: { 153 | // 根据可爱猫api,需要传一个唯一值过去,否则会根据该名在可爱猫服务器下找该文件名发送,找不到才会下载传过去的url图片发送 154 | // 这里我使用的是uuid,你可以调整成其他随机值 155 | name: randomUUID().split('-').join(''), 156 | url: replyInfo.path, 157 | }, 158 | token: token, 159 | robot_wxid: botId, 160 | }; 161 | break; 162 | default: 163 | return; 164 | break; 165 | } 166 | 167 | body && (await requestKeAImao(body)); 168 | // console.log('body', body); 169 | /* 请求体 */ 170 | async function requestKeAImao(body) { 171 | return ( 172 | await request({ 173 | url: keaimaoUrl, 174 | method: 'post', 175 | headers: { Authorization: token }, 176 | body: JSON.stringify(body), 177 | }) 178 | ).body; 179 | } 180 | return ''; //reply中的return 最终会返回到调用者 wx没有撤回方法,所以没有必要返回东西 181 | }; 182 | /* 推送消息方法 */ 183 | wxKeAImao.push = async function (replyInfo) { 184 | return this.reply(replyInfo); 185 | }; 186 | /* wx无法撤回消息 为空 */ 187 | wxKeAImao.delMsg = () => {}; 188 | return wxKeAImao; 189 | }; 190 | -------------------------------------------------------------------------------- /Adapter/wxQianxun.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the Bncr project. 3 | * @author Aming 4 | * @name wxQianxun 5 | * @team Bncr团队 6 | * @version 1.0.3 7 | * @description wxQianxun适配器 8 | * @adapter true 9 | * @public true 10 | * @disable false 11 | * @priority 2 12 | * @Copyright ©2023 Aming and Anmours. All rights reserved 13 | * @classification ["官方适配器"] 14 | * Unauthorized copying of this file, via any medium is strictly prohibited 15 | * Modified by Merrick 16 | */ 17 | 18 | /* 更新日志: 19 | v1.0.2 增加了自动同意添加好友和拉群功能,需要插件配合,插件下载地址:https://github.com/Merrickk/Bncr_plugins 20 | v1.0.3 适配3.0 21 | */ 22 | 23 | /* 配置构造器 */ 24 | const jsonSchema = BncrCreateSchema.object({ 25 | enable: BncrCreateSchema.boolean().setTitle('是否开启适配器').setDescription(`设置为关则不加载该适配器`).setDefault(false), 26 | sendUrl: BncrCreateSchema.string().setTitle('上报地址').setDescription(`无界收到消息要发送到的url`).setDefault(''), 27 | }); 28 | /* 配置管理器 */ 29 | const ConfigDB = new BncrPluginConfig(jsonSchema); 30 | module.exports = async () => { 31 | /* 读取用户配置 */ 32 | await ConfigDB.get(); 33 | /* 如果用户未配置,userConfig则为空对象{} */ 34 | if (!Object.keys(ConfigDB.userConfig).length) { 35 | sysMethod.startOutLogs('未启用Qianxun适配器,退出.'); 36 | return; 37 | } 38 | if (!ConfigDB.userConfig.enable) return sysMethod.startOutLogs('未启用Qianxun 退出.'); 39 | let QianxunUrl = ConfigDB.userConfig.sendUrl; 40 | if (!QianxunUrl) return console.log('可爱猫:配置文件未设置sendUrl'); 41 | const { randomUUID } = require('crypto'); 42 | //这里new的名字将来会作为 sender.getFrom() 的返回值 43 | const wxQianxun = new Adapter('wxQianxun'); 44 | // 包装原生require 你也可以使用其他任何请求工具 例如axios 45 | wxQianxun.Bridge = {}; 46 | const request = require('util').promisify(require('request')); 47 | // wx数据库 48 | const wxDB = new BncrDB('wxQianxun'); 49 | let botId = await wxDB.get('qianxun_botid', ''); //自动设置,无需更改 50 | /**向/api/系统路由中添加路由 */ 51 | router.get('/api/bot/Qianxun', (req, res) => res.send({ msg: '这是Bncr Qianxun Api接口,你的get请求测试正常~,请用post交互数据' })); 52 | router.post('/api/bot/Qianxun', async (req, res) => { 53 | try { 54 | const body = req.body; 55 | if (botId !== body.wxid) 56 | /* 另一种set数据库操作,第三个值必须为一个对象,传入def字段时,设置成功返回def设置的值*/ 57 | botId = await wxDB.set('qianxun_botid', body.wxid, { def: body.wxid }); 58 | // console.log('消息类型:', body.data.data.msgType); 59 | 60 | /** 61 | * 消息类型:1|文本 3|图片 34|语音 42|名片 43|视频 47| 62 | * 动态表情 48|地理位置 49|分享链接或附件 2001| 63 | * 红包 2002|小程序 2003|群邀请 10000|系统消息 64 | */ 65 | // if (body.data.data.msgType !== 1) return `拒收该消息:${body.msg}`; 66 | let msgInfo = null; 67 | //私聊 68 | if (body.event === 10009 && body.data.data.fromType === 1) { 69 | msgInfo = { 70 | userId: body.data.data.fromWxid || '', 71 | userName: '', 72 | groupId: '0', 73 | groupName: '', 74 | msg: body.data.data.msg || '', 75 | msgId: body.data.data.msgBase64 || '', 76 | fromType: `Social`, 77 | }; 78 | //群 79 | } else if (body.event === 10008 && body.data.data.fromType === 2) { 80 | msgInfo = { 81 | userId: body.data.data.finalFromWxid || '', 82 | userName: '', 83 | groupId: body.data.data.fromWxid.replace('@chatroom', '') || '0', 84 | groupName: '', 85 | msg: body.data.data.msg || '', 86 | msgId: body.data.data.msgBase64 || '', 87 | fromType: `Social`, 88 | }; 89 | // 自动同意好友请求 90 | } else if (body.event === 10011) { 91 | wxQianxun.Bridge.body = body; 92 | msgInfo = { 93 | userId: 'EventFriendVerify', 94 | userName: '好友申请通知', 95 | groupId: '0', 96 | groupName: '', 97 | msg: '收到千寻好友添加请求', 98 | msgId: '', 99 | fromType: 'Friend', 100 | }; 101 | } 102 | msgInfo && wxQianxun.receive(msgInfo); 103 | res.send({ status: 200, data: '', msg: 'ok' }); 104 | } catch (e) { 105 | console.error('千寻消息接收器错误:', e); 106 | res.send({ status: 400, data: '', msg: e.toString() }); 107 | } 108 | }); 109 | 110 | wxQianxun.reply = async function (replyInfo) { 111 | // console.log('replyInfo', replyInfo); 112 | let body = null; 113 | const to_Wxid = +replyInfo.groupId ? replyInfo.groupId + '@chatroom' : replyInfo.userId; 114 | switch (replyInfo.type) { 115 | case 'text': 116 | replyInfo.msg = replyInfo.msg.replace(/\n/g, '\r'); 117 | body = { 118 | type: 'Q0001', 119 | data: { 120 | wxid: to_Wxid, 121 | msg: replyInfo.msg, 122 | }, 123 | }; 124 | break; 125 | case 'image': 126 | body = { 127 | type: 'Q0010', 128 | data: { 129 | wxid: to_Wxid, 130 | path: replyInfo.path, 131 | }, 132 | }; 133 | break; 134 | // 自动同意好友请求 135 | case 'friend': 136 | body = { 137 | type: 'Q0017', 138 | data: { 139 | scene: '6', 140 | v3: replyInfo.v3, 141 | v4: replyInfo.v4 142 | }, 143 | }; 144 | break; 145 | // 拉群 146 | case 'group': 147 | body = { 148 | type: 'Q0021', 149 | data: { 150 | wxid: replyInfo.wxid, 151 | objWxid: replyInfo.objWxid, 152 | type: replyInfo.add_type 153 | }, 154 | }; 155 | break; 156 | default: 157 | return; 158 | break; 159 | } 160 | body && (await requestQianxun(body)); 161 | // console.log('body', body); 162 | return ''; //reply中的return 最终会返回到调用者 wx没有撤回方法,所以没有必要返回东西 163 | }; 164 | /* 推送消息方法 */ 165 | wxQianxun.push = async function (replyInfo) { 166 | return this.reply(replyInfo); 167 | }; 168 | /* wx无法撤回消息 为空 */ 169 | wxQianxun.delMsg = () => {}; 170 | /* 发送消息请求体 */ 171 | async function requestQianxun(body) { 172 | return ( 173 | await request({ 174 | url: `${QianxunUrl}/DaenWxHook/httpapi/?wxid=${botId}`, 175 | method: 'post', 176 | body: body, 177 | json: true, 178 | }) 179 | ).body; 180 | } 181 | return wxQianxun; 182 | }; -------------------------------------------------------------------------------- /Adapter/wxWork.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Merrick 3 | * @name wxWork 4 | * @version 1.0.1 5 | * @description 企业微信适配器 6 | * @adapter true 7 | * @public true 8 | * @disable false 9 | * @priority 2 10 | * @Copyright ©2023 Merrick. All rights reserved 11 | */ 12 | 13 | /* 14 | v1.0.1 修复bug,适配无界2.0 WEB界面,增加markdown、voice格式支持,在部分节点增加log输出,方便排查 15 | */ 16 | 17 | 18 | /* 配置构造器 */ 19 | const jsonSchema = BncrCreateSchema.object({ 20 | enable: BncrCreateSchema.boolean().setTitle('是否开启适配器').setDescription(`设置为关则不加载该适配器`).setDefault(false), 21 | corpId: BncrCreateSchema.string().setTitle('corpId').setDescription(`请填入“我的企业-企业信息”页面获取的企业ID`).setDefault(''), 22 | corpSecret: BncrCreateSchema.string().setTitle('Secret').setDescription(`请填入“自建应用”页面获取的Secret`).setDefault(''), 23 | encodingAESKey: BncrCreateSchema.string().setTitle('encodingAESKey').setDescription(`请填入“自建应用-接收消息服务器配置”页面获取的的encodingAESKey`).setDefault('') 24 | }); 25 | /* 配置管理器 */ 26 | const ConfigDB = new BncrPluginConfig(jsonSchema); 27 | const got = require('got'); 28 | const { decrypt } = require('@wecom/crypto'); 29 | const xmlParse = require('xml2js').parseString; 30 | const FormData = require('form-data'); 31 | const xmlparser = require('express-xml-bodyparser'); 32 | 33 | module.exports = async () => { 34 | /* 读取用户配置 */ 35 | await ConfigDB.get(); 36 | /* 如果用户未配置,userConfig则为空对象{} */ 37 | if (!Object.keys(ConfigDB.userConfig).length) return sysMethod.startOutLogs('未配置wxWork适配器,退出'); 38 | if (!ConfigDB.userConfig.enable) return sysMethod.startOutLogs('未启用wxWork适配器,退出'); 39 | const encodingAESKey = ConfigDB.userConfig.encodingAESKey; 40 | if (!encodingAESKey) return console.log('未设置encodingAESKey'); 41 | const corpId = ConfigDB.userConfig.corpId; 42 | if (!corpId) return console.log('未设置corpId'); 43 | const corpSecret = ConfigDB.userConfig.corpSecret; 44 | if (!corpSecret) return console.log('未设置Secret'); 45 | //这里new的名字将来会作为 sender.getFrom() 的返回值 46 | const wxWork = new Adapter('wxWork'); 47 | const wxDB = new BncrDB('wxWork'); 48 | let botId = await wxDB.get('wxWorkBotId', ''); //自动设置,无需更改 49 | agentId = await wxDB.get('wxWorkAgentId', ''); 50 | 51 | /**向/api/系统路由中添加路由 */ 52 | router.use(xmlparser()); 53 | router.get('/api/bot/wxWork', (req, res) => { 54 | try { 55 | const params = req.query; 56 | const { message } = decrypt(encodingAESKey, params.echostr); 57 | return res.send(message); 58 | } catch (e) { 59 | console.error('对接模块出错', e); 60 | res.send({ msg: '这是Bncr wxWork Api接口,你的get请求测试正常~,请用post交互数据' }); 61 | } 62 | }); 63 | 64 | router.post('/api/bot/wxWork', async (req, res) => { 65 | try { 66 | const body = req.body.xml, 67 | botID = body.tousername[0], 68 | xmlMsg = decrypt(encodingAESKey, body.encrypt[0]); 69 | let msgJson = {}; 70 | xmlParse (xmlMsg.message, function (err, result) { 71 | msgJson= result.xml; 72 | }); 73 | // console.log(msgJson); 74 | let { 75 | Content: [ msgContent ], 76 | MsgId: [ msgId ], 77 | MsgType: [ msgType ], 78 | FromUserName: [ usrId ], 79 | AgentID: [ agentID ] 80 | } = msgJson; 81 | if (botId !== botID) await wxDB.set('wxWorkBotId', botID); 82 | if (agentId !== agentID) await wxDB.set('wxWorkAgentId', agentID); 83 | console.log(`收到 ${usrId} 发送的企业微信消息 ${msgJson['Content']}`); 84 | if (msgType !== 'text') return; 85 | let msgInfo; 86 | if (msgType === 'text') { 87 | msgInfo = { 88 | userId: usrId || '', 89 | userName: '', 90 | groupId: '0', 91 | groupName: '', 92 | msg: msgContent || '', 93 | msgId: msgId || '', 94 | fromType: `Social`, 95 | }; 96 | } 97 | msgInfo && wxWork.receive(msgInfo); 98 | res.send(''); 99 | } catch (e) { 100 | console.error('接收消息模块出错', e); 101 | res.send(''); 102 | } 103 | }); 104 | 105 | wxWork.reply = async function (replyInfo) { 106 | try { 107 | let body, mediaId; 108 | const toUser = replyInfo.userId; 109 | agentId = await wxDB.get('wxWorkAgentId', ''); 110 | // console.log(replyInfo); 111 | switch (replyInfo.type) { 112 | case 'text': 113 | replyInfo.msg = replyInfo.msg.replace(/\n/g, '\r'); 114 | body = { 115 | "touser": toUser, 116 | "msgtype": "text", 117 | "agentid": agentId, 118 | "text": { 119 | "content": replyInfo.msg 120 | } 121 | }; 122 | break; 123 | case 'markdown': 124 | body = { 125 | "touser": toUser, 126 | "msgtype": "markdown", 127 | "agentid": agentId, 128 | "markdown": { 129 | "content": replyInfo.msg 130 | } 131 | }; 132 | break; 133 | case 'image': 134 | mediaId = await getMediaID(replyInfo.path, 'image'); 135 | body = { 136 | "touser": toUser, 137 | "msgtype": "image", 138 | "agentid": agentId, 139 | "image": { 140 | "media_id": mediaId 141 | } 142 | }; 143 | break; 144 | case 'video': 145 | mediaId = await getMediaID(replyInfo.path, 'video'); 146 | body = { 147 | "touser": toUser, 148 | "msgtype": "video", 149 | "agentid": agentId, 150 | "video": { 151 | "media_id": mediaId 152 | } 153 | }; 154 | break; 155 | case 'voice': 156 | mediaId = await getMediaID(replyInfo.path, 'voice'); 157 | body = { 158 | "touser": toUser, 159 | "msgtype": "voice", 160 | "agentid": agentId, 161 | "voice": { 162 | "media_id": mediaId 163 | } 164 | }; 165 | break; 166 | default: 167 | return; 168 | } 169 | if (body) { 170 | let msgId = await sendMsg(body); 171 | // console.log('返回的msgid', msgId); 172 | return msgId; //reply中的return 最终会返回到调用者 173 | } 174 | } catch (e) { 175 | console.error('回复消息模块出错', e); 176 | res.send(''); 177 | } 178 | } 179 | 180 | /* 推送消息方法 */ 181 | wxWork.push = async function (replyInfo) { 182 | return this.reply(replyInfo); 183 | } 184 | 185 | wxWork.delMsg = async function (msgId) { 186 | const accessToken = await getAccessToken(); 187 | try { 188 | const url = `https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_token=${accessToken}`; 189 | const body = { "msgid": msgId[1] }; 190 | if (msgId) await got.post({url, json:body}); 191 | return true; 192 | } catch (e) { 193 | console.error('撤回消息模块出错', e); 194 | return false; 195 | } 196 | } 197 | 198 | return wxWork; 199 | 200 | async function sendMsg(body) { 201 | const accessToken = await getAccessToken(); 202 | try { 203 | const url = `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${accessToken}`; 204 | const resJson = await got.post({url, json:body}).json(); 205 | if (resJson['errcode'] === 0) { 206 | return resJson.msgid; 207 | } else { 208 | console.log(`发送消息函数出错`, JSON.stringify(resJson)); 209 | } 210 | } catch (e) { 211 | console.error(`发送消息函数出错`, e); 212 | } 213 | } 214 | 215 | async function getMediaID(mediaPath, mediaType) { 216 | try { 217 | // 获取Token生成上传url 218 | const accessToken = await getAccessToken(); 219 | const url = `https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=${accessToken}&type=${mediaType}`; 220 | // 获取网络图片文件流并上传到微信服务器 221 | const response = await got.get(mediaPath, { responseType: 'buffer' }); 222 | const form = new FormData(); 223 | form.append('media', response.body, { filename: 'media' }); // 设置文件名 224 | const formData = form.getBuffer(); // 获取表单数据 225 | const formHeaders = form.getHeaders(); // 获取表单头部 226 | const options = { 227 | body: formData, 228 | headers: { 229 | ...formHeaders, 230 | 'Content-Length': formData.length // 必须设置 Content-Length 231 | }, 232 | responseType: 'json' // 响应类型为 JSON 233 | }; 234 | const resJson = await got.post(url, options); 235 | if (resJson.body.media_id) { 236 | return resJson.body.media_id; 237 | } else { 238 | console.log(`上传文件函数出错`, JSON.stringify(resJson.body)); 239 | } 240 | } catch (e) { 241 | console.error(`上传文件函数出错`, e); 242 | } 243 | } 244 | 245 | async function getAccessToken () { 246 | const wxTokenExp = await wxDB.get('wxTokenExp', ''); 247 | if (!wxTokenExp || wxTokenExp < Date.now()) { 248 | const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpId}&corpsecret=${corpSecret}`; 249 | try { 250 | const tkJson = await got.get(url).json(); 251 | if (tkJson['access_token']) { 252 | const expTime = Date.now() + (1.5 * 60 * 60 * 1000); 253 | await wxDB.set('wxWorkToken', tkJson['access_token']); 254 | await wxDB.set('wxTokenExp', expTime); 255 | return tkJson.access_token; 256 | } else { 257 | console.log(`获取Token函数出错`, JSON.stringify(tkJson)); 258 | } 259 | } catch (e) { 260 | console.error(`获取Token函数出错`,e); 261 | } 262 | } else { 263 | const accessToken = await wxDB.get('wxWorkToken', ''); 264 | return accessToken 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /Adapter/wxWorkKF.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Merrick 3 | * @name wxWorkKF 4 | * @version 1.0.2 5 | * @description 微信客服适配器 6 | * @adapter true 7 | * @public true 8 | * @disable false 9 | * @priority 2 10 | * @Copyright ©2023 Merrick. All rights reserved 11 | */ 12 | 13 | /* 14 | v1.0.1 更换API为微信客服独立版,适配无界2.0 WEB界面 15 | v1.0.2 修复bug,增加markdown、voice格式支持,在部分节点增加log输出,方便排查 16 | */ 17 | 18 | /* 配置构造器 */ 19 | const jsonSchema = BncrCreateSchema.object({ 20 | enable: BncrCreateSchema.boolean().setTitle('是否开启适配器').setDescription(`设置为关则不加载该适配器`).setDefault(false), 21 | corpId: BncrCreateSchema.string().setTitle('企业ID').setDescription(`请填入“微信客服-开发配置”页面获取的企业ID`).setDefault(''), 22 | corpSecret: BncrCreateSchema.string().setTitle('Secret').setDescription(`请填入“微信客服-开发配置”页面获取的Secret`).setDefault(''), 23 | encodingAESKey: BncrCreateSchema.string().setTitle('encodingAESKey').setDescription(`请填入“微信客服-开发配置-回调配置”页面获取的的encodingAESKey`).setDefault('') 24 | }); 25 | /* 配置管理器 */ 26 | const ConfigDB = new BncrPluginConfig(jsonSchema); 27 | const got = require('got'); 28 | const { decrypt } = require('@wecom/crypto'); 29 | const xmlParse = require('xml2js').parseString; 30 | const FormData = require('form-data'); 31 | const xmlparser = require('express-xml-bodyparser'); 32 | 33 | module.exports = async () => { 34 | /* 读取用户配置 */ 35 | await ConfigDB.get(); 36 | /* 如果用户未配置,userConfig则为空对象{} */ 37 | if (!Object.keys(ConfigDB.userConfig).length) return sysMethod.startOutLogs('未启用wxWorkKF适配器,退出'); 38 | if (!ConfigDB.userConfig.enable) return sysMethod.startOutLogs('未启用wxWorkKF适配器,退出'); 39 | const encodingAESKey = ConfigDB.userConfig.encodingAESKey; 40 | if (!encodingAESKey) return console.log('未设置encodingAESKey'); 41 | const corpId = ConfigDB.userConfig.corpId; 42 | if (!corpId) return console.log('未设置corpId'); 43 | const corpSecret = ConfigDB.userConfig.corpSecret; 44 | if (!corpSecret) return console.log('未设置Secret'); 45 | //这里new的名字将来会作为 sender.getFrom() 的返回值 46 | const wxWorkKF = new Adapter('wxWorkKF'); 47 | const wxDB = new BncrDB('wxWorkKF'); 48 | 49 | /**向/api/系统路由中添加路由 */ 50 | router.use(xmlparser()); 51 | router.get('/api/bot/wxWorkKF', (req, res) => { 52 | try { 53 | const params = req.query; 54 | const { message } = decrypt(encodingAESKey, params.echostr); 55 | return res.send(message); 56 | } catch (e) { 57 | console.error('对接模块出错', e); 58 | res.send({ msg: '这是Bncr wxWorkKF Api接口,你的get请求测试正常~,请用post交互数据' }); 59 | } 60 | }); 61 | 62 | router.post('/api/bot/wxWorkKF', async (req, res) => { 63 | try { 64 | // 接收到用户发送给客服的消息 65 | const body = req.body.xml; 66 | const xmlMsg = decrypt(encodingAESKey, body.encrypt[0]); 67 | const msgCursor = await wxDB.get('msgCursor', ''); 68 | const botId = await wxDB.get('wxWorkKFBotId', ''); //自动设置,无需更改 69 | let msgJson = {}; 70 | xmlParse (xmlMsg.message, function (err, result) { msgJson = result.xml }); 71 | if (msgJson) res.send(''); 72 | // 提取信息组成新的body发送给微信后台拉取消息的详细信息 73 | botID = msgJson.OpenKfId[0]; 74 | reqBody = { 75 | "cursor": msgCursor, 76 | "token": msgJson.Token[0], 77 | "limit": 1000, 78 | "voice_format": 0, 79 | "open_kfid": botID 80 | }; 81 | const resData = await getMsg(reqBody); 82 | if (!resData || resData.errcode !== 0) return console.log(resData); 83 | await wxDB.set('msgCursor', resData.next_cursor); 84 | const msgData = resData.msg_list.pop(); 85 | if (!msgData) return; 86 | if (msgData && msgData.msgtype === 'event') return; 87 | let msgContent, msgId, usrId; 88 | if (msgData.msgtype === 'text') { 89 | msgContent = msgData.text.content; 90 | msgId = msgData.msgid; 91 | usrId = msgData.external_userid; 92 | } 93 | if (botId !== botID) await wxDB.set('wxWorkKFBotId', botID); 94 | console.log(`收到 ${usrId} 发送的微信客服消息 ${msgContent}`); 95 | let msgInfo = { 96 | userId: usrId || '', 97 | userName: '', 98 | groupId: '0', 99 | groupName: '', 100 | msg: msgContent || '', 101 | msgId: msgId || '', 102 | fromType: `Social`, 103 | } 104 | msgInfo && wxWorkKF.receive(msgInfo); 105 | } catch (e) { 106 | console.error('接收消息模块出错', e); 107 | res.send(''); 108 | } 109 | }); 110 | 111 | wxWorkKF.reply = async function (replyInfo) { 112 | try { 113 | let body, mediaId; 114 | const botId = await wxDB.get('wxWorkKFBotId', ''); 115 | switch (replyInfo.type) { 116 | case 'text': 117 | body = { 118 | "touser": replyInfo.userId, 119 | "open_kfid": botId, 120 | "msgtype": "text", 121 | "text": { 122 | "content": replyInfo.msg 123 | } 124 | }; 125 | break; 126 | case 'image': 127 | mediaId = await getMediaID(replyInfo.path, 'image'); 128 | body = { 129 | "touser": replyInfo.userId, 130 | "msgtype": "image", 131 | "open_kfid": botId, 132 | "image": { 133 | "media_id": mediaId 134 | } 135 | }; 136 | break; 137 | case 'video': 138 | mediaId = await getMediaID(replyInfo.path, 'video'); 139 | body = { 140 | "touser": replyInfo.userId, 141 | "msgtype": "video", 142 | "open_kfid": botId, 143 | "video": { 144 | "media_id": mediaId 145 | } 146 | }; 147 | break; 148 | case 'voice': 149 | mediaId = await getMediaID(replyInfo.path, 'voice'); 150 | body = { 151 | "touser": replyInfo.userId, 152 | "msgtype": "voice", 153 | "open_kfid": botId, 154 | "voice": { 155 | "media_id": mediaId 156 | } 157 | }; 158 | break; 159 | default: 160 | return; 161 | } 162 | if (body) { 163 | let msgId = await sendMsg(body); 164 | // console.log(body, msgId); 165 | return msgId; //reply中的return 最终会返回到调用者 166 | } 167 | } catch (e) { 168 | console.error('回复消息模块出错', e); 169 | res.send(''); 170 | } 171 | } 172 | /* 推送消息方法 */ 173 | wxWorkKF.push = async function (replyInfo) { 174 | return this.reply(replyInfo); 175 | } 176 | 177 | /* 撤回消息方法 */ 178 | wxWorkKF.delMsg = async function (msgId) { 179 | try { 180 | await getAccessToken(); 181 | let accessToken = await wxDB.get('wxWorkKFToken', ''), 182 | url = `https://qyapi.weixin.qq.com/cgi-bin/kf/recall_msg?access_token=${accessToken}`, 183 | body = JSON.stringify({ "msgid": msgId[1], "open_kfid": botId}); 184 | if (msgId) await got.post({url, body:body}); 185 | return true; 186 | } catch (e) { 187 | console.error('撤回消息模块出错', e); 188 | return false; 189 | } 190 | } 191 | 192 | return wxWorkKF; 193 | 194 | async function getMsg(body) { 195 | const accessToken = await getAccessToken(); 196 | try { 197 | const url = `https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=${accessToken}`; 198 | let hasMore = 1, 199 | resJson = []; 200 | while (hasMore === 1) { 201 | resJson = await got.post({url, json:body}).json(); 202 | hasMore = resJson.has_more; 203 | } 204 | return resJson; 205 | } catch (e) { 206 | console.error(`获取消息函数出错`, JSON.stringify(resJson)); 207 | } 208 | 209 | } 210 | 211 | async function sendMsg(body) { 212 | const accessToken = await getAccessToken(); 213 | try { 214 | const url = `https://qyapi.weixin.qq.com/cgi-bin/kf/send_msg?access_token=${accessToken}`; 215 | const resJson = await got.post({url, json:body}).json(); 216 | if (resJson['errcode'] === 0) { 217 | return resJson.msgid; 218 | } else { 219 | console.log(`发送消息函数出错`, JSON.stringify(resJson)); 220 | } 221 | } catch (e) { 222 | console.error(`发送消息函数出错`, e); 223 | } 224 | } 225 | 226 | async function getMediaID(mediaPath, mediaType) { 227 | try { 228 | // 获取Token生成上传url 229 | const accessToken = await getAccessToken(); 230 | const url = `https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=${accessToken}&type=${mediaType}`; 231 | // 获取网络图片文件流并上传到微信服务器 232 | const response = await got.get(mediaPath, { responseType: 'buffer' }); 233 | const form = new FormData(); 234 | form.append('media', response.body, { filename: 'media' }); // 设置文件名 235 | const formData = form.getBuffer(); // 获取表单数据 236 | const formHeaders = form.getHeaders(); // 获取表单头部 237 | const options = { 238 | body: formData, 239 | headers: { 240 | ...formHeaders, 241 | 'Content-Length': formData.length // 必须设置 Content-Length 242 | }, 243 | responseType: 'json' // 响应类型为 JSON 244 | }; 245 | const resJson = await got.post(url, options); 246 | if (resJson.body.media_id) { 247 | return resJson.body.media_id; 248 | } else { 249 | console.log(`上传文件函数出错`, JSON.stringify(resJson.body)); 250 | } 251 | } catch (e) { 252 | console.error(`上传文件函数出错`, e); 253 | } 254 | } 255 | 256 | async function getAccessToken () { 257 | const wxTokenExp = await wxDB.get('wxTokenExp', ''); 258 | if (!wxTokenExp || wxTokenExp < Date.now()) { 259 | const url = `https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${corpId}&corpsecret=${corpSecret}`; 260 | try { 261 | const tkJson = await got.get(url).json(); 262 | if (tkJson['access_token']) { 263 | const expTime = Date.now() + (1.5 * 60 * 60 * 1000); 264 | await wxDB.set('wxWorkKFToken', tkJson['access_token']); 265 | await wxDB.set('wxTokenExp', expTime); 266 | return tkJson.access_token; 267 | } else { 268 | console.log(`获取Token函数出错`, JSON.stringify(tkJson)); 269 | } 270 | } catch (e) { 271 | console.error(`获取Token函数出错`, e); 272 | } 273 | } else { 274 | const accessToken = await wxDB.get('wxWorkKFToken', ''); 275 | return accessToken 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /Adapter/wxXyo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file is part of the Bncr project. 3 | * @author Anmour 4 | * @name wxXyo 5 | * @team Bncr团队 6 | * @version 1.0.2 7 | * @description wxXyo适配器 8 | * @adapter true 9 | * @public true 10 | * @disable false 11 | * @priority 2 12 | * @classification ["官方适配器"] 13 | * @Copyright ©2023 Aming and Anmours. All rights reserved 14 | * Unauthorized copying of this file, via any medium is strictly prohibited 15 | */ 16 | /* 配置构造器 */ 17 | const jsonSchema = BncrCreateSchema.object({ 18 | enable: BncrCreateSchema.boolean().setTitle('是否开启适配器').setDescription(`设置为关则不加载该适配器`).setDefault(false), 19 | sendUrl: BncrCreateSchema.string().setTitle('上报地址').setDescription(`无界收到消息要发送到的url`).setDefault(''), 20 | }); 21 | /* 配置管理器 */ 22 | const ConfigDB = new BncrPluginConfig(jsonSchema); 23 | 24 | module.exports = async () => { 25 | /* 读取用户配置 */ 26 | await ConfigDB.get(); 27 | /* 如果用户未配置,userConfig则为空对象{} */ 28 | if (!Object.keys(ConfigDB.userConfig).length) { 29 | sysMethod.startOutLogs('未启用Xyo适配器,退出.'); 30 | return; 31 | } 32 | if (!ConfigDB.userConfig.enable) return sysMethod.startOutLogs('未启用Xyo 退出.'); 33 | let XyoUrl = ConfigDB.userConfig.sendUrl; 34 | if (!XyoUrl) return console.log('Xyo:配置文件未设置sendUrl'); 35 | ///这里new的名字将来会作为 sender.getFrom() 的返回值 36 | const wxXyo = new Adapter('wxXyo'); 37 | // 包装原生require 你也可以使用其他任何请求工具 例如axios 38 | const request = require('util').promisify(require('request')); 39 | // wx数据库 40 | const wxDB = new BncrDB('wxXyo'); 41 | let botId = await wxDB.get('xyo_botid', ''); //自动设置,无需更改 42 | let token = await wxDB.get('xyo_token', ''); //set wxXyo xyo_token xxx 43 | /**向/api/系统路由中添加路由 */ 44 | router.get('/api/bot/Xyo', (req, res) => 45 | res.send({ msg: '这是Bncr Xyo Api接口,你的get请求测试正常~,请用post交互数据' }) 46 | ); 47 | router.post('/api/bot/Xyo', async (req, res) => { 48 | try { 49 | const body = req.body; 50 | if (body.content.from_wxid === body.content.robot_wxid) return res.send({ status: 400, data: '', msg: `拒收该消息:${body.msg}` }); 51 | // console.log(body); 52 | if (botId !== body.content.robot_wxid) 53 | /* 另一种set数据库操作,第三个值必须为一个对象,传入def字段时,设置成功返回def设置的值*/ 54 | botId = await wxDB.set('xyo_botid', body.content.robot_wxid, { def: body.content.robot_wxid }); 55 | 56 | /** 57 | * 消息类型:1|文本 3|图片 34|语音 42|名片 43|视频 47| 58 | * 动态表情 48|地理位置 49|分享链接或附件 2001| 59 | * 红包 2002|小程序 2003|群邀请 10000|系统消息 60 | */ 61 | if (body.content.type !== 1) return `拒收该消息:${body.msg}`; 62 | let msgInfo = null; 63 | //私聊 64 | 65 | if (body.Event === 'EventPrivateChat') { 66 | msgInfo = { 67 | userId: body.content.from_wxid || '', 68 | userName: body.content.from_name || '', 69 | groupId: '0', 70 | groupName: '', 71 | msg: body.content.msg || '', 72 | msgId: body.content.msg_id || '', 73 | fromType: `Social`, 74 | }; 75 | //群 76 | } else if (body.Event === 'EventGroupChat') { 77 | msgInfo = { 78 | userId: body.content.from_wxid || '', 79 | userName: body.content.from_name || '', 80 | groupId: body.content.from_group.replace('@chatroom', '') || '0', 81 | groupName: body.content.from_group_name || '', 82 | msg: body.content.msg || '', 83 | msgId: body.content.msg_id || '', 84 | fromType: `Social`, 85 | }; 86 | } 87 | msgInfo && wxXyo.receive(msgInfo); 88 | res.send({ status: 200, data: '', msg: 'ok' }); 89 | } catch (e) { 90 | console.error('wxXyo接收器错误:', e); 91 | res.send({ status: 400, data: '', msg: e.toString() }); 92 | } 93 | }); 94 | 95 | wxXyo.reply = async function (replyInfo) { 96 | // console.log('replyInfo', replyInfo); 97 | let body = null; 98 | if (!token) throw new Error('xyo发送消息失败,没有设置xyo token,发送set wxXyo xyo_token xxx设置'); 99 | const to_Wxid = +replyInfo.groupId ? replyInfo.groupId + '@chatroom' : replyInfo.userId; 100 | switch (replyInfo.type) { 101 | case 'text': 102 | replyInfo.msg = replyInfo.msg.replace(/\n/g, '\r'); 103 | body = { 104 | to_wxid: to_Wxid, 105 | msg: replyInfo.msg, 106 | api: "SendTextMsg", 107 | }; 108 | break; 109 | case 'image': 110 | body = { 111 | to_wxid: to_Wxid, 112 | path: replyInfo.path, 113 | api: "SendImageMsg", 114 | }; 115 | break; 116 | case 'video': 117 | body = { 118 | to_wxid: to_Wxid, 119 | path: replyInfo.path, 120 | api: "SendVideoMsg", 121 | }; 122 | break; 123 | case 'audio': 124 | body = { 125 | to_wxid: to_Wxid, 126 | title: replyInfo?.name || '', 127 | desc: replyInfo?.singer || '', 128 | url: replyInfo?.path || '', 129 | dataurl: replyInfo?.path || '', 130 | thumburl: replyInfo?.img || '', 131 | api: 'SendMusicLinkMsg' 132 | }; 133 | break; 134 | default: 135 | return; 136 | break; 137 | } 138 | 139 | body && await requestXyo(body); 140 | // console.log('body', ); 141 | return ''; 142 | }; 143 | /* 推送消息方法 */ 144 | wxXyo.push = async function (replyInfo) { 145 | return this.reply(replyInfo); 146 | }; 147 | /* wx无法撤回消息 为空 */ 148 | wxXyo.delMsg = () => { }; 149 | /* 发送消息请求体 */ 150 | async function requestXyo(body) { 151 | return ( 152 | await request({ 153 | url: XyoUrl, 154 | method: 'post', 155 | body: { 156 | ...body, ...{ 157 | token, robot_wxid: botId 158 | } 159 | }, 160 | json: true 161 | }) 162 | ).body; 163 | } 164 | return wxXyo; 165 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![6bb2a486b967a89bf6727173296f2ce4_720](https://github.com/user-attachments/assets/c2a16144-58d8-4caf-a33c-19a427cb029b) 2 | 3 | # [鑫仔TG](https://t.me/xinzfun) 4 | # [鑫仔博客](https://www.xinz.fun) 5 | # [咸鱼博客](http://blog.咸鱼.top) 6 | # 本库脚本一律不含非法行为 不涉及任何CK仓库 本仓库插件鑫仔和咸鱼大佬一起维护仅供娱乐使用 用户使用一切后果自负 7 |

无界 | Bncr |作者: Xinz & 咸鱼 8 | QQ群:849427892 9 |

10 |
11 | 12 | _ 13 |
[官方安装文档](/docs/md/init.md) 36 | * 开发 37 | > [官方开发文档](https://anmours.github.io/Bncr) 38 | * 如果是手机端浏览开发文档,请点击开发文档左下角的按钮手动打开侧边栏目录 39 | 40 | 无界项目官网:https://anmours.github.io/Bncr/#/ 41 | 无界tg群:https://t.me/BncrJSChat 42 | 无界插件商城订阅:https://github.com/seven-XINZ/bncr XINZ 43 | 44 | > **Bncr 是一个开箱即用的Nodejs Chat RoBot(会话式机器人)框架。它基于OOP函数响应式编程,具有占用小、响应快、开发易等特点,允许开发者创建高度可测试、可扩展、松散耦合且易于维护的应用程序。本项目架构深受Koishi与sillyGirl的启发;** 45 | # 特性 46 | * 多平台多账户接入系统 : 2个qq/3个wx/4个tg? so easy!; 47 | * 基于TypeScritp OOP函数响应式编程 :源码仅1.5M,占用小,响应快,开发易 ; 48 | * 极简的插件开发 : 系统高度封装,提供简便人性化的系统方法,随心所欲开发插件; 49 | * 异步同步执行自由控制 : 基于nodejs async/await/Promise特性,你可以自由控制异步同步(阻塞非阻塞运行); 50 | * 不仅仅是Chat RoBot : 原生支持npm/yarn,开发潜力无穷大,如果你愿意,可以在本框架上搭建网站、图片服务器、资源共享平台、并发请求等服务,在JavaScript上能做到的事情在这里都将被实现. 51 | 52 | ## 免责声明 53 | > 任何用户在使用由 xinz/xianyu(以下简称「本团队」)研发的 Nodejs (js脚本)代码(以下简称「xinz.js」)前,请您仔细阅读并透彻理解本声明。您可以选择不使用xinz.js,若您一旦使用xinz.js,您的使用行为即被视为对本声明全部内容的认可和接受。 54 | 1. 本代码内使用的部分包括但不限于字体、npm库等资源来源于互联网,如果出现侵权可联系本框架移除。 55 | 2. 由于使用本仓库脚本产生的包括由于本协议或由于使用或无法使用本仓库脚本而引起的任何性质的任何直接、间接、特殊、偶然或结果性损害(包括但不限于因商誉损失、停工、计算机故障或故障引起的损害赔偿,或任何及所有其他商业损害或损失)由使用者负责。 56 | 3. 您承诺秉着合法、合理的原则使用xinz.js,不利用js进行任何违法、侵害他人合法利益等恶意的行为,亦不将js运用于任何违反我国法律法规的 Web 平台。 57 | 4. 任何单位或个人因下载使用本仓库js而产生的任何意外、疏忽、合约毁坏、诽谤、版权或知识产权侵犯及其造成的损失 (包括但不限于直接、间接、附带或衍生的损失等),本团队不承担任何法律责任。 58 | 5. 用户明确并同意本声明条款列举的全部内容,对使用本仓库js可能存在的风险和相关后果将完全由用户自行承担,本团队不承担任何法律责任。 59 | 6. 任何单位或个人在阅读本免责声明后,应在法律所允许的范围内进行合法的发布、传播和使用xinz.js等行为,若违反本免责声明条款或违反法律法规所造成的法律责任(包括但不限于民事赔偿和刑事责任),由违约者自行承担。 60 | 7. 传播:任何公司或个人在网络上发布,传播我们仓库js的行为都是允许的,但因公司或个人传播框架可能造成的任何法律和刑事事件框架作者不负任何责任。 61 | 8. 如果本声明的任何部分被认为无效或不可执行,则该部分将被解释为反映本团队的初衷,其余部分仍具有完全效力。不可执行的部分声明,并不构成我们放弃执行该声明的权利。 62 | 9. 本团队有权随时对本声明条款及附件内容进行单方面的变更,并以消息推送、网页公告等方式予以公布,公布后立即自动生效,无需另行单独通知;若您在本声明内容公告变更后继续使用的,表示您已充分阅读、理解并接受修改后的声明内容。 63 | 10. 本仓库部分开已开源代码遵循 MIT 开源许可协议。 64 | 65 | -------------------------------------------------------------------------------- /plugins/xinz/ChatGPT.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * @author seven 4 | * @name ChatGPT 5 | * @team xinz 6 | * @version 1.0.5 7 | * @description ChatGpt聊天,适配无界3.0,删除了tts避免部分不支持tts 修复对话只能独立对话 8 | * @rule ^(ai|画图) ([\s\S]+)$ 9 | * @rule ^(ai|画图)$ 10 | * @admin false 11 | * @public true 12 | * @priority 99999 13 | * @classification ["插件"] 14 | * @disable false 15 | */ 16 | 17 | const fs = require('fs'); // 文件系统模块,用于读取文件 18 | const path = require('path'); // 路径模块,用于处理文件路径 19 | const promptFilePath = './mod/prompts.json'; // prompts 文件路径 20 | const fullPath = path.join(__dirname, promptFilePath); // 获取 prompts 文件的完整路径 21 | const BCS = BncrCreateSchema; // 创建插件界面 22 | 23 | // 生成 prompts 选项 24 | let prompts = []; 25 | try { 26 | prompts = JSON.parse(fs.readFileSync(fullPath, 'utf-8')); // 从文件中读取 prompts 27 | } catch (error) { 28 | console.error("读取 prompts 文件时发生错误:", error); 29 | } 30 | 31 | // 生成 prompts 选项 32 | const promptOptions = prompts.map((prompt, index) => ({ 33 | num: index, 34 | name: prompt.act 35 | })); 36 | 37 | // 定义模型选项 38 | const modes = ['web-gpt-3.5-turbo', 'web-gpt-4o-mini', 'gpt-3.5-turbo-16k', 'gpt-4', 'gpt-4-browsing', 'gpt-4-dalle', 'gpt-4-32k', 'midjourney', 'dall-e-2', 'dall-e-3']; 39 | 40 | // 插件界面 41 | const jsonSchema = BCS.object({ 42 | apiBaseUrl: BCS.string().setTitle('ApiBaseUrl').setDescription('必填项,一般为"域名/v1"').setDefault(''), // API 基础 URL 43 | apiKey: BCS.string().setTitle('ApiKey').setDescription('必填项').setDefault(''), // API 密钥 44 | isEdit: BCS.boolean().setTitle('HumanTG是否开启编辑模式').setDescription('关闭则逐条回复,不编辑消息').setDefault(false), // 编辑模式开关 45 | promptSel: BCS.number().setTitle('选择预设角色').setDescription('请根据需要选择').setEnum(promptOptions.map(opt => opt.num)).setEnumNames(promptOptions.map(opt => opt.name)).setDefault(0), // 选择预设角色 46 | modeSel: BCS.string().setTitle('选择GPT模型').setDescription('请根据需要选择').setEnum(modes).setDefault('gpt-3.5-turbo'), // 选择 GPT 模型 47 | promptDiy: BCS.string().setTitle('请输入自定义Prompt').setDescription('输入自定义Prompt会使预设角色失效').setDefault(''), // 自定义 Prompt 48 | imgEnabled: BCS.boolean().setTitle('启用画图功能').setDescription('开启后将使用画图功能').setDefault(false), // 画图开关 49 | imgBaseUrl: BCS.string().setTitle('画图的ApiBaseUrl').setDescription('启用画图功能必填,一般为"域名/v1"').setDefault(''), // 画图 API 基础 URL 50 | imgMode: BCS.string().setTitle('画图的模型').setDescription('启用画图功能必填,根据自己的API支持情况填写').setDefault(''), // 画图模型 51 | imgApiKey: BCS.string().setTitle('画图的ApiKey').setDescription('启用画图功能必填,根据自己的API支持情况填写').setDefault('') // 画图 API 密钥 52 | }); 53 | 54 | // 创建配置数据库 55 | const ConfigDB = new BncrPluginConfig(jsonSchema); // 创建配置数据库 56 | 57 | module.exports = async (sender) => { 58 | try { 59 | // 确保所需的模块已安装 60 | await sysMethod.testModule(['got', 'chatgpt'], { install: true }); // 仅外部模块需要安装 61 | 62 | await ConfigDB.get(); // 获取配置 63 | const CDB = ConfigDB.userConfig; // 获取用户配置 64 | if (!Object.keys(CDB).length) return await sender.reply('请先到WEB界面完成插件首次配置'); // 检查配置是否存在 65 | if (!CDB.apiBaseUrl) return sender.reply("未配置ApiBaseUrl"); // 检查 API 基础 URL 66 | if (!CDB.apiKey) return sender.reply("未配置ApiKey"); // 检查 API 密钥 67 | 68 | const apiKey = CDB.apiKey; // 获取 API 密钥 69 | const apiBaseUrl = CDB.apiBaseUrl; // 获取 API 基础 URL 70 | const isEdit = CDB.isEdit; // 获取编辑模式 71 | const promptDiy = CDB.promptDiy; // 获取自定义 Prompt 72 | const imgEnabled = CDB.imgEnabled; // 获取画图开关 73 | const imgBaseUrl = CDB.imgBaseUrl; // 获取画图 API 基础 URL 74 | const imgMode = CDB.imgMode; // 获取画图模型 75 | const imgApiKey = CDB.imgApiKey; // 获取画图 API 密钥 76 | 77 | // 使用动态导入获取 ChatGPTAPI 和 got 78 | const { ChatGPTAPI } = await import('chatgpt'); // 导入 ChatGPT API 79 | const got = await import('got'); // 导入 got 库 80 | 81 | let gptAPI = new ChatGPTAPI({ 82 | apiKey: apiKey, 83 | apiBaseUrl: apiBaseUrl, 84 | completionParams: { model: CDB.modeSel }, // 设置使用的模型 85 | debug: false 86 | }); 87 | 88 | // 处理 'ai' 命令 89 | if (sender.param(1) === 'ai') { 90 | let prompt = promptDiy || prompts[CDB.promptSel].prompt; // 获取选定的预设 Prompt 或自定义 Prompt 91 | const promptMessage = `${prompt},另外,输出字符限制,输出50-100字。`; 92 | await relpyMod(sender, isEdit, `正在思考中,请稍后...`); // 发送思考中的提示 93 | let fistChat = sender.param(2) || '你好'; // 初始聊天内容 94 | 95 | let response = await gptAPI.sendMessage(fistChat, { 96 | systemMessage: promptMessage, 97 | timeoutMs: 3 * 10 * 1000 // 设置超时时间 98 | }); 99 | await relpyMod(sender, isEdit, response.text); // 发送回复内容 100 | 101 | while (true) { 102 | let input = await sender.waitInput(() => { }, 60); // 等待用户输入 103 | if (!input) { 104 | await relpyMod(sender, isEdit, "对话超时。"); // 超时提示 105 | break; 106 | } 107 | input = input.getMsg(); // 获取用户输入的消息 108 | if (input.toLowerCase() === 'q') { 109 | await relpyMod(sender, isEdit, "对话结束。"); // 结束对话 110 | break; 111 | } 112 | if (input === '') continue; // 如果输入为空,继续循环 113 | try { 114 | response = await gptAPI.sendMessage(input, { 115 | parentMessageId: response.id // 使用上一条消息的 ID 116 | }); 117 | await relpyMod(sender, isEdit, response.text); // 发送回复内容 118 | } catch (error) { 119 | console.log(error); // 打印错误信息 120 | return; // 退出 121 | } 122 | } 123 | } else if (sender.param(1) === '画图') { 124 | // 处理 '画图' 命令 125 | if (!imgEnabled) return await relpyMod(sender, isEdit, "未启用画图功能"); // 检查画图开关 126 | if (!imgBaseUrl) return await relpyMod(sender, isEdit, "未配置画图ApiBaseUrl"); // 检查画图 API 基础 URL 127 | if (!imgApiKey) return await relpyMod(sender, isEdit, "未配置画图ApiKey"); // 检查画图 API 密钥 128 | if (!imgMode) return await relpyMod(sender, isEdit, "未配置画图模型"); // 检查画图模型 129 | 130 | await relpyMod(sender, isEdit, '正在生成图像,请稍后'); // 提示生成图像 131 | try { 132 | const response = await retryRequest(() => 133 | got.default.post(`${imgBaseUrl}/images/generations`, { 134 | json: { 135 | model: imgMode, // 使用指定的画图模型 136 | prompt: `画一幅图,${sender.param(2)}` // 使用用户提供的提示 137 | }, 138 | headers: { 139 | 'Authorization': `Bearer ${imgApiKey}` // 使用 API 密钥进行身份验证 140 | } 141 | }) 142 | ); 143 | let data = JSON.parse(response.body).data; // 解析响应数据 144 | let dataUrl = data[0].url; // 获取图像 URL 145 | await relpyMod(sender, isEdit, { type: 'image', path: dataUrl }); // 发送生成的图像 146 | } catch (error) { 147 | await relpyMod(sender, isEdit, '画图出现异常,请去控制台查看错误提示'); // 提示发生错误 148 | console.log(error); // 打印错误信息 149 | return; // 退出 150 | } 151 | } 152 | } catch (error) { 153 | console.error("模块检查或执行时发生错误:", error); 154 | } 155 | }; 156 | 157 | // 定义 relpyMod 函数 158 | async function relpyMod(s, isEdit, message) { 159 | const userId = s.getUserId(); // 获取用户 ID 160 | if (isEdit) { 161 | await s.edit(message); // 如果启用了编辑模式,编辑消息 162 | } else { 163 | await s.reply(message); // 否则发送新消息 164 | } 165 | } 166 | 167 | // 重试机制 168 | async function retryRequest(fn, retries = 3, delay = 1000) { 169 | for (let i = 0; i < retries; i++) { 170 | try { 171 | return await fn(); 172 | } catch (error) { 173 | if (i === retries - 1) throw error; // 如果是最后一次重试,抛出错误 174 | console.log(`请求失败,重试 ${i + 1}/${retries} 次...`); // 输出重试信息 175 | await new Promise(resolve => setTimeout(resolve, delay)); // 等待重试延迟 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /plugins/xinz/Gemini.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Merrick & Collaborator (AI) 3 | * @name Gemini 4 | * @version 1.0.3 5 | * @description gemini聊天, 支持自定义模型 6 | * @team xinz 7 | * @rule ^(gems|gemc|gemp)([\s\S]+)$ 8 | * @admin true 9 | * @public false 10 | * @priority 9999 11 | * @classification ["插件"] 12 | * @disable false 13 | */ 14 | /* 15 | 使用方法:打开https://ai.google.dev/ 获取API key并填入配置界面(获取方法请自行搜索) 16 | 17 | 命令说明:gems是单问答模式,gemc是聊天模式,gemp是识图模式(目前只能识别图片链接) 18 | 命令+内容就可以触发,有人行的可以直接回复命令。 19 | 20 | 如后台提示"User location is not supported for the API use",请尝试调整梯子 21 | 22 | v1.0.3 增加模型自定义配置项,优化错误处理和临时文件清理 (By AI) 23 | v1.0.2 适配3.0 (By Merrick) 24 | v1.0.1 优化代码,增加单问答模式,修改触发命令,界面增加Max Tokens选项 (By Merrick) 25 | v1.0.0 基于sumuen大佬的插件修改 (By Merrick) 26 | */ 27 | 28 | // 定义插件配置的 Schema 29 | const jsonSchema = BncrCreateSchema.object({ 30 | apiKey: BncrCreateSchema.string().setTitle('API key').setDescription('请输入获取到的Gemini API key').setDefault(""), 31 | maxTokens: BncrCreateSchema.number().setTitle('Max Tokens').setDescription('聊天模式中最大Token数,设置越大可以聊得越久').setDefault(500), 32 | textModelName: BncrCreateSchema.string() 33 | .setTitle('文本/聊天模型名称') 34 | .setDescription('用于处理文本问答(gems)和聊天(gemc)的模型。例如: gemini-1.5-flash-latest, gemini-1.5-pro-latest。请填写 Google AI Studio 中可用的模型名称。') 35 | .setDefault("gemini-1.5-flash-latest"), // 设置一个推荐的默认值 36 | visionModelName: BncrCreateSchema.string() 37 | .setTitle('识图模型名称') 38 | .setDescription('用于处理图片识别(gemp)的模型。例如: gemini-1.5-flash-latest, gemini-1.5-pro-latest (这些新模型同时支持文本和视觉)。请填写支持视觉功能的模型名称。') 39 | .setDefault("gemini-1.5-flash-latest") // 设置一个推荐的默认值 (flash也支持视觉) 40 | }); 41 | 42 | // 创建配置数据库实例 43 | const ConfigDB = new BncrPluginConfig(jsonSchema); 44 | 45 | // 引入所需模块 46 | const fs = require('fs'); 47 | const { randomUUID } = require("crypto"); 48 | const path = require('path'); 49 | 50 | // 主函数入口 51 | module.exports = async s => { 52 | // 获取配置,如果未配置则提示 53 | await ConfigDB.get(); 54 | if (!Object.keys(ConfigDB.userConfig).length || !ConfigDB.userConfig.apiKey) { // 检查apiKey是否为空 55 | return await s.reply('请先发送"修改无界配置",或者前往前端web"插件配置"来完成插件首次配置(API Key为必填项)'); 56 | } 57 | 58 | // 动态导入 got (用于下载图片) 59 | let got; 60 | try { 61 | ({ default: got } = await import('got')); 62 | } catch (e) { 63 | console.error("加载 got 模块失败:", e); 64 | return s.reply("加载网络请求模块失败,请检查依赖或环境。"); 65 | } 66 | 67 | 68 | // 引入 Google Generative AI SDK 69 | let GoogleGenerativeAI; 70 | try { 71 | ({ GoogleGenerativeAI } = require("@google/generative-ai")); 72 | } catch (e) { 73 | console.error("加载 @google/generative-ai 模块失败:", e); 74 | return s.reply("加载 Gemini AI 模块失败,请在Bncr根目录执行 npm install @google/generative-ai"); 75 | } 76 | 77 | 78 | // 读取配置信息 79 | const apiKey = ConfigDB.userConfig.apiKey; 80 | const maxTokens = ConfigDB.userConfig.maxTokens; 81 | // 读取用户自定义的模型名称,如果用户没填,则使用 schema 中定义的默认值 82 | const textModelName = ConfigDB.userConfig.textModelName || "gemini-1.5-flash-latest"; 83 | const visionModelName = ConfigDB.userConfig.visionModelName || "gemini-1.5-flash-latest"; 84 | 85 | // 初始化 Google AI 客户端 86 | let genAI; 87 | try { 88 | genAI = new GoogleGenerativeAI(apiKey); 89 | } catch(e) { 90 | console.error("初始化 GoogleGenerativeAI 失败:", e); 91 | return s.reply("初始化 Gemini AI 客户端失败,请检查 API Key 是否正确。"); 92 | } 93 | 94 | // 使用用户配置的文本模型名称初始化默认模型 95 | let model; 96 | try { 97 | model = genAI.getGenerativeModel({ model: textModelName }); 98 | } catch(e) { 99 | console.error(`获取文本模型 ${textModelName} 失败:`, e); 100 | return s.reply(`初始化文本模型 (${textModelName}) 失败,请检查模型名称是否正确或可用。`); 101 | } 102 | 103 | 104 | // --- 命令路由 --- 105 | const command = s.param(1)?.toLowerCase(); // 获取命令并转小写 106 | const content = s.param(2); // 获取命令后的内容 107 | 108 | // 检查内容是否为空 (对于需要内容的命令) 109 | if (!content && ['gems', 'gemc', 'gemp'].includes(command)) { 110 | return s.reply("命令后面需要加上内容哦!"); 111 | } 112 | 113 | try { // 包裹整个命令处理逻辑,捕获意外错误 114 | if (command === 'gems') { 115 | await s.reply("Gemini思考中..."); 116 | const textOut = await textAns(content); 117 | await s.reply(textOut ? `Gemini回复的内容:\n\n${textOut}` : "Gemini未能生成回复。"); 118 | } else if (command === 'gemc') { 119 | await s.reply("Gemini聊天初始化..."); 120 | await textChat(content); 121 | } else if (command === 'gemp') { 122 | await s.reply("Gemini识图中..."); 123 | await picAns(content); 124 | } 125 | // 如果命令不是 gems, gemc, gemp,则不执行任何操作 (或者可以添加提示) 126 | // else { 127 | // s.reply("未知命令,请使用 gems, gemc 或 gemp。"); 128 | // } 129 | 130 | } catch (error) { 131 | console.error("处理命令时发生未捕获异常:", error); 132 | await s.reply(`处理命令时发生内部错误: ${error.message || '未知错误'}`); 133 | } 134 | 135 | 136 | // --- 功能函数 --- 137 | 138 | // 单文本回答 139 | async function textAns(prompt) { 140 | try { 141 | const result = await model.generateContent(prompt); 142 | const response = await result.response; 143 | // 增加检查,防止 response 或 text() 方法不存在 144 | return response?.text ? response.text() : null; 145 | } catch (error) { 146 | console.error("单问答(textAns)发生异常:", error); 147 | await s.reply(`Gemini请求失败: ${error.message || '未知错误'} (状态码: ${error.status || 'N/A'})`); 148 | return null; // 返回 null 表示失败 149 | } 150 | } 151 | 152 | // 聊天场景 153 | async function textChat(initialPrompt) { 154 | let chat; 155 | try { 156 | chat = model.startChat({ 157 | // 可以保留一个简单的初始对话历史,或根据需要清空 158 | history: [ 159 | // { role: "user", parts: [{ text: "你好" }] }, 160 | // { role: "model", parts: [{ text: "你好!有什么可以帮你的吗?" }] } 161 | ], 162 | generationConfig: { 163 | maxOutputTokens: maxTokens, 164 | }, 165 | }); 166 | } catch (error) { 167 | console.error("启动聊天(startChat)失败:", error); 168 | return s.reply(`启动Gemini聊天失败: ${error.message || '未知错误'}`); 169 | } 170 | 171 | 172 | try { 173 | // 发送第一条消息 174 | const initialResult = await chat.sendMessage(initialPrompt); 175 | const initialResponse = await initialResult.response; 176 | const initialText = initialResponse?.text ? initialResponse.text() : "Gemini未能生成回复。"; 177 | await s.reply(`Gemini Chat:\n\n${initialText}\n\n--- 178 | 回复 'gemq' 退出聊天`); 179 | 180 | // 进入持续对话循环 181 | while (true) { 182 | let input = await s.waitInput(() => { }, 60); // 等待用户输入,超时60秒 183 | if (!input) { 184 | await s.reply("对话超时,自动结束。"); 185 | break; 186 | } 187 | const userMessage = input.getMsg(); 188 | if (userMessage.toLowerCase() === 'gemq') { // 退出指令 189 | await s.reply("对话已结束。"); 190 | break; 191 | } 192 | 193 | // 发送用户后续消息 194 | await s.reply("Gemini思考中..."); // 添加等待提示 195 | const result = await chat.sendMessage(userMessage); 196 | const response = await result.response; 197 | const text = response?.text ? response.text() : "Gemini未能生成回复。"; 198 | await s.reply(`Gemini Chat:\n\n${text}\n\n--- 199 | 回复 'gemq' 退出聊天`); 200 | } 201 | } catch (error) { 202 | // 捕获并处理聊天过程中的异常 203 | console.error('聊天(textChat)发生异常:', error); 204 | await s.reply(`Gemini聊天发生异常: ${error.message || '未知错误'} (状态码: ${error.status || 'N/A'}), 聊天已终止`); 205 | return; // 异常时退出函数 206 | } 207 | } 208 | 209 | // 本地文件转为 Google AI 的数据部分 210 | function fileToGenerativePart(filePath, mimeType) { 211 | try { 212 | return { 213 | inlineData: { 214 | data: Buffer.from(fs.readFileSync(filePath)).toString("base64"), 215 | mimeType 216 | }, 217 | }; 218 | } catch (error) { 219 | console.error(`读取或转换文件 ${filePath} 失败:`, error); 220 | throw new Error(`无法处理本地文件: ${filePath}`); // 抛出异常让调用者处理 221 | } 222 | } 223 | 224 | // 识图场景 225 | async function picAns(imageUrlOrPath) { 226 | // 简单判断是 URL 还是可能是本地路径 (需要更完善的判断) 227 | const isUrl = imageUrlOrPath.startsWith('http://') || imageUrlOrPath.startsWith('https://'); 228 | let imgpath = null; // 图片的最终本地路径 229 | let shouldDelete = false; // 是否需要删除临时下载的文件 230 | 231 | try { 232 | if (isUrl) { 233 | // 下载网络图片 234 | const { body } = await got.get(imageUrlOrPath, { responseType: 'buffer', timeout: { request: 15000 } }); // 增加超时 235 | // 使用更安全的临时文件路径 236 | imgpath = path.join(tempfile.gettempdir(), `gemini_pic_${randomUUID()}.jpg`); 237 | fs.writeFileSync(imgpath, body); // 使用同步写入,简化流程 238 | shouldDelete = true; // 标记为需要删除 239 | // console.log("图片已下载到:", imgpath); // 调试信息 240 | } else { 241 | // 假设是本地路径 (这里可以添加检查路径是否存在的逻辑) 242 | if (fs.existsSync(imageUrlOrPath)) { 243 | imgpath = imageUrlOrPath; 244 | } else { 245 | return s.reply(`提供的本地路径不存在或不是有效的图片URL: ${imageUrlOrPath}`); 246 | } 247 | } 248 | 249 | // 初始化视觉模型实例 250 | let visionModelInstance; 251 | try { 252 | visionModelInstance = genAI.getGenerativeModel({ model: visionModelName }); 253 | } catch (e) { 254 | console.error(`获取视觉模型 ${visionModelName} 失败:`, e); 255 | throw new Error(`初始化视觉模型 (${visionModelName}) 失败`); // 抛出异常 256 | } 257 | 258 | 259 | // 准备图片数据和提示词 260 | const prompt = "详细描述一下你在这张图片里看到了什么?"; // 可以是更具体的提示词 261 | const imageParts = [ 262 | fileToGenerativePart(imgpath, "image/jpeg"), // 假设是jpg, 需要更智能的类型判断 263 | ]; 264 | 265 | // 调用模型进行内容生成 266 | const result = await visionModelInstance.generateContent([prompt, ...imageParts]); 267 | const response = await result.response; 268 | const text = response?.text ? response.text() : "Gemini未能识别图片内容。"; 269 | await s.reply(`Gemini识图结果:\n\n${text}`); 270 | 271 | } catch (error) { 272 | console.error('识图(picAns)发生异常:', error); 273 | await s.reply(`Gemini识图发生异常: ${error.message || '未知错误'} (状态码: ${error.status || 'N/A'})`); 274 | } finally { 275 | // 确保在处理完成后删除临时下载的文件 276 | if (imgpath && shouldDelete) { // 只删除下载的临时文件 277 | try { 278 | fs.unlinkSync(imgpath); 279 | // console.log("临时图片文件已删除:", imgpath); // 调试信息 280 | } catch (unlinkErr) { 281 | console.error("删除临时图片文件失败:", unlinkErr); 282 | // 可以考虑记录这个错误,但不一定要打断用户流程 283 | } 284 | } 285 | } 286 | } 287 | }; 288 | -------------------------------------------------------------------------------- /plugins/xinz/IKUN.js: -------------------------------------------------------------------------------- 1 | /**作者 2 | * @author seven 3 | * @name IKUN 4 | * @team xinz 5 | * @version 1.0.2 6 | * @description 发送IKUN语录和表情包支持所有平台 7 | * @platform tgBot qq ssh HumanTG wxQianxun wxXyo wechaty 8 | * @rule ^(鸡你太美|坤坤|ikun|IKUN|小黑子|小黑粉|真爱粉|你干嘛|纯路人|人民网)(.*)$ 9 | * @admin false 10 | * @public true 11 | * @disable false 12 | * @classification ["功能插件"] 13 | */ 14 | 15 | const request = require('request'); 16 | const path = require('path'); 17 | const fs = require('fs'); 18 | const { randomUUID } = require('crypto'); 19 | 20 | module.exports = async s => { 21 | let jpgURL = `http://api.caonm.net/api/kun/k.php`, 22 | open = false; 23 | ["tgBot", "HumanTG"].includes(s.getFrom()) && (jpgURL = await writeToJpg(jpgURL)), open = true; /* 存储图片 */ 24 | await s.delMsg(s.getMsgId()) 25 | await s.reply({ 26 | type: 'image', 27 | path: jpgURL, 28 | msg: ikun_yvlu(), 29 | }); 30 | !['tgBot', 'HumanTG'].includes(s.getFrom()) && s.reply(ikun_yvlu()); 31 | open && fs.unlinkSync(jpgURL); 32 | }; 33 | 34 | async function writeToJpg(url) { 35 | const paths = path.join(process.cwd(), `BncrData/public/${randomUUID().split('-').join('') + '.png'}`); 36 | return new Promise((resolve, reject) => { 37 | let stream = request(url).pipe( 38 | fs.createWriteStream(paths) 39 | ); 40 | stream.on('finish', () => { 41 | resolve(paths); 42 | }); 43 | }); 44 | }; 45 | 46 | function ikun_yvlu() { 47 | msg = [ 48 | //0 49 | "向阳花木易为春,听说你爱蔡徐坤。", 50 | //1 51 | "千军万马是ikun,ikun永远爱坤坤。", 52 | //2 53 | "待我ikun更强大,定帮坤哥赢天下。", 54 | //3 55 | "两耳不闻窗外事,一心只为蔡徐坤。", 56 | //4 57 | "追梦少年不失眠,未来可期蔡徐坤。", 58 | //5 59 | "向阳花木每逢春,ikun一直爱坤坤。", 60 | //6 61 | "落红飞雪漫纷纷,心头只有蔡徐坤。", 62 | //7 63 | "披荆斩棘,护坤为王。", 64 | 65 | //8 66 | "披金成王,伴坤远航,我们ikun不惹事也不怕事。", 67 | //9 68 | "破天下定风云,我们ikun并肩行。", 69 | //10 70 | "狮子座为王,蔡徐坤为皇。", 71 | //11 72 | "向阳花木易为春,听说你爱蔡徐坤。", 73 | //12 74 | "山外青山楼外楼,唱跳rap打篮球。", 75 | //13 76 | "梳中分,背带裤。我是ikun你记住。", 77 | 78 | //14 79 | "拜托你们膳待我的男骸亿点点好吗,就亿点点,真的很好玩吗?", 80 | //15 81 | "你们是不是人啊!道歉会不会!你们黑牠黑得那么爽,骂牠骂得那么爽,整天TM没有任何一个人会给牠道歉。你们这样会毁了他的前途,毁了牠的一生!!", 82 | //16 83 | "荔枝,你让我拿什么荔枝啊?我爱的那个牠都被你们这样对待了,还让我怎么荔枝,啊?看着牠被你们骂得狗血淋头我还坐视不管这就叫荔枝了吗?!", 84 | //17 85 | "笑死,你就这么几个表情包,啊?你除了这个表情包你发点新的过来啊,都看腻了好不好,小辣鸡。", 86 | //18 87 | "私生饭黑粉你们犯法了知道吗,牠很累的,被警察半夜敲门是什么滋味,被私生饭装跟踪器是什么滋味。", 88 | //19 89 | "告诉你蔡徐坤身上一根毛拔下来都比你好看,都比你有用,都比你有才华,都比你有颜值,都比你有能力。", 90 | //20 91 | "蔡徐坤到底怎么得罪你们了,为什么要这样对侍他。他真的很好,ikun不是不敢惹事,而是因为他告诉我们无论这个世界怎样我们要永远善良。蔡徐坤是我们ikun的底线,请大家放下偏见去看这个男孩。", 92 | //21 93 | "我庆幸我比流言蜚语更早认识蔡徐坤。未曾谋面的也终将会相遇。披金成王,伴坤远航!蔡徐坤值得", 94 | //22 95 | "蔡徐坤真的很不容易!千万不要回黑,既然不能让玩梗的人闭嘴,那我们就尽力将这件事情冷处理,不要被有心人利用。", 96 | //23 97 | "你们只把他当热度和玩笑,把别人的痛苦建立在自己的快乐之上,你们愧疚吗?", 98 | //24 99 | "蔡徐坤这一生很难,也遇到过很多困难,他都克服了,有我们这群默默的守护着他,你们这群黑粉就别想黑他。", 100 | //25 101 | "他拿姜拿到手软,你打字打到手软,所以这就是区别。你有什么资格黑他?", 102 | //26 103 | "评论区好下头,ikun们越是这个时候越要冷静!坤哥好不容易从那段暗无天日的时光熬出来。", 104 | //27 105 | "作为一名ikun真的已经麻木了 ,解释没用,去反驳得到的只会是一堆表情包, 根本没人会站在你这边,这个世界充满恶意,坤坤到底怎么得罪你们了,为什么这样对侍他。", 106 | //28 107 | "鸡那么美你怎么不去娶鸡呢?你那么喜欢说篮球篮球,嫁给篮球得了,把篮球剪个洞套你头上。你会写歌吗?你会作曲吗?你会唱歌吗?啊?你连台都上不去吧,上了台就要尿裤子了吧。", 108 | //29 109 | "你黑蔡徐坤,我们只解释不回黑,不是因为我们不敢,而是牠告诉我们,无论这个世界怎么样,我们永远要善良。但是我们给你好好说话,你不要以为我们好说话,爱鲲不惹事,也从来不怕事。", 110 | //30 111 | "大家好,我是一名爱鲲,我今天想说的是,你们可不可以不要再黑蔡徐坤了,蔡徐坤真的很努力,牠发高烧发到七八十度,都坚持练舞四五个小时,牠真的很努力,你们可以去试着了解一下牠,你们也会爱上牠的,你们也会粉上牠的,不要再玩四字梗好吗,我求求你们这些黑粉,好玩吗?", 112 | 113 | //31 114 | "一个篮球梗用四五年也是好笑,人家只是篮球爱好者。 我想说​不爱请别伤​​!有时人要换位思考,如果你是他,那你会有什么感受? 每个人的眼光不一样,你可能会不喜欢他,但是不能去黑,侮辱他,这是人最鸡本的善良!​", 115 | //32 116 | "纯路​​,不过我还是要说两句。《刑法》第246条:故意散布贬损他人人格,破坏他人名誉,本罪在犯罪客观方面表现为行为人实施捏造并散布某种虚构的事实,足以贬损他人人格、名誉,判处时长两年半有期练习,并上缴全部个人所得动图!", 117 | //33 118 | "你们真的要逼死他么?早上刚得到坤坤被骂的消息,一下子有点恍惚。看着B站的讨伐声,不知道为什么,有点累。想到这么一个优秀的青年突然沦落到这步田地,我的情绪再也控制不住了,整个人蒙在被子里,偷偷的笑出了声​", 119 | //34 120 | "作为一名外国人,我真的很喜欢中国文化,为此我努力学习中文,想要更深入地了解中国文化,在我学习中文的过程中,有一位中国偶像深深影响着我,记得第一次看到他在屏幕中舞动的身影,黑白相间的服装吸引着我的目光,双手交叉传递的篮球狠狠触碰这我的心,至此我坚定了自己学习中文的目标,也许在我的不断努力下,我一定能见到他本人吧,噢对了,我第一个学习的中文单词就是他的名字--蔡徐坤,为了不让偶像对称呼我感到为难,我第一个学会用中文写的就是我自己的名字,只为了能像他更好的介绍我自己。加油,坤坤!我会一直追赶你的脚步!你的粉丝---尼甘·马·艾由​", 121 | //35 122 | "烂梗一直刷是吧,这次真的挺心疼蔡徐坤,其实他长得蛮好看演技也挺不错的,只因一个片段就被全网黑,哎哟何必呢,搞不懂干嘛要这样,反正就觉得很可怜,作为路人我觉得适可而止吧。​", 123 | //36 124 | "至于吗 把人家都搞成那样……玩梗不玩人,你这都不理解吗。看看坤坤被你们黑成什么样子了,生日也不放过他?我本来以为你是真心祝kun蛋辰快乐的,没想到还是黑子。​", 125 | //37 126 | "我不是很明白,他真的只是一个二十多岁的小伙子,他有自己的梦想,也在用自己的方式不断地努力,一心希望把最好的作品呈现给众人,可总有一些人不仅忽视了这些,而且还要对他不断地中伤恶言相向。是否越善良越容易被伤害?这是我第一次怀疑这个世界,世态炎凉,一切似乎都太险恶太残酷了。坤坤,请不要再这样了,我心里真的好痛好痛,这个黑暗的世界不适合你,我希望你能去天堂", 127 | //38 128 | "蔡虚坤发烧62度坚持上台,双腿粉碎性骨折仍练习踢踏舞,每日练习40个小时,大小便失禁仍不去医院,累得高位截瘫还练舞,喉咙肿大30多倍坚持练发音,有全国300亿粉丝却不骄傲,每个2月29、30日慈善义演,将获得的20万亿美元捐给瑞士上不起学的孩子,为了慈善事业每天献出两大桶血,这些你们能做到吗?", 129 | 130 | //39 131 | "有一天,熊大熊二趁着光头强不在家,叫上动物们.来光头强家搬点东西到森林吃火锅,熊大搬着煤气罐看着忙碌的动物们,唯独吉吉国王在那无所事事,熊大默默地放下手中的煤气罐叫到“吉你抬煤”!​", 132 | //40 133 | "一天,纪晓岚,和珅,一起去抬东西,抬完之后,和珅感觉累的不行。纪晓岚却非常轻松,和珅很疑惑,问到:“纪你抬没?”", 134 | //41 135 | "去文具店买笔,正准备把笔捆起来,老板娘不让捆,我问:啥笔才需捆?老板娘撕掉条形码说:撕了码的才需捆。但我不解,继续问:那么挨捆的都是撕了码的吗?老板娘说:对,撕了码的才需捆,挨捆的都是撕了码的!”​", 136 | //42 137 | "通过纣王的不懈努力妲己终于怀孕了,不幸的是,有一天妲己散步的时候被一块石头绊倒了,栽了下去,流产了,纣王看到崩溃地大喊:己你胎没!己你石栽使胎没!!!​", 138 | //43 139 | "有一天小吉和一群人去山洞,发现山洞有个尸体,就和其他人抬出去了。回家后,其他人都死了,爷爷问:“吉你抬没?”小吉摇了摇头。这时小吉的身上变绿了,爷爷说:“不好,是绿尸寒警告!”​", 140 | //44 141 | "近日,一位名叫苏珊的美国女科学家发现了人类已知最小的太阳黑子,国际天文学会决定将该黑子以发现者的名字命名为“小黑子苏珊”。​", 142 | //45 143 | "我要赶紧告诉大家,现在网上有一个骗局,只要你付两千块钱就可以得到一个苹果手机,但是他们只会寄给你一台魅族手机,简称寄你台魅​", 144 | //46 145 | "鸡的体内有一种基因叫做酶,在极端环境之下这种基因酶变就会激活,和体内的另一种酶进行转变,会让公鸡拟变出肽类酶,这种现象在科学角度上被称为“基拟肽酶”", 146 | //47 147 | "有个农民在家摘菜的时候发现菜的须根上有个不认识的昆虫,于是去问昆虫学家,那个昆虫学家也不认识。后来昆虫学家说:"既然这只昆虫喜欢呆在菜的须根上,就叫它菜须昆吧。"​", 148 | //48 149 | "线粒体内膜的嵴上有许多排列规则的颗粒,称为线粒体基粒,每个基粒间隔相距约10nm。基粒又称偶联因子1,实际是ATP合酶,线粒体内膜的嵴上有许多排列规则的颗粒,称为线粒体基粒,每个基粒间隔相距约10nm。基粒又称偶联因子1,实际是ATP合酶,是一个多组分的复合物。这些酶隐匿在嵴上,我们将这种现象称为“嵴匿肽酶”。", 150 | //49 151 | "​双盘吸​​在被其感染的僵尸蜗牛触角内不断跳跃,发出鲜艳的颜色,来模仿毛毛虫,吸引小鸟吃掉蜗牛,以此进入鸟体内继续繁殖。这种拟态方法叫做攻击型拟态魅惑,简称击拟态魅。​", 152 | //50 153 | "一小伙子在公交车上不小心撞到了一名孕妇,连忙抱歉道:“挤你胎没?” 该孕妇回答:“哦,baby” 小伙说:“挤你实在是太霉” 他看着孕妇,脸微微的红了。他低下头小声的说:“迎面走来的你让我如此蠢蠢欲动”", 154 | //51 155 | "2333年,科学家在一次高能对撞实验中意外得到了一种全新的粒子,该粒子处于激发态时与一般粒子带有的普朗克能量不同,科学家将这种全新状态称为逆态,同时粒子会影响周围的动物的思维,致使该动物莫名跳动。因其的​相对原子质​​与镁类似,于是被称作激逆态镁。​", 156 | //52 157 | "1942年6月6日,德军在进攻苏联时,发现一种威力巨大的榴弹炮,据德军不可靠情报,这种武器是由小尼古拉黑椰子斯基制造,德军对这种武器极为恐惧,于是给她起名--“​小黑​​苏制66榴”​", 158 | //53 159 | "美国德克萨斯州的一座小镇上近来经常发生离奇的枪击案,枪手使用消音手枪一到深夜就开始疯狂的射击汽车的轮胎就连摩托车也不放过!小镇的居民也苦不堪言,没有了汽车出行就成了最大的问题,于是小镇居民终于向当地警方报了警!就在报警后的当晚,警方成功击毙了一名身穿背带裤梳着中分的女子还缴获消音P92手枪一把和若干9毫米子弹。最后德克萨斯州警方将这次离奇的枪击案命名为“击你胎没”事件最后结案!​", 160 | //54 161 | "去年去了趟昆明,发现了一种菜,因为昆明的优越天气,所以菜依靠昆明的天气多盛产于此,人们叫这种菜为菜需昆,这种菜又叫芥菜,和昆明肥沃的土壤相结合,称为荠泥太美,因为昆明人有效率的种植施肥,所以荠菜的茎罕见的高,后来被记录为率施罕茎高", 162 | //55 163 | "基泥肽酶是一种在常温下十分稳定的物质,不会因为外界环境改变而发生反应,但是在特殊条件下,也就是在基场条件下,用催化剂异长白甙的环境下,用一种叫做镧球的金属微粒轰击反应物,此时基泥肽酶的内部结构会遭到破坏,里面的泥酐麻化学键会被破坏,于是形成一种新的化学分子,我们把它叫做基泥拾哉式肽酶,并且外观也会发生变形,变成荔枝状球体,又因为新生成的基泥拾哉式肽酶上面有少部分未反应的黑色基泥肽酶,表现为​小黑​​附着,我们把这一现象叫做荔枝小黑子现象。​", 164 | //56 165 | "在狗熊岭的冬天,小动物们正在准备过年,大家都很忙碌,只有吉吉国王一直游手好闲,熊大非常生气,于是放下了扛着的煤气罐,对吉吉国王说:吉你抬煤!吉吉国王不愿意抬煤,于是熊大让它去山洞取荔枝,吉吉国王在去山洞的半路上遇到了光头强,它急中生智朝光头强脚底下扔了一根被虫子钻了黑子的树枝,光头强立刻滑倒在地,吉吉国王高兴地说:小黑子树枝溜溜溜!吉吉国王在路上看到了一颗绿色的宝石,它捡了起来,原来是前年难得一见的绿石罕,吉吉国王立马忘掉了拿荔枝的事情,高兴地跑了回去,熊大看到吉吉国王手中没有荔枝,问它荔枝在哪,吉吉国王问:荔枝?你让我那什么荔枝啊?熊大被气得面红耳赤,这时熊二拿了一块饼来安慰熊大,问:熊大你食不食油饼?", 166 | //57 167 | "那天我玩匹配,选了狄仁杰打下路,看到中路的小乔皮肤很好看,就顺口夸了句。 结果跟着我的辅助蔡文姬不干了,一直围着我跳舞。 “我的皮肤也超可爱的,也夸夸我嘛!” 我看了下她的名字,猜测她可能是个抠脚大汉,所以一直没理她。 她就开启唐僧模式,一边跳舞放技能特效,一边嘟嘟囔囔的。 之后小乔来游走抓死了对面,我发了个干得漂亮。 蔡文姬又不干的,围着我不断放技能,晃的我眼睛疼,把球打来打去,到处都是。 我忍不住说她了两句。 “你能不能好好玩游戏啊,别一直乱丢球,选个蔡文姬还带闪现?一直跳舞,一直嘟囔个没完,当自己是说唱歌手吗?” “人家就想让你夸我一句嘛!” 然后就委屈的不出声了。 不一会对面诸葛亮来抓下路,有个光头闪现把我踢到墙上,我秒解控往后退,却被阴影中浮现的阿珂刺成了残血。 而此刻,蔡文姬还在一旁踢球,大招也在刚才显摆时放掉了! 我不得已放出闪现,却已经被诸葛亮大招连上了死亡之线。 我看了眼蔡文姬的位置,绝望的骂了句脏话。 然而就在那道死亡光波冲到眼前时,蔡文姬从我身后闪现到了我身前,挡住了那个死亡一击。 然后转眼间被对方三个歹徒群殴致死。 她闪着漂亮的彩光倒在了我身前,而我则残血跑回塔下,捡回了狗命。 “对不起,我刚才不该说你的。” 我感觉她刚刚一定很伤心,说不定都被我说哭了,内心充满了愧疚。 她说。 “我也很漂亮对吧?” “夸夸我好吗?” 我内心一软,含泪打下了四个字——“姬 你太美。​", 168 | //58 169 | "老师您好,对于您的观点我有几点疑惑,酯铟铌钛镁中的镁在其中显+2价,但​激铌钛​​中的镁却显+3价,是因为它和销黑子反应产生了变价反应吗?但通过方程式得知酯铟铌钛镁和销黑子反应生成的激铌钛镁和锠、銚、rap,中的rap是气体,显然是不成立的,如果加入催化剂香精碱钰也不能加速反应,同理,香炽酪钒和釉鋲也不行,那是为什么呢?是因为销黑子中含有大量的锂酯吗?​", 170 | //59 171 | "唐僧师徒四人经过女儿国时,守卫:只有女人才能进。随后三个徒弟都变成女人进去了,八戒说师傅怎么办?只见唐僧悄悄的跟那守卫说了一会,那守卫便放他进去,后来八戒问唐僧说了什么,唐僧:我会唱跳,rap,篮球​", 172 | //60 173 | "蔡徐村,这原本是一个祥和宁静的村子,突然有一天,一个叫苏珊的女孩儿把​鸡病​毒带了过来。从此所有村民都变得行为怪异,对篮球十分敏感。见到没有感染的人就会上前攻击传播病毒。原本正常的人也会变得跟他们一样。科学家们捕捉了一个样本带回基地研究,发现他们嘴中一直念叨四个字:鸡你太美​", 174 | //61 175 | "宋仲​基和李晨一个离婚 ,一个分手了,也就是说他们的太太都离开他们了,所以,也就是传说中的基李太没?", 176 | //62 177 | "这种生物学名叫:亚种海鸡 习性在海滩边生活,由于人类的对海滩的大量旅游业发展,常食旅人剩下的油饼,荔枝,鸡脚,树脂等食物。而且拥有极大的药用价值可做成​绿尸​寒去伤风去败火,而且还由于肉质鲜美嫩滑渔民用来使用过只需要捡些树枝起火上搭小黑锅放入荔枝等调料就非常好吃了。而这样的物种还有类似于亚种路鸡身上的纹路像背带裤一样十分可爱", 178 | //63 179 | "小蔡似乎是城里人,只有暑假的时候才会来到村里跟我们玩,她应该是没有父母的,因为每次来都是一个不认识的人把她送来,问她她也闭口不谈。她总是说着想去当练习生,至于原因,我想大概是为了找她的父亲吧。我们村子以前闹过坤,搞得人心惶惶,半夜小孩子一哭,外面总能听到篮球声,村长迫不得已把我们村的唯一一个小型篮球场都给拆掉了,有一次我爸去山上砍柴火时,后山那乱坟岗传来了鸡叫,当时没有听清楚,后来我们村长和我们村几个大户人家耗费巨资把后山那个乱坟岗给填了,做了一个篮球场,扔了一个篮球进去,放了两只鸡,然后把篮球场有很高的围墙围住,不允许任何人进去,后来我们村篮球声就消失了,我感觉这都是老一辈的迷信,直到暑假某一天,我和几个小伙伴带着小蔡偷偷去到后山篮球场玩……我依稀记得那时半夜12点,几个小伙伴比谁胆大,"​你干​嘛,哈哈,哎哟"。我们几人都吓坏了,其中胆最小的小蔡直接哭了出来,我们几个没法子,就安慰小蔡,谁让她是女孩子呢……我试着安慰小蔡,她也不哭了,小伙伴们商量着要下山,我们都表示同意,走了没几步,这时小刚说“小蔡呢?”我意识到不对,连忙回头看,只见小蔡杵在原地,沉着脸,我上前叫她“喂,小蔡,你怎么了?”我以为她吓着了,还待在原地不敢动弹,就推了她一下... “你干嘛哈哈~哎呦~” 我大惊,“你是...鸡!", 180 | //64 181 | "​我是养鸡​的,有一天,肯德基伫我打电话,说要一箱鸡,很急要马上送到,我的员工比较懒,我赶紧去开货车,到了养鸡场,我看见员工在那里歇着,我气的大吼道:鸡你抬没?​", 182 | //65 183 | "近日,柳叶刃自然期刊杂志和鸡国皇家医学院基妮肽酶团队发表了最新的研究成果《关于基妮肽酶的发现及作用》:我们经过两年半的不懈努力,从肌肉中大量提取了一种生物酶--基妮肽酶,这种酶,可以保持激发态妮肽酶的性状于稳定态,在此发生复杂的化学反应,由酵素催化分裂妮肽酶从氨基末端(N-terminus) 蛋白质或者肽(外肽酶)它们也广泛分布于整个动植物界,并存在于许多亚细胞中细胞器,特别是在荔枝和虾头的细胞膜表面作为膜组件。基妮肽酶用于基本细胞功能。这些肽酶中有很多是坤酶(ikun of enzyme)。此外,我们发现它和销黑子反应产生了聚变反应,但通过大量实验证实,基妮肽酶和销黑子反应生成的妮肽酶和锠、銚、rap,而且加入催化剂香精碱钰能加速反应,同理,香炽酪钒和釉鋲也可以加速反应但是制备过于繁琐,需要先用绿尸寒精镐(green ​corps​e cold)在石棉网中加热至融化,加入氨钡釿叁与之混合至出现黑绿色气体--基妮肽钡釿。食不食油饼大学全球卫生研究所所长Antoine Flahault也表示,基妮肽酶的发现是人类科技里程碑式的进步。我们将基妮肽酶致力于补充与替代医学当中,强调健康效果,同时记录生物作用机制,造福人类是我们的责任! ————《柳叶刃 – 全球健康》(The Lancet Global Health)2099年坤年坤月坤日​", 184 | //66 185 | "1S2 2S2 2P6 3S2 化学老师在黑板上写下 “同学们这是什么?” “​氢氦锂铍​硼……”同学们纷纷回忆起来 “太慢了同学们太慢了”,化学老师生气了,“不能一下子反应过来,这可达不到高考要求,这个速度卷子可是写不完的” 化学老师清了清嗓子:“记住了同学们一定要记住了,这是基态镁,这是基态镁!”​", 186 | //67 187 | "我叫小吉是黄河捞尸,在我们圈子有个忌讳,就是遇到穿背带裤溺水的尸体不能捞。尤其是死亡时长达两年半那种最是邪门,在午夜12点后又唱又跳的甚是恐怖,如果抬了这种尸体,会倒大霉,我们将这种说法称为——忌溺抬霉 有一天我和一群人在河里,发现有个尸体,就和其他人抬出去了,但是大家没有注意到这具尸体穿着背带裤,回家后,其他人家的鸡突然发疯又唱又跳,甚至在半夜两点半的时候还在打篮球,被吵醒的爷爷问小吉:“吉你抬没?”小吉摇了摇头。这时小吉感到一股凉意然后身上变绿了,爷爷说:“不好,是绿尸寒警告!这时那具尸体突然起身拿起小吉的篮球大喊:“鸡你太美!”打起了篮球哟西我这有个差不多的", 188 | //68 189 | "有天我误闯女更衣室,顿时惊叫声一片,我急中生智,大喊一声,我喜欢唱,跳,rap,篮球!顿时寂静,大家都放心了", 190 | //69 191 | "我是一名地质学家,一天我在台湾的一个叫“四泰寺”的地方出土了一块珍贵的煤炭矿石样本,我欣喜若狂连忙将其寄给了同为地质爱好者的妻子,当她收到包裹时并没有着急打开而是打电话问我给她寄了什么好东西,我回答她说:寄你四泰寺台煤", 192 | //70 193 | "小兔对小鸡说: “你知道娃娃鱼身上最珍贵的部位是哪里吗?” 小鸡想了想,恍然大悟说: “鲵肝嘛,哎呦。”​", 194 | //71 195 | "孟子来到梁国,见证了大梁城的繁华,又看到梁国乡野饿殍遍地,不禁悲从中来,长长叹息,哀民生之多艰。 带着这种情感,孟子前去面见​梁惠​王,提出了自己施政的种种对策,希望重新以礼规范整个国家,恢复生产、与民休养、制止浪费,孟子说:“以礼治国,则国泰而民安也, 王 道之成,可举目而盼。” 梁惠王环顾四周,看到的尽是阿谀奉承之臣,又想起秦国对大魏河套地区垂涎欲滴,楚国有心再次问鼎中,内忧外患之下,梁惠王悲愤交加,竟然对孟子大吼:“礼治!你让我拿什么礼治!”", 196 | //72 197 | "相传华佗帮关羽划骨疗毒的时候,无意间发现关羽额眉间有一颗毒块,华佗为了割掉那颗毒块,索性用针刺戳了关羽的骨肉一下,关羽痛的抬起头来,华佗趁机割掉了毒块。后来世人将此举动称之为:激你抬眉​", 198 | //73 199 | "最近我发现有好多幼儿园小朋友想要尝试老干妈,却又怕辣。于是我模拟老干妈发明了一款爱护幼儿的辣酱,我决定称其为拟干妈 爱幼", 200 | ] 201 | num = Math.floor(Math.random() * msg.length) 202 | msg = msg[num] 203 | // console.log(`NUM:${num}\nMSG:${msg}`) 204 | // console.log(`NUM:${Math.random()}*${msg.length}=${num}\nMSG:${msg}`) 205 | return msg 206 | }; 207 | -------------------------------------------------------------------------------- /plugins/xinz/bncr_ssh2.0.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author seven 3 | * @name bncr_ssh2.0 4 | * @team xin 5 | * @systemVersion >=:2.0.5 6 | * @authentication true 7 | * @classification ["ssh","更新docker容器"] 8 | * @version 1.2.1 9 | * @description 最好用的ssh连接,内置docker容器更新命令(如:更新 bncr);触发词:ssh或ssh 序号 这个是大佬加密插件 可使用也可不使用 10 | * @rule ^(ssh)$ 11 | * @rule ^(Ssh)$ 12 | * @rule ^(ssh)([^\n]+) 13 | * @rule ^(Ssh)([^\n]+) 14 | * @admin true 15 | * @priority 9999 16 | * @public false 17 | * @public true 18 | * @classification ["功能插件"] 19 | 说明: 20 | 1.支持设置多个主机信息 21 | 2.ssh后不跟数字会让你选择要执行的主机编号 22 | 3.ssh后加数字会直接进入该主机:比如"ssh 3",会直接连接编号3的主机 23 | 4.内置docker更新命令,比如要更新bncr,直接输 '更新 bncr' 24 | */ 25 | /** Code Encryption Block[419fd178b7a37c9eae7b7426c4a04203f5f502784ecfb5aec20d23d78e2e72e32c2b22d2103c6fb449d3a8497a6b9e0092606e0c580204ee4e03d64ccdc8aa8db8f96e05a92c7590fcd32098e13a47358e69260687bd98801d7c894233557e36cf107d75cd339f8f55ecee2f64902e927a14a9d9d7ee26edec2c5ebc77fc7118134d3212b1b45109561f46d29455c0c961e253a7d26dc2930cb5095c246c0576ab1127c49034eef79a3245de159133a971754d91d4b98afb1ba18da30cdc2490d369a1458d472e393fba8a71bbac04c14804d068739c32b92c499fdddd8ef4a6b03ac9119b2ef3a308cd0a66f639993cb3f31e83b4da9d3fb02757ca7f5039604f3391d596a20ba009030793810e8f6d22ae1a7ab38839d5fe09c20ea10dec47199270bb6cff2709678302ea42e9347726aff6133ba5fd7bc98ac94878871599a3f997b22f03511b8be96533d6eaea03360bbbdc432a9cda14befa37ecdccd1cf28483ca56f1da99a35d5eb37a3d41a29273462c955327beb8ec5af06276f136e0fa6aa414b76516f6b390f875987f8672dc222b347dc8d8cdc6849092e3d1e639babd9cc4540aa4e92605a0968419408a7fa96da3a38a5ab382d9503c6ea71536d96a8862bf308b7107318445da290b7c9073b55191b3912de68a297be911186cb0ca92c1957549fc315ed67949ecd8d8c62a971436354ef5fea7d49b0e6cfd357f119df185a21c71a971cfba38593b72d02ee807fd0851c3d992d05e63d5b5464cbf3acdeb267639b98623a71caed48a46d66fc2494a5915a39cb4d39e15d4c1637d34aee0b359bc541dff54aae119efe13298a6a062f029cca9e8b37995f9e679a2314c775774c822f3714c9f5dabad5cf5d9e677a07b7998d36e87e1523c892c731442bf550fcf2ddb0eed6f9066b8ee4303d44fa09bce099008d9187c59d5e35304145bb808ed00f61e6b8100160aea0e4a3c9564dffb20e8a6879c69d54f38aace9e116da699490e22c47f3eef633328df041f29a22448fdbc1f7ef76b2981366195f0fe0d6eabe3296ae5e62459fb0ed617dbff71cf0e0608cebf8f9223ddcaff36e4ecf8d44a1b807fe6e1060abac2ef2ddad6e3407237009c87d5740b0050c65467c06077a63c3ace55e341859353eaf53488b763d0ba5d59c5a367f23c5304c2e4cb1d7c1b19c6876be976cdac00b37aa81182eea95352bf9efc95589d62225053c39166f54116a43328240f85ccebf4953a214cb98054a68a9ddc98ee64924429ba399487d2330005f66714ea943bc2ac77ac282f96754d23ea52ad101495a88c7c6d8c97b4d77bd3eaf0abb311aed07cd5e5fec124756a8e37b164396119cf1bde250b60d15c38642823adf804dd42d925fb836589926fe261f6f584e5ac53b902fa6ff6840f8050f1d584b03776df3a7475bde89b0b1da007d9c064c92f2f5a9c057edea4af81e3bd8dc8dffde9ab89dc6eb1ea12775c28ce460c16038811b6bbccd482c679fcf9bfde1d25760e9abaf55d015a4c66c7d864af5e7a70281d2feb6e0f66ebc6bc86ba457738646bac4a81b81edfab6ca77a6b15f14ce883f0deef31e39bdf53d1dc755af5842d46fc68f18adb52d1f3a4be0e8639393681c7f607ab84c739e843cee1a1749b42387ce2e451a2a818a9b4cb7e488c63df5023c2acb86491493c8984972ab76936491440294650cb1bb443f84519b84e7969f147d94e2e67c29430dd14e64121c54c444e7497f6b4819ff537c51aad061b1e4112a3cc01639456a5882d776cd0913773d692dce765ed76711102c142a408e8aa4750589174108ed9084b5a0c6e30c4c84d70f32a561e896cc44719ec2fa3740c4b49bfc4f03a07ff558c7ef4623ac82569d0023d64ac4c9b107039897495162edebd541d1031f676a0b74ff63f9b09a9a225140b32c72383d7968a4b7830abb8d76a8f497518870f0717334db7b335c600d24d14cc5726b661df38225f208b8f992167952733628c30dbf22a4232be0a757b1bd9b3f4c11d705ff0dc6fe7150fcbff7872dbb3e190e1bcf22497738921e1420bc9b887c803f876e535c961fa6a973687abc9cf6376203431be19d2c9d1582aa5cfbc9bf7bf9cdbf0a3e10035fd1884b674868b5a041e03cd45cda0bc5731e1dcec21cb5b5f06593e40795b908071fea39ce0f712a8c072aca073e19f6830a28f5aa6a9c0526228080b4d7ac6ff7c7c84af1ab2b5ab06d9c2967bb0fe05de31163ad8945c34e46087905d4765b804f57c1a8eace3c971bb490b90fc7b0ba35475b8dffd8186552bf6601f15e4bc18cded3cffddc68b355e5d5fa8e120a62a0438252f95b4cbec34a0813b30dbecb79b06b54f4b1fb3f6196b7d31e5e94c5094d70bf36eb2d67c209948a6c639c40b839ea8994dc92d04490fe2d9572abb4cf90b32dd22a100fb1e1a1131de56be6ef3c89aba9616cf49fb343ed2cea83e554cc7c11df88124853a78eefa351a7e78994beeeb06a2a8135eb357e87f8af541ba19a488c39e5ca038e8eaf55a4ea67e6c773a344edb5d4b452139acfbbc6269d37ed59a172a9df0020f90b7d175f0c75d81cff16cc29f26d3590b130028f8e97d3646f63a780c0549fe250e2e61817d7832b20d5f2ab9bf5ac28d92548ca3f5648cf8ef56f10ccbdd308d22df4e23ce75c71330c9c451bce62977e241733f559bf5d01dad0132f09ae09f70d5a89582fe7bc624bd9371478be11f7281469025feef28490b4a4fb1b3bb2d896c3d08dfec140e692e7bf6ab6092106e1db98e8b5d6cb9952761631817dd2cd31f835fd285ace5ac3e0bb0e1e21828a69977a8853f20856d2aed39bc467f94ab1f15d994419992d8079d48d87979a102ac5d28007ac58c20e8f09871b08c8267d5a47eb0f1c7f1b71cd8e799907e180a72200273b61b152f07e209d35d76ba470584c0f198f1a1800952744c18ea4177dee14a168a70e5ef22782f20db6b23a4aed396517b029b403041a10ae1dd8dff4de7755edd20f691f56c53fbc08337455d53a5799ef3b2eb2e470603023e2a1dd033c88660ee78dec3e83da5459477173fc9a259fefd1fbb517a11ad6b93901ba02e802902bb38428a2a5888e6cbf3907483dc059970886874ec822a4fd2050a29127108dd54e9a55cb56912949c34163091c60d56928a77e66d24edcca87faa12912999dcff65befa0a7f21593c50719bfcff849e76bcb31e0ddffe2ce20a916368dc6a0203a3f9aa14bbf88f28c5e4d3c82924f1f43dafa1c5c74f83b2f9470638e209f14b6e298e934ce6bfe683b365580ce4bbafb792e180e601dda7d094d5273f9a2675e4825758c74bbe368bcfd26de8da4f77be4dfa20f49325209960a11db94fca6740e3af8f62bdb49711324ea7d6105ccad96f8c1bcc7aebd42c81c005eddaa1de01125a28414724c7c4efd9753a0bf5379cf2708341e63adf598f93f95dd85dd4652f9b9336f154dec66de6e08b6f922144746901495beb1eb1c9182105b0ed6f476fa3e44ef7e0842e4e2359f59efd31e36c3893a92b8b4499fc895994a5fc7baf4d7b6bbbfd21c2dc6a0929b0c5bb12b131352fc2b8dcf179dcbbd373cab206cb6779bbebc1f54f255426e6b18652e3cf2ea97c2c4c6216c5027bfa90e4b27cf71dee3af5152f1608be8630876b05a29884ff56f5075c2319bf5afb36fec78235aea9fb246c762ac5f7eccaa6449c5e36f3ffbe334a0c87328e445f87055dd97c0880e79d081063b0b26fe14c85515e2c9be301460130f1a1dd589ecdcc2a580b4f4fa34e3f3a457a419a8162b1be216d293ecb2bbeacaef6361865995d31385b15e4ef38a05feb8d01805259ff7d7577ac458e7304b3f82803aefa3ac8b23d783c3a25e04a50465c21410d138f01eac58337ae03b620e5faa65c29d8755bed7189c5e86aa77ff658c60de85ff84d506d847b79b37760ad71304dcf14617e578803f589bb83a9267a44f6e9babe7bfff4fa1097ae5bdac024897ffc0bc55e17894decee4a183cf9e81b032c76c3055a8011075be127ca19e6d73ab6d192372e8bfc6a08101262c985c09a87e387620045f73fdf8aa791c70952adc0312f33cc5df042905ae362a2fa48aa2c2bad82b0c7e52a537d180fc30a9282f383d07d1391b455562f1b54690cd55927ba755d80868fddd0ee7d14f691b5f5da3c218fcb0c80f347ab5de4614ac7548dfd9a42c57e0179362a2a842137ccc8f8868a9c646ac253c8cb2a5693b302dda1d6f7d6d97357e91b5ed946780553f7f7b04d9d0efd65a93208336144b2d7cc9fcc816f8f90114bcbe1c66e0cfe8dcfc6ec8609d7b54867e1485c78649bcf567766db1c2882035bc406564e5ddd815855f6a016249fe37542e93afccadb9735f01e064c6bab4cdfcda900531405e8c32e13c44f309598f7fa1cdd4770c04eebe9b4d68b514b50ab83513edb3ee3f2e0268d657f04e92492f10ef0648df9e638d37612562fd61496a966569cf0b47871b5828a112e775e467772d6b13d2373e04d465b91803f30cf86944e15cb12224e2fc014cb61d81e02f7ced220f7112dea7eb79383e880db6bf8486da00c2c7464b5eca7f3fe4e9be8a8fa22739bd4cd2f3a0db6c543a07e0a93ad4f20498f91187e118dfff53bef25b37c4bfb274c6759935eb1156d8940cd88f597c76d556df1c75810ad5481744a5f76eca2ef911af2b3a6c19a082bb16eb036d6003bf9891b06f844009da6652bffaaa3aa07fb07e3cb92e5926eb1079d2d2eb5c8e6da5f1d0fe24dc11e032704a82179f4b44dcc36e9af07ac539c341f6edcf4c28e637c7f675afbaeaa0ed6bfcc4e315ac7f26b147f90af5ac1703d236080f3f463b5e7ebbf4445bb666be1e2b3d7bf7dc2c26518bb64254f0d23e2bd1b35340a1429d93d210d54107ad001615c351b206668be8b7d43a8df5a6f7e13356a4da4ed868d1dfab05387290b033b7c00aef2b02563c8025b6c6fbc7e4e522b2e5f53e69a0294e76b56c7bf38897b8f25702703f33c7f34263b96cbe458b4d5a8617bca412ff85aa681ea82e21e022fcfccba98b6da705f65ae680c808d1f6632324613c5227a8aa7501b1ae932adad433dd549ac0ca35f1d71082171e41effbcd38186e7a49159d3ba6e50447f0526aa55c9d734bddad696fb886c9c3404bc22965c5645f9f361244de036013f5770f3b6dbe504beb3b7acea32f9cc5409ad6d706899492f10473de39d076e13279b0b0eba9e9c80268e4b716b938fa67d871043ab6e8b9804184d985f5a48e418001a85acd08eadef7c02d3afb87ebbaeeaee2b70b9caab628c95122cf3830bca1583f15c508905017a38db779c49b6dd48b31acfe27d850c3f3e6a824b7f7ea2a6624d4c6089580a9e993c067a6d908961ad4817483c0fd838212d91b5ab029a5fc246eb55911e91694dcac50fa3e8d50211aa104caf313438f04b72961e77c413a28b7eb7ea82e3f5c40b65b21a14f832a48998401076ca7035e1e6b9933d20f2aa92c9561e2a0a28b5ad193f045284392b766fcd542c1febc4361650010e43d2ed2f57b46abbe7515389109a18fe7aed95d93f50acf712ebf6aecf3248c0e9ba04584ed7317bdb80d85a45c694ffe4ab6a40d905e942e330846ba80b540979184e7a367d76b8ff68352a2f3cc06ec62c48887386bad98a0623799c7610b93dd831a26971a04f70fd5487b48665807f51ef34958f15dc949526452cd654e31fe165ade93521fb2f738955954fa0852dd99b45b4aa826b58f0bd87f817def08480ef1dfb7724b5b3dbe7e96bf24f86d8fc4318293c5b183e7d43a6e26f0ebcc44972e1cb632009961c2dfec8b686d5fee6c1436edbafbaefad98257e0451af689035f63d79009bada410cd50f086c70c92aa1a4ac42945d97fd52b0429cf6e420c79c1ec0fe9e542bdb1ee489e6353a0ec5a34f5769e2335ddea484befdab4eb643f2dc2947cb92164dfd0d6c7f3a8692cd07c763a572e6f4c02a37de79c61d2a3baa110595ff49ed4b60802547f071af8fa6f6ba8a853925f4a5ccbd6176a52a5457d0b239817861486e4b753c1c608de18e918ee36c94336d50f335d91101ee4740f9b58c0d1cac8b8371d3c109518b0505d9f67f3689a5568555d076a0522ae987bdfc68a544eef5656750422f2f89c12586c3920b53f77e52c8a4cdfaab5245344eca2a4e346fa752102d86a2abfe0e6fdf15fb4a2e33858d52cea13d0ee77474b68cae6356060714b4dc247d05f9f83e2bd06315ff51d222f6ac3cf61f5dc2d749e9c8b0b1e0c6fe5fdefe498fc28db30fed998a838bf473eb2d614bbfdb32783819704439f5f178b343d572cee7b2ce5789f2be17a7cef9c71e48cecd2f64df2413d93ea74551c91e1e6a05203f699ed020e277ab3908037bcbdaa9f5f6240632cef32e60d5167c17367ea6bff46bea4f47ce06facda26a7d78bfd813a71f4ce5ec4ec6b058208ad5452b0467eae1d2694589f250390101edb55201afd1fcc54dfe70be5286d7c8ddada22230d1c66c9009eef803b98efa7a209373ad227c86a14f06c1de651bc2091a629cb41c5d5cfb5a1d43346854483dc1984d3cddcfafcd2fada5bf9f2690b1e36b14981dc3b2bd6baa0849c38ab06094749a35045a91a968a268e33c690e82ef0ad5a56313a000c27cd9c882aefe575e86a5151b7fb6d2bc93a65c868a2dd370ee7efc70ada6c55bc1b61bbeab270f30ee3ee6f79fe6540a666ef54e31fd1e6834c5c8879a5a7a236f7e53fbd5f3f173e451db0f771e0c395c59f63e5f0615b2feec8629785560c1185569917a4b338c5f4f97bd694b3bf3fdc12a4111ee13fa1f7b83f3f2e733e90ed0a9a28394a0e511f42be6f1d294c22f89d4fa698da3b7d5ba3bc556cb517a6e4ad0769b0a92ccbb7733c400a0a40d2d67c55e2dedc504cc6c35613144c9911b08d618c9a81d29128adc100b1ccc40852cac4a6329265d9ed7d708aad41e609b77ddb062bae8e5ddb1a28c64afe0c3ee8d188bc6beec0511aa5bca7bdd03196d9bb5280383c2b41a7badbdd0ff9e0a6fc9ac20deeadc4d9ec03d0a776bec65061e25d591e29a846ede02573b616836a394355bffab86dd5c0be976618b3366148f9c04418e3d7a887969a66fbddb8f3fcccefc6e351b2a98c2a2615dfd726085b4c2ad53af33778ce7f8c77e818c605c89595e15ab5c5181a64f999b16ec0e118355fb4c8844676aeaaacca3b8e787acdf0284e6042d41d7d328e15dd262b4690ebeb8d7af0e512ab6fd9981c6729e9686bbfc37e6d21fc4bf5fe152e9f5bec778d7c755021de41b654251d51cc12e1eb7750be87b0c3b54d4a42965404c6ce94caaf1860d4df3fb9d19ff613bf8f0c13c092e82b7c8d9c723d2ad060b634415b6a0e486f4eed6ba8a66b60af5ee5ab355e2aadd82bc0c74b6394f94257394fd823ee246d4bf50e992afc2ef8c7b480ffc298f19722a2aff4e5cebcf6426760da14216a0cd2a6e41d0253132bb083094fc1532b674c5272bbc933106720ebab710e339a1366255742a3b91244bcb1cd2e767d7900b04539d330b3a0851ae97df16eecbe6dedbfb05c4a9314483382bd5e21bb6a3a31fe0f26a6cb8479ab97c1654b1538a7be9f088a2662120533ed11ff33e2eb067fd1f91a1b8caeb6f60527425e279336e2ea523fc2cdc02a96effb9469459dbb7c76701a9ef39c993a4250331084ec73e248721cc8614b2416ab0831edbebc83b8a6644540e715fe74823264ffec962101b6c2679c1f092c5decea19c4fed5fc737ad20a23223149e60fdd] */ 26 | -------------------------------------------------------------------------------- /plugins/xinz/cd.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author 咸鱼 3 | * @name cd 4 | * @team xinz 5 | * @version v1.0.0 6 | * @description 菜单功能 7 | * @rule ^(菜单)$ 8 | * @priority 10 9 | * @admin false 10 | * @public true 11 | * @encrypt false 12 | * @disable false 13 | * @classification ["功能插件"] 14 | */ 15 | 16 | module.exports = async s => { 17 | // 定义菜单内容 18 | const txt = ` 19 | `; 20 | 21 | // 回复菜单内容 22 | s.reply("——功能列表——\n" + 23 | "ChatGPT \n" + 24 | "城市天气 \n" + 25 | "二维码生成 \n" + 26 | "摸鱼日报 \n" + 27 | "热点趋势 \n" + 28 | "60秒读懂世界 \n" + 29 | "欢迎使用鑫仔机器人,友善使用,请勿滥用 ~㊗️🎊家人们发大财,心想事成,身体健康"); 30 | } 31 | -------------------------------------------------------------------------------- /plugins/xinz/ping.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author seven 3 | * @name ping 4 | * @team xinz 5 | * @version 1.17.0 6 | * @description ping/web测速插件 7 | * @rule ^ping (.+)$ 8 | * @admin false 9 | * @public true 10 | * @priority 9999 11 | * @disable false 12 | * @classification ["功能插件"] 13 | */ 14 | 15 | const { exec } = require('child_process'); 16 | const got = require('got'); 17 | const dns = require('dns'); 18 | 19 | /** 20 | * 插件入口,插件被触发时将运行该function 21 | * @param {Sender} sender 22 | */ 23 | module.exports = async sender => { 24 | // 处理 ping 指令 25 | const pingMatch = sender.getMsg().match(/^ping (.+)$/); 26 | if (pingMatch) { 27 | const address = pingMatch[1]; 28 | await pingAndWebTest(address, sender); 29 | } 30 | }; 31 | 32 | /** 33 | * ping 和 web 测试 34 | * @param {string} address - 要测试的地址 35 | * @param {Sender} sender - Sender对象 36 | */ 37 | async function pingAndWebTest(address, sender) { 38 | // 先进行 DNS 解析 39 | dns.lookup(address, async (err, ipAddress) => { 40 | if (err) { 41 | sender.reply(`无法解析 DNS: ${err.message}`); 42 | return; 43 | } 44 | 45 | // 输出 DNS 解析结果 46 | const dnsOutput = `dns: ${ipAddress}`; 47 | 48 | // 查询 IP 地址的地理位置 49 | let regionOutput = ''; 50 | try { 51 | const locationResponse = await got(`https://ipinfo.io/${ipAddress}/json`); 52 | const locationData = JSON.parse(locationResponse.body); 53 | const city = locationData.city || ''; 54 | const region = locationData.region || ''; 55 | const country = locationData.country || ''; 56 | 57 | // 生成地区字符串 58 | regionOutput = `地区: ${city ? city + ', ' : ''}${region ? region + ', ' : ''}${country}`; 59 | } catch (error) { 60 | regionOutput = `地区信息获取失败: ${error.message}`; 61 | } 62 | 63 | // 执行 ping 测试 64 | exec(`ping -c 4 ${ipAddress}`, async (error, stdout, stderr) => { 65 | if (error) { 66 | sender.reply(`ping ${address} 出现错误: ${stderr}`); 67 | return; 68 | } 69 | 70 | // 解析 ping 结果 71 | const lines = stdout.split('\n'); 72 | let packetsSent = 0; 73 | let packetsReceived = 0; 74 | let minDelay = Infinity; // 初始化最小延迟 75 | let pingOutput = ''; 76 | 77 | lines.forEach(line => { 78 | if (line.includes('bytes from')) { 79 | packetsSent++; 80 | const timeMatch = line.match(/time=(\d+\.?\d*) ms/); 81 | if (timeMatch) { 82 | const delay = parseFloat(timeMatch[1]); 83 | packetsReceived++; 84 | // 更新最小延迟 85 | if (delay < minDelay) { 86 | minDelay = delay; 87 | } 88 | } 89 | } else if (line.includes('packet loss')) { 90 | const lossMatch = line.match(/([\d.]+)% packet loss/); 91 | if (lossMatch) { 92 | const lossRate = lossMatch[1]; 93 | const successRate = (1 - (packetsReceived / packetsSent)) * 100; 94 | pingOutput += `丢包率: ${lossRate}%\n成功率: ${successRate.toFixed(2)}%`; 95 | } 96 | } 97 | }); 98 | 99 | // 如果没有成功的 ping 结果 100 | if (pingOutput === '') { 101 | sender.reply(`无法提取 IP 地址,请检查地址 ${address}`); 102 | return; 103 | } 104 | 105 | // 格式化输出 106 | const finalPingOutput = `ip: ${ipAddress}\n延迟: ${minDelay === Infinity ? '无' : minDelay + ' ms'}`; 107 | 108 | // 进行 web 测试 109 | const startTime = Date.now(); // 记录开始时间 110 | try { 111 | // 自动补全 https/http,优先使用 https 112 | const url = /^https?:\/\//i.test(address) ? address : `https://${address}`; 113 | const response = await got(url, { timeout: 5000, https: { rejectUnauthorized: false } }); // 设置超时时间 114 | const duration = Date.now() - startTime; // 计算耗时 115 | 116 | // 发送格式化的结果 117 | const replyMessage = `ping ${address}\n${dnsOutput}\n${regionOutput}\n${finalPingOutput}\n访问 ${url} 成功,用时: ${duration} ms\n发包: ${packetsSent}\n接收: ${packetsReceived}\n${pingOutput}`; 118 | sender.reply(replyMessage); 119 | } catch (error) { 120 | const errorMessage = `ping ${address}\n${dnsOutput}\n${regionOutput}\n${finalPingOutput}\n访问 ${url} 失败: ${error.message}\n发包: ${packetsSent}\n接收: ${packetsReceived}\n${pingOutput}`; 121 | sender.reply(errorMessage); 122 | } 123 | }); 124 | }); 125 | } 126 | -------------------------------------------------------------------------------- /plugins/xinz/pixiv.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name pixiv 3 | * @author seven&啊屁 4 | * @team xinz 5 | * @version 2.0 6 | * @description 完善版随机获取一组涩涩纸片人 首次使用请自行到web插件配置配置插件 机器自动科学可使用直连模式 7 | 需要存储图片则打开存储开关 图片自动存放到mod目录下 8 | * @rule ^zpr(18)?$ 9 | * @admin false 10 | * @public false 11 | * @priority 50 12 | */ 13 | 14 | // 常量定义 15 | const fs = require('fs'); 16 | const path = require('path'); 17 | const axios = require('axios'); 18 | 19 | // 常量定义 20 | const headers = { 21 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42" 22 | }; 23 | const modPath = path.join(__dirname, 'mod'); // mod 文件夹路径 24 | const zprPath = path.join(modPath, 'zpr'); // zpr 文件夹路径 25 | const zpr18Path = path.join(modPath, 'zpr18'); // zpr18 文件夹路径 26 | 27 | // 创建文件夹 28 | function createDirectoryIfNotExists(directory) { 29 | if (!fs.existsSync(directory)) { 30 | fs.mkdirSync(directory, { recursive: true }); 31 | } 32 | } 33 | 34 | // JSON Schema 定义 35 | const jsonSchema = BncrCreateSchema.object({ 36 | r18: BncrCreateSchema.boolean() 37 | .setTitle('R18内容') 38 | .setDescription('是否允许获取R18内容') 39 | .setDefault(false), 40 | pageHost: BncrCreateSchema.string() 41 | .setTitle('反代页面') 42 | .setDescription('配置反代页面地址') 43 | .setDefault('i.yuki.sh'), // 默认反代页面 44 | directConnect: BncrCreateSchema.boolean() 45 | .setTitle('是否直连') 46 | .setDescription('是否直接连接 API,而不是使用反代') 47 | .setDefault(false), 48 | saveImages: BncrCreateSchema.boolean() // 新增的开关 49 | .setTitle('保存图片') 50 | .setDescription('是否保存下载的图片') 51 | .setDefault(false), // 默认不保存 52 | }); 53 | 54 | const ConfigDB = new BncrPluginConfig(jsonSchema); 55 | 56 | // 确保创建分类文件夹 57 | createDirectoryIfNotExists(zprPath); 58 | createDirectoryIfNotExists(zpr18Path); 59 | 60 | // 获取图片 61 | async function getResult(sender, r18 = 0, axiosConfig = {}, pageHost = 'i.yuki.sh') { 62 | const size = "regular"; 63 | let description = "出错了,没有纸片人看了。"; 64 | let setuList = []; 65 | try { 66 | const { data } = await axios.get(`https://api.lolicon.app/setu/v2?num=1&r18=${r18}&size=${size}&size=original&proxy=${pageHost}&excludeAI=true`, { 67 | headers, 68 | timeout: 10000, 69 | ...axiosConfig 70 | }); 71 | const result = data.data; 72 | for (let i = 0; i < result.length; i++) { 73 | const { urls, pid, title, width, height } = result[i]; 74 | const imgURL = urls[size]; 75 | const originalURL = urls.original; 76 | 77 | // 添加图片到 setuList 78 | setuList.push({ 79 | type: 'photo', 80 | media: imgURL, 81 | caption: `**${title}**\nPID:[${pid}](https://www.pixiv.net/artworks/${pid})\n查看原图:[点击查看](${originalURL})\n原图尺寸:${width}x${height}`, 82 | has_spoiler: r18 === 1 83 | }); 84 | 85 | // 保存图片到指定文件夹(根据配置决定是否保存) 86 | if (ConfigDB.userConfig.saveImages) { 87 | await saveImageToFolder(imgURL, r18); 88 | } 89 | } 90 | } catch (error) { 91 | console.error(error); 92 | description = "连接二次元大门出错。。。"; 93 | } 94 | 95 | return { setuList, description }; 96 | } 97 | 98 | // 保存图片到文件夹 99 | async function saveImageToFolder(imageUrl, r18) { 100 | const folderPath = r18 === 1 ? zpr18Path : zprPath; // 根据 r18 选择文件夹 101 | const fileName = path.basename(imageUrl); // 从 URL 获取文件名 102 | const filePath = path.join(folderPath, fileName); // 文件完整路径 103 | 104 | // 下载并保存图片 105 | const writer = fs.createWriteStream(filePath); 106 | const response = await axios({ 107 | url: imageUrl, 108 | method: 'GET', 109 | responseType: 'stream', 110 | headers 111 | }); 112 | 113 | response.data.pipe(writer); 114 | return new Promise((resolve, reject) => { 115 | writer.on('finish', resolve); 116 | writer.on('error', reject); 117 | }); 118 | } 119 | 120 | // 发送图片并检查是否成功 121 | async function sendImages(sender, setuList) { 122 | for (const item of setuList) { 123 | let success = false; 124 | let attempts = 0; 125 | 126 | while (!success && attempts < 3) { // 最多尝试 3 次 127 | try { 128 | await sender.reply({ 129 | type: 'image', 130 | path: item.media, 131 | msg: item.caption, 132 | }); 133 | success = true; // 如果发送成功,设置 success 为 true 134 | } catch (error) { 135 | console.error(`发送图片失败,尝试重新获取: ${error}`); 136 | attempts++; 137 | // 如果发送失败,重新获取图片 138 | const { setuList: newSetuList, description } = await getResult(sender, item.has_spoiler ? 1 : 0, {}, 'i.yuki.sh'); 139 | if (newSetuList.length > 0) { 140 | item.media = newSetuList[0].media; // 更新媒体链接 141 | item.caption = newSetuList[0].caption; // 更新标题 142 | } else { 143 | await sender.reply(description); // 如果没有新图片,发送错误描述 144 | break; // 跳出重试循环 145 | } 146 | } 147 | } 148 | } 149 | } 150 | 151 | module.exports = async (sender) => { 152 | try { 153 | // 确保必要的包已安装 154 | await sysMethod.testModule(['axios'], { install: true }); 155 | await ConfigDB.get(); 156 | if (!Object.keys(ConfigDB.userConfig).length) { 157 | return await sender.reply('请先发送"修改zpr配置",或者前往前端web"插件配置"来完成插件首次配置'); 158 | } 159 | 160 | const config = ConfigDB.userConfig; 161 | let r18 = config.r18 ? 1 : 0; // 默认 R18 设置 162 | const msg = sender.getMsg(); 163 | 164 | // 检查命令并设置 R18 165 | if (msg.trim() === "zpr") { 166 | r18 = 0; // 设置为普通内容 167 | } else if (msg.trim() === "zpr18") { 168 | r18 = 1; // 设置为 R18 169 | } else { 170 | return await sender.reply("命令无效,请使用 'zpr' 或 'zpr18'。"); 171 | } 172 | 173 | // 提示消息 174 | await sender.reply("正在前往二次元,稍等片刻。。。"); 175 | 176 | const { setuList, description } = await getResult(sender, r18, {}, config.pageHost); 177 | 178 | if (setuList.length === 0) { 179 | await sender.reply(description); // 没有返回内容时发送提示 180 | return; 181 | } 182 | 183 | await sender.reply("传送中。。。"); 184 | // 发送图片 185 | await sendImages(sender, setuList); 186 | } catch (error) { 187 | console.error(error); // 记录错误日志 188 | await sender.reply(`发生错误:\n${error}`); 189 | } 190 | }; 191 | -------------------------------------------------------------------------------- /plugins/xinz/pixiv完整版.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name pixiv完整版 3 | * @author xinz&啊屁 4 | * @team xinz 5 | * @version 1.1.0 6 | * @description 更具分类搜索随机获取一组涩涩纸片人 也可自定义搜索 并且设置了代理或者直连模式直连速度视你的科学网速而定 图片可以存到本地分类存放 7 | * @rule ^zpr(18)? (all|illustration|manga|novel|animation|game|original|doujinshi)?$|^pp$|^zpr (.+)$ 8 | * @admin false 9 | * @public false 10 | * @priority 50 11 | */ 12 | 13 | // 常量定义 14 | const fs = require('fs'); 15 | const path = require('path'); 16 | const axios = require('axios'); 17 | 18 | // 常量定义 19 | const headers = { 20 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42" 21 | }; 22 | const modPath = path.join(__dirname, 'mod'); // mod 文件夹路径 23 | const ppPath = path.join(modPath, 'pp'); // pp 文件夹路径 24 | const zprPath = path.join(modPath, 'zpr'); // zpr 文件夹路径 25 | const zpr18Path = path.join(modPath, 'zpr18'); // zpr18 文件夹路径 26 | 27 | // 创建文件夹 28 | function createDirectoryIfNotExists(directory) { 29 | if (!fs.existsSync(directory)) { 30 | fs.mkdirSync(directory, { recursive: true }); 31 | } 32 | } 33 | 34 | // JSON Schema 定义 35 | const jsonSchema = BncrCreateSchema.object({ 36 | r18: BncrCreateSchema.boolean() 37 | .setTitle('R18内容') 38 | .setDescription('是否允许获取R18内容') 39 | .setDefault(false), 40 | pageHost: BncrCreateSchema.string() 41 | .setTitle('反代页面') 42 | .setDescription('配置反代页面地址') 43 | .setDefault('i.yuki.sh'), // 默认反代页面 44 | directConnect: BncrCreateSchema.boolean() 45 | .setTitle('是否直连') 46 | .setDescription('是否直接连接 API,而不是使用反代') 47 | .setDefault(false), 48 | saveImages: BncrCreateSchema.boolean() // 新增的开关 49 | .setTitle('保存图片') 50 | .setDescription('是否保存下载的图片') 51 | .setDefault(false), // 默认不保存 52 | }); 53 | 54 | const ConfigDB = new BncrPluginConfig(jsonSchema); 55 | 56 | // 确保创建分类文件夹 57 | createDirectoryIfNotExists(zprPath); 58 | createDirectoryIfNotExists(zpr18Path); 59 | createDirectoryIfNotExists(ppPath); // 确保 pp 文件夹存在 60 | 61 | // 发送使用说明 62 | async function sendUsageInstructions(sender) { 63 | const usageMessage = ` 64 | 使用方法: 65 | - \`zpr\` - 获取随机普通内容 66 | - \`zpr all\` - 获取所有类型的内容 67 | - \`zpr illustration\` - 获取插图类型的内容 68 | - \`zpr manga\` - 获取漫画类型的内容 69 | - \`zpr novel\` - 获取小说类型的内容 70 | - \`zpr animation\` - 获取动画类型的内容 71 | - \`zpr game\` - 获取游戏类型的内容 72 | - \`zpr original\` - 获取原创内容 73 | - \`zpr doujinshi\` - 获取同人志类型的内容 74 | 75 | - \`zpr18 <分类>\` - 获取 R18 内容(同上分类选项) 76 | - 例如:\`zpr18 manga\` - 获取随机 R18 漫画内容 77 | - \`zpr <自定义内容>\` - 根据自定义内容搜索图片 78 | - 例如:\`zpr cute cat\` - 搜索与“cute cat”相关的图片 79 | - \`pp\` - 查看此帮助信息 80 | `; 81 | await sender.reply(usageMessage); 82 | } 83 | 84 | // 保存图片到指定文件夹 85 | async function saveImageToFolder(imgURL, r18) { 86 | const folderPath = path.join(ppPath, r18 === 1 ? 'R18' : 'Normal'); 87 | createDirectoryIfNotExists(folderPath); 88 | const fileName = path.basename(imgURL); 89 | const filePath = path.join(folderPath, fileName); 90 | 91 | const writer = fs.createWriteStream(filePath); 92 | const response = await axios({ 93 | url: imgURL, 94 | method: 'GET', 95 | responseType: 'stream', 96 | }); 97 | response.data.pipe(writer); 98 | return new Promise((resolve, reject) => { 99 | writer.on('finish', resolve); 100 | writer.on('error', reject); 101 | }); 102 | } 103 | 104 | // 获取图片 105 | async function getResult(sender, r18 = 0, category = '', axiosConfig = {}, pageHost = 'i.yuki.sh', customQuery = '') { 106 | const size = "original"; // 使用原图 107 | let description = "出错了,没有纸片人看了。"; 108 | let setuList = []; 109 | try { 110 | const query = customQuery ? `&keyword=${encodeURIComponent(customQuery)}` : ''; 111 | const { data } = await axios.get(`https://api.lolicon.app/setu/v2?num=1&r18=${r18}&size=${size}&proxy=${pageHost}&excludeAI=true&category=${category}${query}`, { 112 | headers, 113 | timeout: 10000, 114 | ...axiosConfig 115 | }); 116 | 117 | // 检查返回的数据是否有效 118 | if (data && data.data && data.data.length > 0) { 119 | for (const result of data.data) { 120 | const { urls, pid, title, author, tags, r18, category: artCategory } = result; 121 | 122 | // 过滤掉包含 'r18' 或 'r-18' 标签的内容 123 | if (r18 === 0 && (tags.includes('r18') || tags.includes('R18') || tags.includes('R-18') || tags.includes('r-18'))) { 124 | continue; // 如果是普通内容且包含 R18 标签,跳过 125 | } 126 | const imgURL = urls.original; 127 | 128 | // 限制标签数量为最多5个 129 | let tagList = tags.slice(0, 5); // 取前5个标签 130 | 131 | // 将标签列表转换为字符串 132 | const tagString = tagList.join(','); // 使用中文逗号分隔 133 | 134 | // 添加图片信息 135 | setuList.push({ 136 | title: title, 137 | author: author, 138 | pid: pid, 139 | tags: tagString, 140 | category: artCategory || "未分类", 141 | imgURL: imgURL, 142 | has_spoiler: r18 === 1 143 | }); 144 | } 145 | } else { 146 | description = "没有找到相关内容,请稍后再试。"; 147 | } 148 | } catch (error) { 149 | console.error(error); 150 | description = "连接二次元大门出错。。。"; 151 | } 152 | 153 | return { setuList, description }; 154 | } 155 | 156 | // 发送图片信息 157 | async function sendImageInfo(sender, item) { 158 | const message = ` 159 | 本条数据来源于Lolisuki Api~ 160 | 标题:${item.title} 161 | 画师:${item.author} 162 | 画师id:${item.pid} 163 | Level:${item.has_spoiler ? 'R18' : '普通'} 164 | 分类:${item.category} 165 | 标签:${item.tags} 166 | 图片地址:${item.imgURL} 167 | `; 168 | await sender.reply(message); // 发送信息 169 | } 170 | 171 | // 发送图片并检查是否成功 172 | async function sendImage(sender, imgURL) { 173 | let success = false; 174 | let attempts = 0; 175 | 176 | while (!success && attempts < 3) { // 最多尝试 3 次 177 | try { 178 | await sender.reply({ 179 | type: 'image', 180 | path: imgURL, // 使用 imgURL 作为路径 181 | msg: `查看原图: [点击这里](${imgURL})` 182 | }); 183 | success = true; // 如果发送成功,设置 success 为 true 184 | } catch (error) { 185 | console.error(`发送图片失败,尝试重新获取: ${error}`); 186 | attempts++; 187 | // 等待一会儿再重试 188 | await new Promise(resolve => setTimeout(resolve, 1000)); 189 | } 190 | } 191 | } 192 | 193 | // 发送图片信息并单独发送图片 194 | async function sendImages(sender, setuList) { 195 | for (const item of setuList) { 196 | try { 197 | // 先发送图片信息 198 | await sendImageInfo(sender, item); 199 | // 然后发送图片 200 | await sendImage(sender, item.imgURL); 201 | } catch (error) { 202 | console.error(`发送图片或信息失败: ${error}`); 203 | } 204 | } 205 | } 206 | 207 | // 主处理函数 208 | module.exports = async (sender) => { 209 | try { 210 | // 确保必要的包已安装 211 | await sysMethod.testModule(['axios'], { install: true }); 212 | await ConfigDB.get(); 213 | 214 | if (!Object.keys(ConfigDB.userConfig).length) { 215 | const initialMsg = await sender.reply('请先发送"修改zpr配置",或者前往前端web"插件配置"来完成插件首次配置'); 216 | // 不删除 pp 命令的提示消息 217 | // 撤回提示消息 218 | setTimeout(() => { 219 | sender.delMsg(initialMsg.id); 220 | }, 30000); // 30秒后撤回 221 | return; 222 | } 223 | 224 | const config = ConfigDB.userConfig; 225 | let r18 = config.r18 ? 1 : 0; // 默认 R18 设置 226 | const msg = sender.getMsg().trim(); 227 | 228 | // 添加对 pp 命令的处理 229 | if (msg.toLowerCase() === 'pp') { 230 | await sendUsageInstructions(sender); 231 | return; // 不删除 pp 命令的提示消息 232 | } 233 | 234 | // 解析命令和分类 235 | const match = msg.match(/^(zpr(18)?)\s*(all|illustration|manga|novel|animation|game|original|doujinshi)?$|^zpr (.+)$/); 236 | if (!match) { 237 | const invalidCommandMsg = await sender.reply("命令无效,请使用 'zpr' 或 'zpr18',后面可以跟上分类(如:all、illustration、manga等)。"); 238 | // 撤回提示消息 239 | setTimeout(() => { 240 | sender.delMsg(invalidCommandMsg.id); 241 | }, 20000); // 20秒后撤回 242 | return; 243 | } 244 | 245 | const command = match[1]; // 捕获到的命令 246 | const category = match[3] ? match[3] : ''; // 捕获到的分类,默认为空 247 | const customQuery = match[4] ? match[4] : ''; // 捕获到的自定义内容,默认为空 248 | 249 | // 设置 R18 250 | if (command === "zpr") { 251 | r18 = 0; // 设置为普通内容 252 | } else if (command === "zpr18") { 253 | r18 = 1; // 设置为 R18 254 | } else if (command === "zpr" && customQuery) { 255 | r18 = 0; // 确保自定义内容为普通内容 256 | } else if (command === "zpr18" && customQuery) { 257 | r18 = 1; // 确保自定义内容为 R18 258 | } 259 | 260 | // 提示消息 261 | const loadingMsg = await sender.reply("正在前往二次元,稍等片刻。。。"); 262 | // 撤回提示消息 263 | setTimeout(() => { 264 | sender.delMsg(loadingMsg.id); 265 | }, 20000); // 20秒后撤回 266 | 267 | const { setuList, description } = await getResult(sender, r18, category, {}, config.pageHost, customQuery); 268 | 269 | if (setuList.length === 0) { 270 | const errorMsg = await sender.reply(description); // 没有返回内容时发送提示 271 | // 撤回错误消息 272 | setTimeout(() => { 273 | sender.delMsg(errorMsg.id); 274 | }, 20000); // 20秒后撤回 275 | return; 276 | } 277 | 278 | await sender.reply("传送中。。。"); 279 | // 发送图片信息和图片 280 | await sendImages(sender, setuList); // 传递存储图片的开关 281 | 282 | } catch (error) { 283 | console.error(error); // 记录错误日志 284 | const errorMsg = await sender.reply(`发生错误:\n${error}`); 285 | // 撤回错误消息 286 | setTimeout(() => { 287 | sender.delMsg(errorMsg.id); 288 | }, 20000); // 20秒后撤回 289 | } 290 | }; 291 | -------------------------------------------------------------------------------- /plugins/xinz/tgstickstoqq.js: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /plugins/xinz/二维码生成.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author seven 3 | * @name 二维码生成 4 | * @team xinz 5 | * @version 1.0.1 6 | * @description 二维码生成插件,通过自定义的API接口生成二维码 7 | * @rule ^(二维码生成) (.+)$ 8 | * @priority 99999 9 | * @admin false 10 | * @public true 11 | * @classification ["工具"] 12 | * @disable false 13 | */ 14 | const axios = require('axios'); 15 | const fs = require('fs'); 16 | const path = require('path'); 17 | 18 | const jsonSchema = BncrCreateSchema.object({ 19 | apiUrl: BncrCreateSchema.string() 20 | .setTitle('自定义API接口') 21 | .setDescription('设置用于生成二维码的API接口') 22 | .setDefault('https://api.vvhan.com/api/qr') 23 | }); 24 | 25 | const ConfigDB = new BncrPluginConfig(jsonSchema); 26 | 27 | module.exports = async s => { 28 | await sysMethod.testModule(['axios'], { install: true }); 29 | await ConfigDB.get(); 30 | 31 | const userConfig = ConfigDB.userConfig; 32 | if (!Object.keys(userConfig).length) { 33 | return s.reply('请先前往前端web界面配置插件。'); 34 | } 35 | 36 | const apiUrl = userConfig.apiUrl || 'https://api.vvhan.com/api/qr'; 37 | const textToEncode = s.param(2); 38 | 39 | try { 40 | const response = await axios.get(apiUrl, { 41 | params: { 42 | text: textToEncode 43 | }, 44 | responseType: 'stream' // 确保接收的是图片数据流 45 | }); 46 | 47 | const filePath = path.join(process.cwd(), `BncrData/public/qrcode_${Date.now()}.jpg`); 48 | const writer = fs.createWriteStream(filePath); 49 | 50 | response.data.pipe(writer); 51 | 52 | writer.on('finish', async () => { 53 | if (s.getFrom() === 'tgBot' || s.getFrom() === 'HumanTG') { 54 | await s.reply({ 55 | type: 'image', 56 | path: filePath, // 发送本地文件路径 57 | options: { 58 | contentType: 'image/jpeg' 59 | } 60 | }); 61 | } else { 62 | await s.reply({ 63 | type: 'image', 64 | path: response.request.res.responseUrl // 直接使用响应URL 65 | }); 66 | } 67 | 68 | // 删除本地文件 69 | fs.unlink(filePath, (err) => { 70 | if (err) { 71 | console.error('删除文件时发生错误:', err); 72 | } 73 | }); 74 | }); 75 | 76 | writer.on('error', async (err) => { 77 | console.error('写入文件时发生错误:', err); 78 | await s.reply('抱歉,发生了错误,请稍后再试。'); 79 | }); 80 | } catch (error) { 81 | if (error.code === 'EAI_AGAIN') { 82 | await s.reply('无法连接到二维码生成服务,请检查网络连接或稍后再试。'); 83 | } else { 84 | console.error('发生错误:', error); 85 | await s.reply('抱歉,发生了错误,请稍后再试。'); 86 | } 87 | } 88 | }; 89 | -------------------------------------------------------------------------------- /plugins/xinz/各种小姐姐视频.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name 各种小姐姐视频 3 | * @rule ^(吊带|丝滑舞蹈|小姐姐|美女穿搭|鞠婧祎|完美身材|杀猪饲料|章若楠|古风|玉足|慢摇|清纯|COS|纯情女高|萝莉|欲梦|甜妹|jk|热舞)$ 4 | * @admin false 5 | * @public true 6 | * @author zhu 7 | * @team xinz 8 | * @Github https://github.com/Mrzqd 9 | * @version 1.0.0 10 | * @disable false 11 | * @priority 100 12 | * @create_at 2023-08-06 16:42:58 13 | * @Description 暂无描述 14 | * @LastEditTime 2023-08-06 20:02:55 15 | * @Copyright Copyright (c) 2023 by zhu, All Rights Reserved. 16 | */ 17 | const request = require('util').promisify(require('request')); 18 | 19 | module.exports = async s => { 20 | var USER_AGENT_Browser = [ 21 | 'Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; OPPO A73 Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 HeyTapBrowser/10.7.29.2', 22 | 'Mozilla/5.0 (Linux; Android 7.1.1; vivo X20Plus A Build/NMF26X; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 VivoBrowser/10.2.10.0', 23 | 'Mozilla/5.0 (Linux; U; Android 7.0; zh-cn; HUAWEI CAZ-AL10 Build/HUAWEICAZ-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/12.0 Mobile Safari/537.36 COVC/045817', 24 | 'Mozilla/5.0 (Linux; Android 7.0; Redmi Note 4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Mobile Safari/537.36', 25 | 'Mozilla/5.0 (Linux; U; Android 7.1.2; zh-cn; vivo X9L Build/N2G47H) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/12.0 Mobile Safari/537.36 COVC/045817', 26 | 'Mozilla/5.0 (Linux; Android 7.1.2; vivo Y66i Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 VivoBrowser/10.2.10.0', 27 | 'Mozilla/5.0 (Linux; Android 7.1.2; vivo X9 Build/N2G47H; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 VivoBrowser/10.2.11.4', 28 | 'Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; vivo Y75A Build/N6F26Q) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/12.0 Mobile Safari/537.36 COVC/045816', 29 | 'Mozilla/5.0 (Linux; Android 7.1.1; OD105 Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.49 Mobile MQQBrowser/6.2 TBS/043409 Safari/537.36 MicroMessenger/6.5.13.1100 NetType/WIFI Language/zh_CN', 30 | 'Mozilla/5.0 (Linux; Android 7.1.1; OPPO A83t Build/N6F26Q; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.84 Mobile Safari/537.36 iThunder;thirdChannel_SHOUJIXUNLEI/7.32.0.7705 xl_cloud statusBarHeight/36 statusBarHeightDp/18.0', 31 | 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-CN; V1818CA Build/O11019) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 UCBrowser/13.6.0.1140 Mobile Safari/537.36', 32 | 'Mozilla/5.0 (Linux; Android 8.1.0; vivo Y83A Build/O11019; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 VivoBrowser/9.9.10.0', 33 | 'Mozilla/5.0 (Linux; U; Android 8.0.0; zh-CN; VIE-AL10 Build/HUAWEIVIE-AL10) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 UCBrowser/13.6.0.1140 Mobile Safari/537.36', 34 | 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; SM-N9600 Build/M1AJQ) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/12.0 Mobile Safari/537.36 COVC/045816', 35 | 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; DUB-AL00 Build/HUAWEIDUB-AL00) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/12.0 Mobile Safari/537.36 COVC/045817', 36 | 'Mozilla/5.0 (Linux; Android 8.0.0; BLN-AL10; HMSCore 6.1.0.313; GMSCore 17.4.55) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 HuaweiBrowser/11.1.5.320 Mobile Safari/537.36', 37 | 'Mozilla/5.0 (Linux; U; Android 8.0.0; zh-cn; Mi Note 2 Build/OPR1.170623.032) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.116 Mobile Safari/537.36 XiaoMi/MiuiBrowser/15.4.12', 38 | 'Mozilla/5.0 (Linux; U; Android 8.1.0; zh-cn; vivo X20A Build/OPM1.171019.011) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/12.0 Mobile Safari/537.36 COVC/045730', 39 | 'Mozilla/5.0 (Linux; Android 8.1.0; V1818A Build/OPM1.171019.026; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 VivoBrowser/9.8.53.0', 40 | 'Mozilla/5.0 (Linux; U; Android 8.0.0; zh-cn; SM-G9300 Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/12.0 Mobile Safari/537.36 COVC/045816', 41 | 'Mozilla/5.0 (Linux; U; Android 9; en-US; SM-G950F Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 UCBrowser/13.4.0.1306 Mobile Safari/537.36', 42 | 'Mozilla/5.0 (Linux; U; Android 9; zh-cn; RVL-AL09 Build/HUAWEIRVL-AL09) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/10.7 Mobile Safari/537.36', 43 | 'Mozilla/5.0 (Linux; U; Android 9; zh-cn; PDBM00 Build/PPR1.180610.011) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/77.0.3865.120 MQQBrowser/11.9 Mobile Safari/537.36 COVC/045709', 44 | 'Mozilla/5.0 (Linux; Android 9; INE-AL00; HMSCore 6.1.0.313; GMSCore 19.6.29) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.93 HuaweiBrowser/11.1.5.310 Mobile Safari/537.36', 45 | 'Mozilla/5.0 (Linux; Android 9; V1913A Build/P00610; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/87.0.4280.141 Mobile Safari/537.36 VivoBrowser/10.2.11.6', 46 | 'Mozilla/5.0 (Linux; Android 9; V1934A Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.91 Mobile Safari/537.36 iThunder;thirdChannel_SHOUJIXUNLEI/7.09.2.7123 xl_cloud', 47 | 'Mozilla/5.0 (Linux; U; Android 9; zh-cn; MI 6X Build/PKQ1.180904.001) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.116 Mobile Safari/537.36 XiaoMi/MiuiBrowser/15.4.12', 48 | 'Mozilla/5.0 (Linux; Android 9; Mi Note 3 Build/PKQ1.181007.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.99 Mobile Safari/537.36 iThunder;thirdChannel_SHOUJIXUNLEI/7.32.0.7705 xl_cloud statusBarHeight/66 statusBarHeightDp/24.0', 49 | 'Mozilla/5.0 (Linux; U; Android 9; zh-cn; LLD-AL30 Build/HONORLLD-AL30) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.72 MQQBrowser/12.0 Mobile Safari/537.36 COVC/045817', 50 | 'Mozilla/5.0 (Linux; Android 9; COL-AL10; HMSCore 6.1.0.305; GMSCore 17.7.85) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.93 HuaweiBrowser/11.1.4.301 Mobile Safari/537.36' 51 | ] 52 | let url = "" 53 | switch(s.getMsg()){ 54 | case "吊带": 55 | url = "http://api.yujn.cn/api/diaodai.php" 56 | break; 57 | case "丝滑舞蹈": 58 | url = "http://api.yujn.cn/api/shwd.php" 59 | break; 60 | case "小姐姐": 61 | url = "http://api.yujn.cn/api/zzxjj.php" 62 | break; 63 | case "美女穿搭": 64 | url = "http://api.yujn.cn/api/chuanda.php" 65 | break; 66 | case "鞠婧祎": 67 | url = "http://api.yujn.cn/api/jjy.php" 68 | break; 69 | case "完美身材": 70 | url = "http://api.yujn.cn/api/wmsc.php" 71 | break; 72 | case "杀猪饲料": 73 | url = "http://api.yujn.cn/api/shejie.php" 74 | break; 75 | case "章若楠": 76 | url = "http://api.yujn.cn/api/zrn.php" 77 | break; 78 | case "古风": 79 | url = "http://api.yujn.cn/api/hanfu.php" 80 | break; 81 | case "玉足": 82 | let a = "http://api.yujn.cn/api/jpmt.php" 83 | let b = "http://api.yujn.cn/api/yuzu.php" 84 | url = Math.random() > 0.5 ? a : b 85 | break; 86 | case "慢摇": 87 | url = "http://api.yujn.cn/api/manyao.php" 88 | break; 89 | case "清纯": 90 | url = "http://api.yujn.cn/api/qingchun.php" 91 | break; 92 | case "COS": 93 | url = "http://api.yujn.cn/api/COS.php" 94 | break; 95 | case "纯情女高": 96 | url = "http://api.yujn.cn/api/nvgao.php" 97 | break; 98 | case "萝莉": 99 | url = "http://api.yujn.cn/api/luoli.php" 100 | break; 101 | case "欲梦": 102 | url = "http://api.yujn.cn/api/ndym.php" 103 | break; 104 | case "甜妹": 105 | url = "http://api.yujn.cn/api/tianmei.php" 106 | break; 107 | case "jk": 108 | url = "http://api.yujn.cn/api/jksp.php" 109 | break; 110 | case "热舞": 111 | url = "http://api.yujn.cn/api/rewu.php" 112 | break; 113 | } 114 | opt = { 115 | url, 116 | headers:{ 117 | "User-Agent":USER_AGENT_Browser[Math.floor(Math.random() * USER_AGENT_Browser.length)] 118 | }, 119 | followRedirect:false 120 | } 121 | const r = await request(opt) 122 | // console.log(r.headers) 123 | s.reply({ 124 | type:"video", 125 | path:r.headers.location 126 | }) 127 | 128 | } 129 | -------------------------------------------------------------------------------- /plugins/xinz/命令大全.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Doraemon 3 | * @name 命令大全 4 | * @team xinz 5 | * @version 1.0.0 6 | * @description 无界命令大全 7 | * @rule ^(命令)$ 8 | * @admin true 9 | * @public true 10 | * @priority 1000 11 | * @classification ["功能插件"] 12 | */ 13 | 14 | 15 | var link = ':' //连接符 16 | 17 | var basics = { //基础命令 18 | 'set ? ? ?': '*设置存储桶-名-值', 19 | 'del ? ?': '*删除存储桶-名', 20 | 'get ? ?': '*获取存储桶-名的值', 21 | 'time': '*查看无界时间.', 22 | '我的id': '*我的ID.', 23 | '群id': '*群聊ID.', 24 | 'name': '*无界昵称.', 25 | 'bncr版本': '*查看bncr版本', 26 | '启动时间': '*查看启动时间', 27 | '监听该群': '*监听当前群聊', 28 | '屏蔽该群': '*不监听当前群聊', 29 | '回复该群': '*别人可以调戏你', 30 | '不回复该群': '*别人不可以调戏你', 31 | '拉黑这个b': '*别人私聊不可以调戏你', 32 | '拉出小黑屋': '*别人私聊可以调戏你', 33 | 'npm i ?': '*安装指定npm依赖', 34 | 'set groupWhitelist HumanTG:xxxx true': '*监听频道', 35 | 'set noReplylist HumanTG:xxxx true': '*不回复频道', 36 | '监听该群': '*监听群聊', 37 | 'set qq admin xxx': '*设置内置qq管理员 多个&连接', 38 | 'set tgBot admin xxx': '*设置tgBot管理员 多个&连接', 39 | 'set HumanTG admin xxx': '*设置人形管理员 多个&连接', 40 | 'set qqOutside admin xxx': '*设置外置qq管理员 多个&连接', 41 | 'set wxKeAImao admin xxx': '*设置可爱猫管理员 多个&连接', 42 | 'set wxQianxun admin xxx': '*设置千寻管理员 多个&连接', 43 | 'set wxXyo admin xxx': '*设置西瓜管理员 &连接', 44 | 'set wechaty admin xxx': '*设置wechaty管理员 &连接' 45 | } 46 | 47 | //插件入口 48 | module.exports = async s => { 49 | let newBasics = '' 50 | for (let [key, value] of Object.entries(basics)) { 51 | newBasics += `${key}${link}${value}\n` 52 | } 53 | s.delMsg(await s.reply(`基础命令:\n${newBasics}`), {wait: 10}); 54 | }; 55 | -------------------------------------------------------------------------------- /plugins/xinz/微信好友申请、拉群.js: -------------------------------------------------------------------------------- 1 | /**作者 2 | * @author 薛定谔的大灰机 3 | * @name 微信好友申请、拉群 4 | * @team xinz 5 | * @version 2.0.1 6 | * @rule 收到好友添加请求 7 | * @description 下方加群关键词可自定义 8 | * @rule ^(加|进)(.*)群$ 9 | * @platform wxXyo 10 | * @priority 99999 11 | * @admin false 12 | * @public true 13 | * @disable false 14 | * @classification ["功能插件"] 15 | */ 16 | 17 | /** 更新日志: 18 | 2.0.1: 19 | 1.如果将“关键词和群ID”填写为`@1234567890`,发送“加群”则会拉对应群(仅支持设置1个不带关键词) 20 | 2.没有按照1描述中的关键词留空,则发送“加群”则会将已储存的关键词列表发送出来: 21 | 22 | 2.0.0: 23 | 适配Bncr2.0,支持web页、修改无界配置命令修改配置 24 | */ 25 | 26 | /** 使用说明: 27 | 修改适配器【wxXyo.js】 28 | 29 | 1:原文件35行下方"添加"以下内容 30 | 31 | wxXyo.Bridge = {}; 32 | 33 | 2:原58行注释 34 | 3:原文件83行最后位置"添加"以下内容 35 | 36 | else if (body.Event === 'EventFrieneVerify') { // 好友申请 37 | wxXyo.Bridge.body = body; 38 | msgInfo = { 39 | userId: 'EventFrieneVerify', 40 | userName: '好友申请通知', 41 | groupId: '0', 42 | groupName: '', 43 | msg: '收到好友添加请求', 44 | msgId: '', 45 | type: "friend", 46 | }; 47 | } 48 | 49 | 4:原文件120-122行"替换"为以下内容 50 | 51 | case 'friend': 52 | body = { 53 | type: replyInfo.fromType, 54 | v1: replyInfo.json_msg.v1, 55 | v2: replyInfo.json_msg.v2, 56 | api: 'AgreeFriendVerify' 57 | }; 58 | break; 59 | default: 60 | body = replyInfo 61 | break; 62 | 63 | 5:重启无界 64 | 65 | */ 66 | 67 | const BCS = BncrCreateSchema 68 | const jsonSchema = BCS.object({ 69 | Agree: BCS.boolean().setTitle('同意好友申请开关').setDescription('开启则自动同意同意').setDefault(false), 70 | Mode: BCS.string().setTitle('邀请进群模式',).setDescription('请选择模式').setEnum(['InviteInGroup','InviteInGroupByLink']).setEnumNames(['直接拉群','发送卡片']).setDefault('InviteInGroupByLink'), 71 | Agree_keyword: BCS.string().setTitle('关键词同意').setDescription('空则全部同意').setDefault(''), 72 | AutoReply_keyword: BCS.string().setTitle('自动回复词').setDescription('空则不回复').setDefault(''), 73 | GroupId_keyword: BCS.array(BCS.string()).setTitle('关键词和群ID').setDescription('关键词和群ID用@隔开').setDefault(['交流@25214210457']), 74 | }); 75 | 76 | const ConfigDB = new BncrPluginConfig(jsonSchema); 77 | 78 | module.exports = async s => { 79 | await ConfigDB.get(); 80 | const CDB = ConfigDB.userConfig 81 | if (!Object.keys(CDB).length) { 82 | return await s.reply('请先发送"修改无界配置"来完成插件首次配置'); 83 | } 84 | 85 | if (s.getMsg() !== '收到好友添加请求') { 86 | Group() 87 | } else { 88 | Friend() 89 | } 90 | 91 | async function Group() { 92 | if (CDB.GroupId_keyword.length < 1) { 93 | return s.reply('未设置群ID') 94 | } 95 | let group_list = '已添加的群:\n ' 96 | for (let i = 0; i < CDB.GroupId_keyword.length; i++) { 97 | let Keyword = CDB.GroupId_keyword[i].split("@")[0] 98 | let GroupId = CDB.GroupId_keyword[i].split("@")[1] 99 | if (Keyword == s.param(2)) { 100 | await s.reply({ 101 | type: 'Group', 102 | msg: '邀请入群', 103 | api: CDB.Mode, 104 | group_wxid: GroupId + '@chatroom', 105 | friend_wxid: s.getUserId() 106 | }) 107 | return 108 | } else if(!s.param(2)) { 109 | group_list += `\n${i + 1}.${Keyword}` 110 | continue 111 | } else if ((CDB.GroupId_keyword.length - 1) == i) { 112 | s.reply("没有这个群哦[汗]") 113 | } 114 | } 115 | if (group_list) { 116 | s.reply(`${group_list}\n \n请发【加**群】`) 117 | } 118 | } 119 | 120 | async function Friend() { 121 | const body = s.Bridge.body.content 122 | if (body && s.getUserId() == 'EventFrieneVerify') { 123 | console.log(`收到【${body.from_name}】好友申请`) 124 | await main() 125 | } else { 126 | console.log('非真实好友申请,忽略') 127 | } 128 | 129 | async function main() { 130 | if (CDB.Agree) { 131 | await AgreeFriendVerify() 132 | if (!!CDB.AutoReply_keyword) { 133 | await AutoReply() 134 | } 135 | } 136 | } 137 | 138 | async function AgreeFriendVerify() { 139 | if (CDB.Agree_keyword) { 140 | if (CDB.Agree_keyword !== body.json_msg.content) { 141 | return console.log('暗号错误') 142 | } 143 | } 144 | await s.reply({ 145 | type: 'friend', 146 | msg: '同意好友申请', 147 | fromType: body.type, 148 | json_msg: body.json_msg 149 | }) 150 | console.log('已同意好友申请') 151 | } 152 | 153 | async function AutoReply() { 154 | if (CDB.AutoReply_keyword) { 155 | await s.reply({ 156 | type: 'text', 157 | userId: body.from_wxid, 158 | msg: CDB.AutoReply_keyword, 159 | }) 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /plugins/xinz/手动黑名单.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author 咸鱼&xinz 3 | * @name 手动黑名单 4 | * @team xinz 5 | * @version v1.1.0 6 | * @description 用户黑白名单管理系统|支持操作备注 7 | * @rule ^(拉黑|拉白) (\S+)$ 8 | * @priority 10 9 | * @admin true 10 | * @disable false 11 | */ 12 | 13 | module.exports = async s => { 14 | const [command, target] = [s.param(1), s.param(2)]; 15 | const platform = s.getFrom(); 16 | const userKey = `${platform}:${target}`; 17 | 18 | // 初始化数据库连接 19 | const blacklistDB = new BncrDB('userBlacklist'); 20 | const remarksDB = new BncrDB('userBlacklistRemarks'); 21 | 22 | try { 23 | // 命令处理器 24 | const handlers = { 25 | '拉黑': async () => { 26 | await Promise.all([ 27 | blacklistDB.set(userKey, true), 28 | remarksDB.set(userKey, '通过插件手动拉黑') 29 | ]); 30 | return `✅ 已封禁用户 [${target}]\n▸ 平台:${platform}\n▸ 备注:管理手动操作`; 31 | }, 32 | 33 | '拉白': async () => { 34 | const results = await Promise.allSettled([ 35 | blacklistDB.del(userKey), 36 | remarksDB.del(userKey) 37 | ]); 38 | const success = results.every(r => r.status === 'fulfilled'); 39 | return success ? `✅ 已解封用户 [${target}]` : '⚠️ 部分数据清除失败'; 40 | } 41 | }; 42 | 43 | // 执行操作并获取结果 44 | const message = await handlers[command](); 45 | const reply = await s.reply([ 46 | `🛡️ 用户状态变更通知`, 47 | `────────────────`, 48 | message, 49 | `────────────────`, 50 | `⏳ 本提示10秒后自动清除` 51 | ].join('\n')); 52 | 53 | // 自动清理消息 54 | setTimeout(() => s.delMsg(reply), 10 * 1000); 55 | 56 | } catch (error) { 57 | console.error('黑白名单操作失败:', error); 58 | const errorMsg = await s.reply([ 59 | '⚠️ 操作执行异常', 60 | '────────────────', 61 | `错误类型:${error.name}`, 62 | `详细信息:${error.message}` 63 | ].join('\n')); 64 | setTimeout(() => s.delMsg(errorMsg), 10 * 1000); 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /plugins/xinz/摸鱼.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Dswang 3 | * @name 摸鱼 4 | * @team xinz 5 | * @version 1.0.1 6 | * @description 摸鱼人日历,可以自定义接口 自用接口 https://api.vvhan.com/api/moyu 7 | * @rule ^(摸鱼)$ 8 | * @priority 99999 9 | * @admin false 10 | * @public true 11 | * @classification ["工具"] 12 | * @disable false 13 | */ 14 | const axios = require('axios'); 15 | const fs = require('fs'); 16 | const path = require('path'); 17 | 18 | const jsonSchema = BncrCreateSchema.object({ 19 | apiUrl: BncrCreateSchema.string() 20 | .setTitle('自定义API接口') 21 | .setDescription('设置用于生成摸鱼的API接口') 22 | .setDefault('https://api.vvhan.com/api/moyu') 23 | }); 24 | 25 | const ConfigDB = new BncrPluginConfig(jsonSchema); 26 | 27 | module.exports = async s => { 28 | await sysMethod.testModule(['axios'], { install: true }); 29 | await ConfigDB.get(); 30 | 31 | const userConfig = ConfigDB.userConfig; 32 | if (!Object.keys(userConfig).length) { 33 | return s.reply('请先前往前端web界面配置插件。'); 34 | } 35 | 36 | const apiUrl = userConfig.apiUrl || 'https://api.vvhan.com/api/moyu'; 37 | 38 | const today = new Date().toISOString().split('T')[0]; // 获取当前日期 39 | const filePath = path.join(process.cwd(), `BncrData/public/moyu_${today}.png`); // 使用 PNG 格式 40 | 41 | // 检查当天的文件是否已经存在 42 | if (fs.existsSync(filePath)) { 43 | // 文件存在,直接发送 44 | if (s.getFrom() === 'tgBot' || s.getFrom() === 'HumanTG') { 45 | await s.reply({ 46 | type: 'image', 47 | path: filePath, // 发送本地文件路径 48 | options: { 49 | contentType: 'image/png' // 设置 content-type 为 PNG 50 | } 51 | }); 52 | } else { 53 | await s.reply({ 54 | type: 'image', 55 | path: apiUrl 56 | }); 57 | } 58 | } else { 59 | // 文件不存在,重新生成并保存 60 | try { 61 | const response = await axios.get(apiUrl, { responseType: 'stream' }); 62 | 63 | const writer = fs.createWriteStream(filePath); 64 | 65 | response.data.pipe(writer); 66 | 67 | writer.on('finish', async () => { 68 | const imageUrl = response.request.res.responseUrl; 69 | 70 | if (s.getFrom() === 'tgBot' || s.getFrom() === 'HumanTG') { 71 | await s.reply({ 72 | type: 'image', 73 | path: filePath, // 发送本地文件路径 74 | options: { 75 | contentType: 'image/png' // 设置 content-type 为 PNG 76 | } 77 | }); 78 | } else { 79 | await s.reply({ 80 | type: 'image', 81 | path: imageUrl // 直接使用响应URL 82 | }); 83 | } 84 | 85 | // 删除前一天的文件 86 | const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0]; // 获取前一天日期 87 | const oldFilePath = path.join(process.cwd(), `BncrData/public/moyu_${yesterday}.png`); 88 | if (fs.existsSync(oldFilePath)) { 89 | fs.unlink(oldFilePath, (err) => { 90 | if (err) { 91 | console.error('删除文件时发生错误:', err); 92 | } 93 | }); 94 | } 95 | }); 96 | 97 | writer.on('error', async (err) => { 98 | console.error('写入文件时发生错误:', err); 99 | await s.reply('抱歉,发生了错误,请稍后再试。'); 100 | }); 101 | } catch (error) { 102 | if (error.code === 'EAI_AGAIN') { 103 | await s.reply('无法连接到摸鱼图片生成服务,请检查网络连接或稍后再试。'); 104 | } else { 105 | console.error('发生错误:', error); 106 | await s.reply('抱歉,发生了错误,请稍后再试。'); 107 | } 108 | } 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /plugins/xinz/星座运势.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author seven 3 | * @name 星座运势 4 | * @team xinz 5 | * @version 1.0.9 6 | * @description 星座运势插件,通过自定义API接口获取星座运势并自动匹配 自用接口https://api.vvhan.com/api/horoscope 7 | * @rule ^(星座) (.+) (今日运势|明日运势|一周运势|月运势)$ 8 | * @rule ^(今日运势|明日运势|一周运势|月运势) (.+)$ 9 | * @priority 99999 10 | * @admin false 11 | * @public true 12 | * @classification ["工具"] 13 | * @disable false 14 | */ 15 | 16 | const axios = require('axios'); 17 | 18 | const jsonSchema = BncrCreateSchema.object({ 19 | apiUrl: BncrCreateSchema.string() 20 | .setTitle('自定义API接口') 21 | .setDescription('设置用于获取星座运势的API接口') 22 | .setDefault('https://api.vvhan.com/api/horoscope') 23 | }); 24 | 25 | // 配置管理器 26 | const ConfigDB = new BncrPluginConfig(jsonSchema); 27 | 28 | module.exports = async s => { 29 | console.log('星座运势插件已加载'); 30 | 31 | await sysMethod.testModule(['axios'], { install: true }); 32 | await ConfigDB.get(); 33 | 34 | const userConfig = ConfigDB.userConfig; 35 | if (!Object.keys(userConfig).length) { 36 | return s.reply('请先前往前端web界面配置插件。'); 37 | } 38 | 39 | const apiUrl = userConfig.apiUrl || 'https://api.vvhan.com/api/horoscope'; 40 | 41 | // 解析命令 42 | const command = s.param(3) || s.param(2); // 获取用户输入的命令 43 | const starSign = s.param(2) || s.param(1); // 获取用户输入的星座 44 | const isCustomCommand = ['今日运势', '明日运势', '一周运势', '月运势'].includes(command); 45 | 46 | console.log(`获取的命令: ${command}, 星座: ${starSign}`); 47 | 48 | // 星座与对应英文小写的映射 49 | const starSignsMap = { 50 | '白羊座': 'aries', 51 | '金牛座': 'taurus', 52 | '双子座': 'gemini', 53 | '巨蟹座': 'cancer', 54 | '狮子座': 'leo', 55 | '处女座': 'virgo', 56 | '天秤座': 'libra', 57 | '天蝎座': 'scorpio', 58 | '射手座': 'sagittarius', 59 | '摩羯座': 'capricorn', 60 | '水瓶座': 'aquarius', 61 | '双鱼座': 'pisces' 62 | }; 63 | 64 | // 运势类型映射 65 | const timeMap = { 66 | '今日运势': 'today', 67 | '明日运势': 'nextday', 68 | '一周运势': 'week', 69 | '月运势': 'month' 70 | }; 71 | 72 | // 检查星座的有效性 73 | if (!Object.keys(starSignsMap).includes(starSign)) { 74 | return s.reply('请提供有效的星座。'); 75 | } 76 | 77 | const type = starSignsMap[starSign]; 78 | 79 | // 存储运势结果 80 | let results = []; 81 | 82 | try { 83 | // 处理命令 84 | if (isCustomCommand) { 85 | const time = timeMap[command]; 86 | 87 | const response = await axios.get(apiUrl, { 88 | params: { 89 | type: type, 90 | time: time 91 | } 92 | }); 93 | 94 | console.log(`API 响应 (${command}):`, response.data); 95 | 96 | // 处理 API 响应 97 | if (response.data.success) { 98 | const fortuneData = response.data.data; 99 | 100 | if (fortuneData) { 101 | const title = fortuneData.title; 102 | const time = fortuneData.time; 103 | const shortComment = fortuneData.shortcomment || "暂无简要信息"; 104 | const fortuneText = fortuneData.fortunetext.all || "暂无详细信息"; 105 | 106 | results.push(`**${title}的${command}**:\n时间: ${time}\n简要: ${shortComment}\n详细: ${fortuneText}`); 107 | } else { 108 | results.push(`**${command}**: 暂无内容`); 109 | } 110 | } else { 111 | results.push(`抱歉,无法获取${command}。`); 112 | } 113 | } else { 114 | // 如果是单独的运势命令, 例如 "今日运势 双鱼座" 115 | const time = timeMap[command]; 116 | 117 | const response = await axios.get(apiUrl, { 118 | params: { 119 | type: starSignsMap[starSign], 120 | time: time 121 | } 122 | }); 123 | 124 | console.log(`API 响应 (${command}):`, response.data); 125 | 126 | // 处理 API 响应 127 | if (response.data.success) { 128 | const fortuneData = response.data.data; 129 | 130 | if (fortuneData) { 131 | const title = fortuneData.title; 132 | const time = fortuneData.time; 133 | const shortComment = fortuneData.shortcomment || "暂无简要信息"; 134 | const fortuneText = fortuneData.fortunetext.all || "暂无详细信息"; 135 | 136 | results.push(`**${title}的${command}**:\n时间: ${time}\n简要: ${shortComment}\n详细: ${fortuneText}`); 137 | } else { 138 | results.push(`**${command}**: 暂无内容`); 139 | } 140 | } else { 141 | results.push(`抱歉,无法获取${command}。`); 142 | } 143 | } 144 | 145 | // 返回所有运势结果 146 | await s.reply(results.join('\n\n')); 147 | } catch (error) { 148 | console.error('发生错误:', error); 149 | await s.reply('抱歉,发生了错误,请稍后再试。'); 150 | } 151 | }; 152 | -------------------------------------------------------------------------------- /plugins/xinz/每天60s.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author seven 3 | * @name 每天60s 4 | * @team xinz 5 | * @version 1.0.0 6 | * @platform tgBot qq ssh HumanTG wxQianxun wxXyo wechaty 7 | * @description 60秒读懂世界 8 | * @rule ^(60s)$ 9 | * @admin false 10 | * @public true 11 | * @priority 99999 12 | * @disable false 13 | * @cron 0 10 9 * * * 14 | * @classification ["功能插件"] 15 | */ 16 | 17 | module.exports = async s => { 18 | //填写定时要发送的平台 19 | var platform = 'qq' 20 | //填写定时要发送的群id 21 | var groupid='376940436' 22 | //填写定时要发送的用户id 23 | var userid='536576656e' 24 | //s.delMsg(s.getMsgId(), { wait: 1}) 25 | const axios = require('axios').default 26 | let url = "https://api.vvhan.com/api/60s" 27 | let res = await axios.request({ 28 | url: url, 29 | family: 4, 30 | //timeout: 1000, 31 | method: 'get', 32 | json: true 33 | }) 34 | //console.log(res.data) 35 | let title = res.data.title.replace(/在这里每天/g, "") 36 | let todaydate = res.data.time 37 | let datalength = res.data.data.size 38 | //let info = res.data.data 39 | let info = await displayDataWithIndex(res.data.data) 40 | let result = res.data 41 | let msg_60s = "【" + todaydate + "】" + title + "\n" + info 42 | //console.log(info) 43 | //console.log(res.data) 44 | if(s.getFrom() == "cron") { 45 | sysMethod.push({ 46 | platform: platform, 47 | groupId: groupid, 48 | userId: userid, 49 | msg: msg_60s 50 | }) 51 | } 52 | else { 53 | await s.reply(msg_60s) 54 | //console.log(msg_60s) 55 | } 56 | }; 57 | async function displayDataWithIndex(data) { 58 | let result = ""; 59 | data.forEach((item, index) => { 60 | // 去除字符串中的 "undefined" 61 | item = item.replace(/undefined/g, ""); 62 | result += `${index + 1}. ${item}\n`; 63 | }); 64 | return result; 65 | } 66 | -------------------------------------------------------------------------------- /plugins/xinz/消息转发.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 监听某个人或群,当触发关键字,转发到指定目的地 3 | * @team xinz 4 | * @author seven 5 | * @platform tgBot qq ssh HumanTG wxQianxun wxXyo wechaty 6 | * @version v2.0.0 7 | * @name 消息转发 8 | * @rule [\s\S]+ 9 | * @priority 100000 10 | * @admin false 11 | * @disable false 12 | * @public true 13 | * @classification ["功能插件"] 14 | */ 15 | 16 | /* 17 | 这个插件会拦截所有消息进行处理,控制台日志会由此增多 18 | */ 19 | 20 | /* 配置 */ 21 | 22 | const jsonSchema = BncrCreateSchema.object({ 23 | configs: BncrCreateSchema.array(BncrCreateSchema.object({ 24 | enable: BncrCreateSchema.boolean().setTitle('启用').setDescription('是否启用').setDefault(true), 25 | listen: BncrCreateSchema.object({ 26 | id: BncrCreateSchema.string().setTitle('ID').setDescription(`群号或个人id`).setDefault(""), 27 | type: BncrCreateSchema.string().setTitle('类型').setDescription('群或个人2选1').setEnum(["userId", "groupId"]).setEnumNames(['个人', '群']).setDefault("groupId"), 28 | from: BncrCreateSchema.string().setTitle('平台').setDescription(`填写适配器`).setDefault(''), 29 | }).setTitle('监听来源').setDescription('配置监听来源').setDefault({}), 30 | rule: BncrCreateSchema.array(BncrCreateSchema.string()).setTitle('触发关键词,填写“任意”则无视关键字').setDefault(['任意']), 31 | toSender: BncrCreateSchema.array(BncrCreateSchema.object({ 32 | id: BncrCreateSchema.string().setTitle('ID').setDescription(`群号或个人id`).setDefault(""), 33 | type: BncrCreateSchema.string().setTitle('类型').setDescription('群或个人2选1').setEnum(["userId", "groupId"]).setEnumNames(['个人', '群']).setDefault("groupId"), 34 | from: BncrCreateSchema.string().setTitle('平台').setDescription(`填写适配器`).setDefault(''), 35 | })).setTitle('转发目的地').setDescription('转发到哪儿,支持多个').setDefault([]), 36 | replace: BncrCreateSchema.array(BncrCreateSchema.object({ 37 | old: BncrCreateSchema.string().setTitle('旧消息').setDescription(`需要替换的旧消息`).setDefault(""), 38 | new: BncrCreateSchema.string().setTitle('新消息').setDescription(`替换后的新消息`).setDefault(""), 39 | })).setTitle('替换信息').setDescription('需要替换的消息,支持多个').setDefault([]), 40 | addText: BncrCreateSchema.string().setTitle('自定义尾巴').setDescription(`尾部增加的消息,“\\n”换行`).setDefault("") 41 | })).setTitle('消息转发').setDescription(`监听某个人或群,当触发关键字,转发到指定目的地`).setDefault([]) 42 | }); 43 | const ConfigDB = new BncrPluginConfig(jsonSchema); 44 | 45 | /* main */ 46 | module.exports = async s => { 47 | try { 48 | await ConfigDB.get(); 49 | if (!Object.keys(ConfigDB.userConfig).length) { 50 | console.log('请先发送"修改无界配置",或者前往前端web"插件配置"来完成插件首次配置'); 51 | return 'next'; 52 | } 53 | const configs = ConfigDB.userConfig.configs.filter(o => o.enable) || []; 54 | 55 | /* 异步处理 */ 56 | await new Promise(resolve => { 57 | const msgInfo = s.msgInfo; 58 | for (const config of configs) { 59 | let msgStr = msgInfo.msg, 60 | open = false; 61 | if (msgInfo.from !== config.listen.from || msgInfo[config.listen.type] !== config.listen.id) continue; 62 | for (const rule of config.rule) { 63 | if (msgInfo.msg.includes(rule) || rule == "任意") { 64 | open = true; 65 | config.replace.forEach(e => (msgStr = msgStr.replace(new RegExp(e.old, 'g'), e.new))); 66 | break; 67 | } 68 | } 69 | if (!open) break; 70 | const msgToForward = `${msgStr}${config.addText.replaceAll('\\n', '\n')}`; 71 | config.toSender.forEach(e => { 72 | let obj = { 73 | platform: e.from, 74 | msg: msgToForward, 75 | }; 76 | obj[e.type] = e.id; 77 | sysMethod.push(obj); // 这里需要确保 sysMethod.push 方法存在并按照你的需求进行处理 78 | }); 79 | } 80 | resolve(); 81 | }); 82 | } 83 | catch (e) { 84 | console.debug(e) 85 | } 86 | return 'next'; 87 | }; 88 | -------------------------------------------------------------------------------- /plugins/xinz/热点趋势.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Dswang 3 | * @name 热点趋势 4 | * @team xinz & SmartAI 5 | * @version 1.0.1 6 | * @description 获取网络热点 7 | * @platform tgBot qq ssh HumanTG wxQianxun wxXyo wechaty 8 | * @rule ^((抖音|微博|知乎|bilibili|bili|哔哩|哔哩哔哩)热搜)$ 9 | * @admin false 10 | * @public true 11 | * @priority 9999 12 | * @classification ["工具"] 13 | */ 14 | 15 | const axios = require('axios'); 16 | 17 | module.exports = async s => { 18 | await sysMethod.testModule(['axios', 'input'], { install: true }); 19 | 20 | const apiEndpoints = { 21 | '抖音热搜': 'https://tenapi.cn/v2/douyinhot', 22 | '微博热搜': 'https://tenapi.cn/v2/weibohot', 23 | '知乎热搜': 'https://tenapi.cn/v2/zhihuhot', 24 | 'bilibili热搜': 'https://tenapi.cn/v2/bilihot', 25 | 'bili热搜': 'https://tenapi.cn/v2/bilihot', 26 | '哔哩热搜': 'https://tenapi.cn/v2/bilihot', 27 | '哔哩哔哩热搜': 'https://tenapi.cn/v2/bilihot' 28 | }; 29 | 30 | const platform = s.getMsg().match(/(抖音|微博|知乎|bilibili|bili|哔哩|哔哩哔哩)热搜/)[0]; 31 | const apiUrl = apiEndpoints[platform]; 32 | 33 | if (!apiUrl) { 34 | await s.reply('不支持的平台'); 35 | return; 36 | } 37 | 38 | try { 39 | // 调用接口获取热搜数据 40 | const response = await axios.get(apiUrl); 41 | 42 | // 检查响应数据是否有效 43 | if (response.data.code !== 200 || !response.data.data) { 44 | throw new Error('无效的响应数据'); 45 | } 46 | 47 | const hotList = response.data.data; 48 | 49 | // 只取前10条数据 50 | const top10HotList = hotList.slice(0, 10); 51 | 52 | // 格式化输出消息 53 | let replyMessage = `${platform}前10名:\n`; 54 | 55 | top10HotList.forEach((item, index) => { 56 | // 检查是否有热度数据 57 | if (item.hot) { 58 | replyMessage += `${index + 1}. 热搜词:${item.name}\n热度:${item.hot}\n链接:${item.url}\n\n`; 59 | } else { 60 | replyMessage += `${index + 1}. 热搜词:${item.name}\n链接:${item.url}\n\n`; 61 | } 62 | }); 63 | 64 | // 回复消息给用户 65 | await s.reply(replyMessage); 66 | } catch (error) { 67 | await s.reply('获取热搜失败,请稍后再试。'); 68 | console.error(error); 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /plugins/xinz/状态查询.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author seven 3 | * @name 状态查询 4 | * @team xinz 5 | * @version 1.0.0 6 | * @description 本机资源查询 7 | * @rule ^(运行状态)$ 8 | * @admin true 9 | * @public false 10 | * @priority 9999 11 | * @disable false 12 | */ 13 | 14 | const os = require('os'); 15 | const { execSync } = require('child_process'); 16 | const si = require('systeminformation'); // 引入 systeminformation 库 17 | 18 | const delMsgTime = 5000; // 设置删除消息的时间为 5000 毫秒 19 | 20 | /** 21 | * 获取系统运行时间 22 | */ 23 | function getUptime() { 24 | const uptimeInSeconds = os.uptime(); 25 | const hours = Math.floor(uptimeInSeconds / 3600); 26 | const minutes = Math.floor((uptimeInSeconds % 3600) / 60); 27 | return { hours, minutes }; 28 | } 29 | 30 | /** 31 | * 获取系统负载信息 32 | */ 33 | function getLoadInfo() { 34 | const load = os.loadavg(); 35 | return { 36 | '1分钟负载': load[0].toFixed(2), 37 | '5分钟负载': load[1].toFixed(2), 38 | '15分钟负载': load[2].toFixed(2), 39 | '最大负载': os.cpus().length, 40 | '负载限制': os.cpus().length * 2, 41 | '安全负载': os.cpus().length * 1.5, 42 | }; 43 | } 44 | 45 | /** 46 | * 获取活动进程数量 47 | */ 48 | function getActiveProcessesCount() { 49 | const output = execSync('ps -e | wc -l').toString().trim(); 50 | return parseInt(output, 10); // 返回活动进程数量 51 | } 52 | 53 | /** 54 | * 获取CPU信息 55 | */ 56 | async function getCpuInfo() { 57 | const cpus = os.cpus(); 58 | const cpuModel = cpus[0].model; 59 | const cpuUsage = await getCpuUsage(); 60 | const cpuSpeed = cpus[0].speed; // CPU速度 61 | const cpuTemperature = await getCpuTemperature(); // 获取CPU温度 62 | return { 63 | 'CPU型号': cpuModel, 64 | 'CPU使用率': `${cpuUsage ? cpuUsage.toFixed(2) : '未获取'}%`, 65 | 'CPU温度': cpuTemperature ? `${cpuTemperature} °C` : '未获取', // CPU温度 66 | 'CPU具体运行状态': { 67 | '速度': cpuSpeed, 68 | '总进程数': process.pid, 69 | '活动进程数': getActiveProcessesCount(), 70 | '核心数': cpus.length, 71 | }, 72 | }; 73 | } 74 | 75 | /** 76 | * 获取CPU使用率 77 | */ 78 | async function getCpuUsage() { 79 | try { 80 | const cpuData = await si.currentLoad(); 81 | return cpuData.currentLoad; // 返回当前 CPU 使用率 82 | } catch (error) { 83 | console.error('获取CPU使用率失败:', error); 84 | return null; // 返回null表示未获取到使用率 85 | } 86 | } 87 | 88 | /** 89 | * 获取CPU温度 90 | */ 91 | async function getCpuTemperature() { 92 | try { 93 | const data = await si.cpuTemperature(); 94 | return data.main; // 返回主温度 95 | } catch (error) { 96 | console.error('获取CPU温度失败:', error); 97 | return null; // 返回null表示未获取到温度 98 | } 99 | } 100 | 101 | /** 102 | * 获取内存信息 103 | */ 104 | function getMemoryInfo() { 105 | const totalMemory = os.totalmem(); 106 | const freeMemory = os.freemem(); 107 | const usedMemory = totalMemory - freeMemory; 108 | return { 109 | '总内存': `${(totalMemory / (1024 ** 3)).toFixed(2)} GB`, 110 | '可用内存': `${(freeMemory / (1024 ** 3)).toFixed(2)} GB`, 111 | '已用内存': `${(usedMemory / (1024 ** 3)).toFixed(2)} GB`, 112 | }; 113 | } 114 | 115 | /** 116 | * 获取磁盘信息 117 | */ 118 | function getDiskInfo() { 119 | const diskInfo = execSync('df -h').toString(); 120 | return diskInfo.split('\n').slice(1).map(line => { 121 | const parts = line.split(/\s+/); 122 | return { 123 | '文件系统': parts[0], 124 | '总大小': parts[1], 125 | '已用': parts[2], 126 | '可用': parts[3], 127 | '使用率': parts[4], 128 | }; 129 | }); 130 | } 131 | 132 | /** 133 | * 获取系统信息并发送 134 | */ 135 | async function getSystemInfo(s) { 136 | const uptime = getUptime(); 137 | const loadInfo = getLoadInfo(); 138 | const cpuInfo = await getCpuInfo(); 139 | const memoryInfo = getMemoryInfo(); 140 | const diskInfo = getDiskInfo(); 141 | 142 | // 格式化输出 143 | const systemInfo = ` 144 | 运行时间: ${uptime.hours}小时 ${uptime.minutes}分钟 145 | 146 | 系统信息: 147 | 版本: ${process.version} 148 | 操作系统: ${os.type()} ${os.release()} 149 | 150 | 系统负载信息: 151 | 1分钟负载: ${loadInfo['1分钟负载']} 152 | 5分钟负载: ${loadInfo['5分钟负载']} 153 | 15分钟负载: ${loadInfo['15分钟负载']} 154 | 最大负载: ${loadInfo['最大负载']} 155 | 负载限制: ${loadInfo['负载限制']} 156 | 安全负载: ${loadInfo['安全负载']} 157 | 158 | CPU信息: 159 | CPU型号: ${cpuInfo['CPU型号']}(速度: ${cpuInfo['CPU具体运行状态']['速度']} MHz)(核心数: ${cpuInfo['CPU具体运行状态']['核心数']}) 160 | CPU使用率: ${cpuInfo['CPU使用率']} 161 | CPU温度: ${cpuInfo['CPU温度']} 162 | CPU具体运行状态: 163 | 总进程数: ${cpuInfo['CPU具体运行状态']['总进程数']} 164 | 活动进程数: ${cpuInfo['CPU具体运行状态']['活动进程数']} 165 | 166 | 内存信息: 167 | 总内存: ${memoryInfo['总内存']} 168 | 可用内存: ${memoryInfo['可用内存']} 169 | 已用内存: ${memoryInfo['已用内存']} 170 | 171 | 磁盘信息: 172 | 文件系统: ${diskInfo[0]['文件系统']} 173 | 总大小: ${diskInfo[0]['总大小']} 174 | 已用: ${diskInfo[0]['已用']} 175 | 可用: ${diskInfo[0]['可用']} 176 | `; 177 | 178 | const replyid = await s.reply(systemInfo); 179 | 180 | // 设置删除回复消息的延迟 181 | setTimeout(async () => { 182 | try { 183 | await s.delMsg(replyid); // 撤回刚刚发的消息 184 | console.log('消息撤回成功'); 185 | } catch (error) { 186 | console.error('撤回消息失败:', error); 187 | } 188 | }, delMsgTime); 189 | } 190 | 191 | // 插件入口,处理指令“运行状态” 192 | module.exports = async s => { 193 | // 假设用户输入的指令 194 | const command = '运行状态'; 195 | 196 | // 检查指令是否为“运行状态” 197 | if (command === '运行状态') { 198 | await getSystemInfo(s); 199 | } else { 200 | await s.reply('无效指令,请发送“运行状态”以获取系统信息。'); 201 | } 202 | }; 203 | -------------------------------------------------------------------------------- /plugins/xinz/疯狂星期四.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author seven 3 | * @name 疯狂星期四 4 | * @team xinz&咸鱼 5 | * @version 1.0.0 6 | * @description lsp 7 | * @rule ^(疯狂星期四)$ 8 | * @admin false 9 | * @public false 10 | * @priority 99999 11 | * @disable false 12 | * @cron 16 3 10 * * * 13 | */ 14 | 15 | const axios = require('axios').default; 16 | 17 | module.exports = async s => { 18 | // 定义推送列表,包含多个平台和QQ群号 19 | const PushList = [ 20 | { 21 | groupId: '834396229', 22 | platform: 'qq' 23 | }, 24 | { 25 | groupId: '671685583', 26 | platform: 'qq' 27 | }, 28 | { 29 | groupId: '420035660', 30 | platform: 'qq' 31 | }, 32 | { 33 | groupId: '960566450', 34 | platform: 'tgbot' 35 | }, 36 | { 37 | groupId: '671685583', 38 | platform: 'HumanTG' 39 | } 40 | // 可以继续添加其他平台和群组 41 | ]; 42 | 43 | // 删除触发消息 44 | s.delMsg(s.getMsgId(), { wait: 1 }); 45 | 46 | // 获取当前日期的星期几 47 | const weekUrl = "https://www.mxnzp.com/api/holiday/single/20181121?ignoreHoliday=false&app_id=cwclhnmhw2vgojdo&app_secret=WkhnOXhKMlhOaFo5WndsNE5QUTRRdz09"; 48 | let weekRes; 49 | 50 | try { 51 | weekRes = await axios.get(weekUrl); 52 | } catch (error) { 53 | console.error("获取星期几失败:", error); 54 | return s.reply("获取星期几失败,请稍后再试。"); 55 | } 56 | 57 | const numMax = weekRes.data.data.weekDay; 58 | console.log("当前星期几:", numMax); 59 | 60 | // 获取疯狂星期四的内容 61 | const url = "https://kfc-crazy-thursday.vercel.app/api/index"; 62 | let result; 63 | 64 | try { 65 | const res = await axios.get(url); 66 | result = res.data; 67 | } catch (error) { 68 | console.error("获取疯狂星期四内容失败:", error); 69 | return s.reply("获取疯狂星期四内容失败,请稍后再试。"); 70 | } 71 | 72 | console.log("疯狂星期四内容:", result); 73 | 74 | // 判断是否是定时任务 75 | if (s.getFrom() === "cron") { 76 | // 如果是定时任务,检查是否是星期四 77 | if (numMax == "4") { 78 | // 遍历推送列表并发送消息 79 | for (const { groupId, platform } of PushList) { 80 | sysMethod.push({ 81 | platform: platform, 82 | groupId: groupId, 83 | userId: [], // 如果需要,可以填入特定的用户ID 84 | msg: result 85 | }); 86 | } 87 | } 88 | } else { 89 | // 非定时任务,直接回复 90 | await s.reply(result); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /plugins/xinz/联系管理员助手.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author xinz 3 | * @name 联系管理员助手 4 | * @team xinz 5 | * @version 1.0.1 // Version bump for improved prompt 6 | * @description 允许用户向所有管理员发送消息。 7 | * @rule ^联系管理$ 8 | * @admin false 9 | * @public true 10 | * @priority 500 11 | * @classification ["工具"] 12 | * @disable false 13 | * @service false 14 | */ 15 | 16 | // --- 引入/声明全局对象和依赖 --- 17 | // 假设 sender, sysMethod 在全局可用 18 | /** Code Encryption Block[419fd178b7a37c9eae7b7426c4a04203eccc74889588e76f2f619a7024a36c8fd857def5ccf504de79907cc6e8932b3e01539d5b794734086aa1470bbf993244f0f7b68ea1bcee6c83f921ea32a09b109bcee4c703d4a44aa7b048d68dfbf6adaa2bf6e71e8db6245175fa5ab4d0a9b778e150d85de8ddd959ece082c4a9ddd058e9e3d51cb303fb6355e0937ddf2c171736d448396fd4d35e186969e26ebb929905dbf2df7ba8a917173d796a36ba01720b7bce610b68f622205df137ec45f3cdab7d1f6089f8b05eb6976d085a07be4e946af29b8b5aa9aa09956e02a359c62f57cd3b7b2c1e6784bcb6c8f432e562266bb0d3a250d70b6eafdbfd417e06a16e095d691dacd2fa7cdbfa18eb2b94764938b22dfd590e07a2e5c7e0590191ae1366ca786235e13742dfb1c54712d1b07e0b5626437614176e1f3b3a496d9d2db66035e224c55403982c83f29e235ca77008ef67916330d99dccd3cdaf973825063dd80b1912dff419b7890a3c125d578d23dcf60a2c42c6625a273a42874b314d2473d9e2de03226ee9cf019a560a690b52f7fb646b5acf69ba3b8c406bc377ee42e24de8fe141e58e895fe5898315141c5b41d79dfe93cf80c5d8e2144b04aa263b5c04fd6b7e65c4864ec975a39429b9649f166e5c08af4c70fc556c31886998d6130ba9999ef5c7eb7591b75384e6b1c3bd7e9c9b5b6a6fcd947b9c2a4f4d6a996bf56e4ae8d540477552978061a74a195b4a79d18ed0860442d23e212c8b2057aa1e5c25ae17ed7d17c2bdf92be1a8593a8bccb0ca0d14fe3e8ddeff6f3e2d8295a97a2652456a206c555f3712c6ef3213f8b259f098760000555f26f39215046364af92b1021245e16087861f6ce2dd37c70e8da74e3563221d43c5585c43f6d925d1f912f41d6b92525d4d5eb32630fd93b4e0aeeb96cdeb884707a3c171301c605407e154cbe48e8809e0422a0ffb853f6f46497416c704d46aa22ea04647e74ffd9a346f2cd30b0816561442c8b1dccf6ab8514d9d94025af654fc4ce999cbd7098ec478200838f008cb1d8cac9dc18268a79f0b888d71f0e8da378f7c9aedccec0e3e997a9671918f6bf3276bd849cc90fd9968e18bfcf340501701150b50e5af80088c7f8349cb6b297daae8845dac8d7b7bf9141037725181059c633904624652fff553833820d0c0654272223d214c491699480a6d54a9618377230483a557bad1dede48aa6ad418ac459fc7530de4167da5b5f4b50f95e82a51ebcc816f1c658a47f40659a245e72dd23c5f0c57ee921b021fcbd4f347b5ac42b275ceea2fccd1f326a0ebafbf40ad9b08a88015b5116a85310cc278d2ec1ae776f6a0bde5c65570e9efe16fa76afbd598da1359928852f8dbc3838cbd8a279d70aafc0a349708ff460637dcd33297a344123004103a762ae57138e6eccc6fad3f15b5031c1f7db727f206d93bb61c9499d607f94c545a4c4e2d9623a8ccbb5ecc1a149b1e21210f53b0945da9996ed431113484f17ab5d922b532a9923a845be051e664acfee9c1e9eb64862ab7967bd22646b21c8ee33a28f4064aa5eee5075ae552017484e26e8f36eb2e63ae5e2c6374ac062bdd1d87f8394b1f11ce38c36ea9e9ebde55f521ec1c04fb9f56cf4ed106113e0079c0d52606386b078e89bd0738dd8aa6aee280b3957d77bc2896f9c624aace00b1bdf150a8fe80aeff90ccfc4e7810888ed633ab46b1db5bc15e839c43f78fc77fcd2ea17cc507be99fbc916afb06d5d22ff5d54dccbba7fff481e1e5ddf9386f441d96f163964a078c9a10cd1cd27d8ea0a34053258291c0e3c4958a8f0fbf9f1b3ec0e531f7359f40d5ca8d0d7e986b5451369fc013c1a5e36a054d8ff209a1b3959310ea3d875f418e621179990c8232da61c37080c4d4a393e24a632f832bcc294a188aa1371d43c898798ee2475f9935bbfeafda2cdc9cda6cff96475be4742ed1c00142394e0c1ce9471e50a9ba78dd5a6fb805eca837f5981d7ec1ad3ae915e1a010f15afb466789fd3399b2f85dcb3bca8cef3fecc3dd6c09502e2a728895a1cc3ee22ce1bd8c1cf8ada8c0e490301e7936662c97302af79588ce1029cef635ea7da708c0e0082c9bd52e2c98b3051543b2949eec7b0ea8e8d6ec271150e2d39505a440a1c77e7ce31b3533975572bd12da6554711eb23a37671b4b240a030c814c6e2e35eed4c765d549806d2021865d9266e7d894bace6e66b50fda3d839be264b8b555b99973b87651781d56eb69a2a7b6745f30628901cdedd52d1a68e1e96078749c806f0e61d07973a036054a5db0ef98923ff23c11375259f547196ef102d71f9310acc5e162cb5e1abc8852f6202ed74083282ec813f5c0b3f774a205fd42ada520d3a3c149866e06b3fea9baf95ee0c37d11356dcf7a0e04e7d8f19bb7331c247e0b7ef08bff962023f4cf564ced5add95c1eede0039f827d90f154194ad3cd653406aa712f9d7d600029d002cdfdbe313e081e7d34aed59331ce22e6e62a1d5747f86d7a29ba6258c079c7fe7e3e35df169beac8a02048f694c3c3921ed0aaed5f367823ab18fce6c48c039aff9a0b42f48bc96205ccc199269ad2a0c985c7e713f56c150d8e1954327dfdbb22e395e0098a32566745acddca6984d567d73a21b03d0ce0f0d083a0a2d76d5e37bb2a26cbbfc9053f7ff6eee91d97b7bd3306fef6cefa2de6767cf81e1239c11505ba3d7f27d876e8ca917e551bf6f6f7794fd5b1768044bd76e38cbc83887b09ffb910b098de030c3e6838e515b17d31b5998a73e34862f682f62f9f41cfb19bce3fb7a18b0e12856f5edf3bcfddd149abcccdc55a7f53acf27ba72b87c093dc405d6398d6f371186eabd0ad6c5a5c1f69faa570e793d99e6588c943817f29775507ff5d4fb9a55c1ab79d14ba15feed2ceab706d28db3c0ebf8df56dacc6f2e47d9e8a3edaab3c25c2347c5a278ff5a4abc319266a81b6c85d7fa405a5ad3dee6a9d5786a69045b58a24e14489adbb44c3158a70697a0e65e875691fca8793c7d295aa5dc850fdb1112217fd3e37802919497d84d3dfdbca0cf53ec2bd01c7d5039f4495995542cbe89ef6403bdd51b4697bc21f3e3150a5e0cd25c29687707241be62df22d29228df39fd68b0f7d7e50ab22973459003c9e185e9564a257de4b50054e78193111f18ed24504772921de83b79fd6e35d1e1c1bac1e814c8c682d00d1a7a79f0bf295424fdeef3977db81a8c6663cdfdbca817870c5ffee42bcb0d2e20636059670125a568a9659ce3b83a7fbd0ddfaa42c34af72459fa6c57384e1f15e307c5b463fd08c193c6249d126adac9f546e38a93dc9d18572c0f0dfd0d96f06a2ad208ec088a6629b5e4a117d89e9558e0f0200796a09d5760ff8303053549a0ad60788be9810d879c2d6e36315481a594f6f5b33914b6cc87ef3630f8eee82b5897411c6232617514112159b145d345215e2a5430f8875ce820e68eec3471dcd4ff84b60421206f5e220dd64e7ed8b061338e7d86f6024b4ff6d3e5292e321a34b90e126a026ceb8464b260a1cf1432a0dafbc63df03dbf1087d428976fdd836e274e7b5736759874cddca2d4b9c4665b5555819cde910ec6a578ce1b795394e1753ff0fcb2eaeaf38b6401166427fea112be0e051c3e4f0485021d6e4c1e8b42e258e3e787803a9d05471cd88b5a43d7bab9024e8498b6439a644d3cdf3e8944a5e42065ddbdff14c8cf40797d4d1ff3121e28f38241f5175a70aac8078bd3a619ed6d44561f50cbcc54eff9c5d3833d386932b4083042c72d4977c660f2a504f5defe4644125e17f511408a3d3ccda563759403a268ea253f2ae8242fe60aafe7cf1b18785d50bbbd7572bc05dce4b9ffa10c67fdcec44496b96ad03c32b9e6db490342bd874fce1c1449a227cf8f8e5e976e834ad3bb96c18ba6a95af3620515ae0fafaab5b8d93165b053303e5a135dc01136c4266af1afb278efe41b9b1e52e5d1053d8f78c4b80d1a71b5c2ea8bbb55606e237ac097430679c2484b6216f484931a909281b13b05a20952ec3d310f723c98a071a13d0ba86d13ab9852acc91c17141a8a5fa89a95c02111d0b4c372c48babed032066358965833db56a4ca0f6895dc5064fbfd7a4c1b0cc7d605c2c67adf67da7afe3e0b7c26488dfc336815c60ec59da0e9f925be06bf27470016e3f944094dd60da23879ef374361940db94adc0fc6185859f810e06188c50ad6dec1cd730a368ddbb6ed826f6ed420ce29a5f03f263826aa2b5fcacd0348638db27d734cba1a64e3e58ecaf36832e77392c17fc8187e9cad9930fd162f14b0fb2c0248a882a22cb186d76af23c7fa368ec8b00e849960ecca5606a46982ad2eb284e6706ed7650563771112c0e42d430dd291e2afdf6c81fbe33e19af62ee2804acd0271302bf3495f191e7aacb695890a715069fe1b70afa04a313e4f577b727dd77e6dd09fca7744764361a17cb37f5c24b6d0f5600701fff2b9d0397f8763a5955bfe6879b4f00f68202e20bfda74f5bd0626e4d0e2c1103151e165e3ea3c441a2a242370b4d186b03fe12a41678048928b0a4fe7c73e5605c9253354a3672eeec13e81e1233f3eb3b93509f9b37c655489fc5ebdbd47f0a689880832e7a82271bd8e8495ce59459240bf8cb6e188a85c7d52e178b4074887ead78fd041af876bbe5d9f0590c20975da1e2bfb0e00637fa4a88830701cd04fa05cc3c94c6777ae96cff2809c77d4378945c74d9e3d1e8277b338368bdea9d8025fde8a10e1d6acd4999cea57d0ddce5c10bd6b22674d02406b3230e44e450df8535360fe20d0ae388e5e6d8f243be0ed48d7e36370db4a4abf3b9bcc9e51a39ac3c89f698a61e35f804037f08ec4d5b6ff5fc4e57d45ef001a8aa3e1a42e56ca6572bd7fc9da122e27e2f05b7c8792b21b1d8a167f6a35d0e19dae69aaa9000e50b086f4b64ee3fb41fea6f5a0d77f1a69ca09ca5e3a672b19658be84f49412ac84b971c7ef02ff2379a4aac7e19b86c9692ec6d1ef36616f7604c0e3791fdd7c66fa12a2f8193ba78807d9d34cdfcf6755e959124f56cf48097381d1587ce9a51ae062f4ed386b221088affc8149f5001eb74cc95b56999ee5016bb1a1cc4ce02f67586d9d7a1f30d89bc9154398d947c64580fb63597e8cc4a6adc2330b018f2ff25f5ab8bc3362e39eb7df0a20e78b4ea4288d3aea92d828564855b6d3433b2f4d18362ac3878c83af235dbbfaf45b3b8eb43550d3eb8c33fd5a392b6f1754bec05f1d8050e811a7a01fa30c85606c6bd6f655fe1fbf3189996483c60e1268502c561f25d3f7c8815eff080762d132b4672d52f092cd844ec204ac341690317bdf8cea4d60b55bb23352adf81ee1f807b27e89770f3996592c17af6304137072cb2c23f4c0e094456a67e3c5e663148361526870fe7c98b8d6b39747594bfe89ab3219116d94de8e0843b1ea354c8f64f55dedba2bf2eeda58d9f9c1819ecd6131647af81744dd5620634ad9ad1f8d635aaf76b523eccff233cafd9f3fa55d19ebeebb8627f90448b357e5aecc0197b654197d82c774eb81d9269f01999396584df644f89d433d175678db7317ac1d7e4564a543bf9917cebb2f6f8dab509e2aad5f00e32a0488d8406c7526ff973cabe427523f8946754c6ab3714220f1cd69656c3c5e51a5f78df1cfa59f1600ae4ea413fc4f765942b41860f79f35d506d3af8009aaed9f023c37fa906d30c8302b84b00916e6a7f4093e1a16b55652833cf549aa251fdb581edbad9cf7c6fa95451b8e8e1022db4cac6a4f5c5eb603119189d0b26fff6ec6a2547a42aa8d4df1562abdca35303aab4b77b5cafec7b8c3123a6c260ec18bc024d9a5a4d6599ce5a01c1531802f9fb2dc15db51e4c7ae1d679019f753a5b5d699258d93c0bde367dd5e1c36e77ddcd783b0ad1ec569820a9c756914d7620da93039364f0473ea260f4db7c918984d396eab5a828d7a32d45a3c5ec643d856c0df74878c62417aca62ec573daabc55fc12b2874ec3e2ded9fd039e261d98e45d35aeb693d411dea164421ddaee25ae305792c47b8dab49f3fde348b82c977e63e1fdb50766065980f3a31631866e94e403524269414d664f25da0e954ed490b2505a9230d3a7b7036805a8335ead3d06547157fd32c883b6502e011cf04b05803f729dfc426f55281e0033d786f923b561ac53b97274a072b1910397a8c425a7ba146e25a7a312b319b9d60626eb825c25cdf6204c83cabd31d099dac4dad1cc7f5f2def3b74c0e0a63bc0ce3dd1066538b2dcc1152f90f06843480949408751cd576ecf6d35aaf890da0f45b534a13d1173d79b122ccbe93bec769e8b0130c3eb5e5501b34fe718783c04919dde9232ccceb7d66dd0294882871a007c52abd98d699021624bb7cb8f0193bbf6c3aae0c0ca1eb48a5926f1a5dbf0fc56149edfb29070ff4471dd2d8980645c4dbf16ab204953ff70b1b8762101eb8182b2c49580f2f8265c9d5a30b3102202f2451406aa4e9569512c069e537009ead24a9286382e943f4eb0f91dfb4dd07b64fdae37044e9be2934b7fc36af612b9945963b41bd474162176494bcde09537b087f53b5f7ed14999737b3b5674e23204a1c81f88248af4b7c3a8362afe8a193e7fa049ebf0556f8a8133cbea44ce77eb6e79599268bb554f5ffdd39fce371e099dd766128ca70450d67f9838a3caae2043ce682f55f1e8d9541b1f5c65bce41cd566a0593ce2c96c721aa8224f569759888bb8a1510ac91883e095a694fc2e46455290de0e562bb8233a4c4e153295d2abfe081790c959493ae655f4039b2932d705c617ca9e7455fae9313ef61db66660ea28db93aea4fa930555c08616ff02e9642c5ccc5e9b81852d5c198dbc7e4e6e9d906196429b8bc694044b6f3656d77e4c13242fbce9d24712adde4538b9f11dcc62e3ddcc612c780135dd04916007a8126ea76693e2f86cd88a70bd04a40abb393e9b300bd1c6387469b6e3c3e6ed83899ceee2b00b68bdc578082af45149e9887d97b6e7157d45ce531d7c916753e9248a543df8fc5c07e8a3b5ae7a841dd85da602d65fa6104668c69db733d608a1a8fa0b613159c18db15a7885cf031d0114102ca4a98df3f6ab9a92972db23fac70b52f93cc158c912268a0338ea0be597d9751a3621729dd6f10ff9bcfd52b6de58143b47aa57ef15eba6511d22f22437ab63701f131667ce5598acc9a52f6e34b2d724d2ac58abbce2aa22a299b8c1c66a7f166759ae37d01d5853f0ee6ee1e6e97d9fdfca2ee0ce2db2892a42993d58906b948409d1d58e165e6a7be5d9c8680dbd396dbcd8db8ffaf3e91eb6f93fa133c3378900bbd4fc2709794088479d97c74eb1418f77edc156d3bc62413c610ed4ab9f759b3605f27a954c16619c96b9c09b002f1d98bc018b3e92a0086746f12616d7325d3635dac35c281cee0991dfede28115a361847ee903365ceac39ec736ea5cff3a43544f319c6ae4c] */ -------------------------------------------------------------------------------- /plugins/xinz/自动回复.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author seven 3 | * @description 自动回复 4 | * @team xinz 5 | * @version v1.0.0 6 | * @name 自动回复 修改为菜单项的变更版本 可发送菜单指令 获取用户设置的关键词列表 仅管理员添加或删除关键词 7 | 关键词添加识别错误请到mod目录查看关键词信息自己更改 8 | * @rule ^(删除自动回复) ([^\n]+)$ 9 | * @rule ^(添加自动回复) ([^\n]+) ([\s\S]+)$ 10 | * @rule ^(自动回复列表)$ 11 | * @rule ^(菜单)$ 12 | * @rule [\s\S]+ 13 | * @priority 1 14 | * @admin false 15 | * @disable false 16 | */ 17 | 18 | const fs = require('fs'); 19 | const path = require('path'); 20 | 21 | const dataFilePath = path.join(__dirname, './mod/autoReplyData.json'); 22 | 23 | // 确保数据文件存在 24 | if (!fs.existsSync(dataFilePath)) { 25 | fs.writeFileSync(dataFilePath, JSON.stringify({})); 26 | } 27 | 28 | const delMsgTime = 10000; // 设置删除消息的时间为 10000 毫秒 29 | 30 | /* main */ 31 | module.exports = async s => { 32 | const msgInfo = s.msgInfo; 33 | 34 | // 从文件中读取自动回复数据 35 | const loadAutoReplies = () => JSON.parse(fs.readFileSync(dataFilePath)); 36 | 37 | switch (s.param(1)) { 38 | case '添加自动回复': 39 | if (!(await s.isAdmin())) return s.reply('您没有权限添加自动回复。'); // 仅管理员可以添加 40 | const addKeyword = s.param(2); 41 | const addReply = s.param(3); 42 | const autoReplies = loadAutoReplies(); 43 | 44 | autoReplies[addKeyword] = { 45 | groupId: msgInfo.groupId || '非群组', // 记录群组ID或标记为非群组 46 | from: msgInfo.from, 47 | reply: addReply, 48 | }; 49 | 50 | fs.writeFileSync(dataFilePath, JSON.stringify(autoReplies, null, 2)); 51 | return s.delMsg(await s.reply('添加成功'), { wait: 10 }); 52 | 53 | case '自动回复列表': 54 | const autoReplyList = loadAutoReplies(); 55 | let logs = ''; 56 | 57 | for (const key in autoReplyList) { 58 | const r = autoReplyList[key]; 59 | logs += `${r.from}:${r.groupId} => ${key} | ${r.reply}\n`; 60 | } 61 | return s.delMsg(await s.reply(logs || '空列表'), { wait: 10 }); 62 | 63 | case '删除自动回复': 64 | if (!(await s.isAdmin())) return s.reply('您没有权限删除自动回复。'); // 仅管理员可以删除 65 | const deleteKeyword = s.param(2); 66 | const currentReplies = loadAutoReplies(); 67 | 68 | if (deleteKeyword in currentReplies) { 69 | delete currentReplies[deleteKeyword]; 70 | fs.writeFileSync(dataFilePath, JSON.stringify(currentReplies, null, 2)); 71 | return s.delMsg(await s.reply(`已删除: ${deleteKeyword}`), { wait: 10 }); 72 | } else { 73 | return s.delMsg(await s.reply('没有该关键词回复列表'), { wait: 10 }); 74 | } 75 | 76 | case '菜单': // 修改菜单命令 77 | const replyKeywords = loadAutoReplies(); 78 | const keywordList = Object.keys(replyKeywords).map(key => `${key}`).join('\n'); 79 | 80 | const menuMessage = ` ——功能列表——\n` + 81 | `——发送关键字——\n` + 82 | (keywordList || '当前没有自动回复关键词。\n') + 83 | `\n——作者 xinz——`; 84 | 85 | const replyId = await s.reply(menuMessage); // 发送菜单消息 86 | // 设置定时删除消息 87 | setTimeout(() => { 88 | s.delMsg(replyId); // 20秒后删除菜单消息 89 | }, 20000); // 20000毫秒 = 20秒 90 | 91 | return; // 结束处理 92 | 93 | } 94 | 95 | /* 异步处理 */ 96 | new Promise(async resolve => { 97 | const autoReplies = loadAutoReplies(); // 重新加载自动回复数据 98 | for (const key in autoReplies) { 99 | const r = autoReplies[key]; 100 | if (msgInfo.msg.trim() === key) { // 精确关键词匹配 101 | const id = await s.reply(r.reply); // 发送回复 102 | // 设置删除回复消息的延迟 103 | setTimeout(() => { 104 | s.delMsg(id); // 删除回复的消息 105 | }, delMsgTime); // 使用设置的时间 106 | } 107 | } 108 | resolve(void 0); 109 | }); 110 | 111 | return 'next'; 112 | }; 113 | -------------------------------------------------------------------------------- /plugins/xinz/舔狗日记.js: -------------------------------------------------------------------------------- 1 | /**作者 2 | * @author seven 3 | * 插件名 4 | * @name 舔狗日记 5 | * 组织名 预留字段,未来发布插件会用到 6 | * @team xinz 7 | * 版本号 8 | * @version 1.0.5 9 | * 说明 10 | * @platform tgBot qq ssh HumanTG wxQianxun wxXyo wechaty 11 | * @description 舔狗日记 12 | * 触发正则 在bncr 所有的rule都被视为正则 13 | * @rule ^(舔狗|舔狗日记|我舔)$ 14 | * // 是否管理员才能触发命令 15 | * @admin flase 16 | * // 是否发布插件,预留字段,可忽略 17 | * @public true 18 | * // 插件优先级,越大优先级越高 如果两个插件正则一样,则优先级高的先被匹配 19 | * @priority 9999 20 | * // 是否禁用插件 21 | * @disable false 22 | * // 是否服务模块,true不会作为插件加载,会在系统启动时执行该插件内容 23 | * @service false 24 | * @classification ["功能插件"] 25 | */ 26 | 27 | const request = require('request'); 28 | 29 | const apiKey = "62f7df1333f54e1b3a1f406b6c8d160e"; 30 | 31 | module.exports = async s => { 32 | code = s.param(1); 33 | //you code 34 | request.post({ 35 | url: 'https://apis.tianapi.com/tiangou/index', 36 | form: { 37 | key: apiKey 38 | } 39 | },async function (err, response, tianapi_data) { 40 | if (err) { 41 | console.log(err); 42 | } else { 43 | var data = JSON.parse(tianapi_data); 44 | var list = data.result; 45 | data = 46 | list.content 47 | } 48 | await s.reply(data); 49 | //console.log(data); 50 | }) 51 | //插件运行结束时 如果返回 'next' ,则继续向下匹配插件 否则只运行当前插件 52 | return 'next' //继续向下匹配插件 53 | } 54 | -------------------------------------------------------------------------------- /plugins/xinz/菜单.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author seven 3 | * @name 菜单 4 | * @team xinz 5 | * @version 1.0.4 6 | * @description 可自定义菜单内容和底部显示的菜单插件 7 | * @rule ^(菜单)$ 8 | * @admin false 9 | * @public true 10 | * @priority 1000 11 | * @disable false 12 | * @classification ["工具"] 13 | */ 14 | /// 15 | const defaultConfig = { 16 | menuItems: [ 17 | { 18 | category: '🎡京东活动🎡', 19 | items: [ 20 | { command: '登录', description: '短信/扫码登录' }, 21 | { command: '查询', description: '查询账户信息' }, 22 | { command: '豆豆', description: '查询豆豆明细' }, 23 | { command: '浇水', description: '东东农场(旧)浇水' }, 24 | { command: '更新ck', description: '密码登录专属更新ck' }, 25 | { command: '奖票兑换', description: '玩一玩奖票兑换红包' }, 26 | { command: '账户管理', description: '管理/删除账户' }, 27 | { command: '密码管理', description: '删除密码登录账户' }, 28 | { command: '位置查询', description: '位置每天都会变动' } 29 | ] 30 | }, 31 | { 32 | category: '👽其它命令👽', 33 | items: [ 34 | { command: '城市天气', description: '例如:北京天气' }, 35 | { command: '查Q绑 qq', description: '例如:查Q绑 123456' }, 36 | { command: '打赏', description: '打赏一下,维护不易' }, 37 | { command: '打赏排行榜', description: '记住每一位老板' } 38 | ] 39 | } 40 | ], 41 | bottomContent: '请多多拉人,一起撸 ~\n㊗️🎊家人们发大财,心想事成,身体健康' 42 | }; 43 | 44 | const jsonSchema = BncrCreateSchema.object({ 45 | menuItems: BncrCreateSchema.array( 46 | BncrCreateSchema.object({ 47 | category: BncrCreateSchema.string() 48 | .setTitle('分类名称') 49 | .setDescription('设置菜单分类的名称'), 50 | items: BncrCreateSchema.array( 51 | BncrCreateSchema.object({ 52 | command: BncrCreateSchema.string() 53 | .setTitle('命令') 54 | .setDescription('设置菜单项的命令'), 55 | description: BncrCreateSchema.string() 56 | .setTitle('描述') 57 | .setDescription('设置菜单项的描述') 58 | }) 59 | ).setTitle('菜单项') 60 | .setDescription('设置该分类下的菜单项') 61 | }) 62 | ).setTitle('菜单内容') 63 | .setDescription('设置菜单的内容结构') 64 | .setDefault(defaultConfig.menuItems), 65 | bottomContent: BncrCreateSchema.string() 66 | .setTitle('底部显示内容') 67 | .setDescription('设置菜单底部显示的内容,使用\\n表示换行') 68 | .setDefault(defaultConfig.bottomContent) 69 | }); 70 | 71 | const ConfigDB = new BncrPluginConfig(jsonSchema); 72 | 73 | function generateMenu(menuItems, bottomContent) { 74 | let message = [ 75 | 76 | '┄┅┄┅┄┅┄┅┄┅┄┅┄', 77 | '❤️💗菜单选项列表💗❤️', 78 | '┄┅┄┅┄┅┄┅┄┅┄┅┄', 79 | '═════命令❀描述════' 80 | ]; 81 | for (const category of menuItems) { 82 | message.push(category.category); 83 | for (const item of category.items) { 84 | message.push(`${item.command.padEnd(8)}║ ${item.description}`); 85 | } 86 | message.push('┄┅┄┅┄┅┄┅┄┅┄┅┄'); 87 | } 88 | 89 | // 添加底部内容,处理换行 90 | message = message.concat(bottomContent.split('\\n')); 91 | 92 | message.push('┄┅┄┅┄┅┄┅┄┅┄┅┄'); 93 | 94 | return message.join('\n'); 95 | } 96 | 97 | /** 98 | * 插件入口 99 | * @param {Sender} s 100 | */ 101 | module.exports = async s => { 102 | try { 103 | await ConfigDB.get(); 104 | if (!Object.keys(ConfigDB.userConfig).length) { 105 | return await s.reply('请先发送"修改无界配置",或者前往前端web"插件配置"来完成插件首次配置'); 106 | } 107 | 108 | const { menuItems, bottomContent } = ConfigDB.userConfig; 109 | const menuContent = generateMenu(menuItems, bottomContent); 110 | await s.reply(menuContent); 111 | } catch (error) { 112 | console.error('生成或发送菜单时出错:', error); 113 | await s.reply('抱歉,生成菜单时出现错误,请稍后再试。'); 114 | } 115 | }; 116 | --------------------------------------------------------------------------------