UNPKG

koishi-plugin-emojihub-bili

Version:

[<ruby>Emojihub-bili<rp>(</rp><rt>点我查看食用方法</rt><rp>)</rp></ruby>](https://www.npmjs.com/package/koishi-plugin-emojihub-bili)🐱支持QQ官方json按钮!20群即可发按钮!😍可自定义添加或删除任意表情包!基本每个指令都有上千张!支持定时/定量消息触发![点我查看食用方法](https://www.npmjs.com/package/koishi-plugin-emojihub-bi

1,083 lines (1,068 loc) 60.5 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name2 in all) __defProp(target, name2, { get: all[name2], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply, inject: () => inject, name: () => name, reusable: () => reusable, usage: () => usage }); module.exports = __toCommonJS(src_exports); var import_koishi3 = require("koishi"); // src/config.ts var import_koishi = require("koishi"); var import_node_path = __toESM(require("node:path")); var usage = ` --- <details> <summary><h2>点我展开 - 如何额外添加表情包</h2></summary> <p>添加额外的表情包到 <strong>emojihub-bili</strong> 中非常简单!只需按照以下步骤操作:</p> <ol> <li><strong>安装扩展(用户脚本管理器)</strong>:<br>在浏览器中添加扩展:<a href="https://docs.scriptcat.org/" target="_blank">ScriptCat---脚本猫</a>。</li> <li><strong>安装脚本</strong>:<br>在用户脚本管理器中添加脚本:<a href="https://greasyfork.org/zh-CN/scripts/521666-bilibili%E4%B8%93%E6%A0%8F%E5%8E%9F%E5%9B%BE%E9%93%BE%E6%8E%A5%E6%8F%90%E5%8F%962024%E6%94%B9%E7%89%88" target="_blank">(点击右侧文字查看)Bilibili专栏原图链接提取2024改版</a>。</li> <li><strong>搜索表情包</strong>:<br>开启扩展后,打开<a href="https://search.bilibili.com/article/" target="_blank">哔哩哔哩专栏搜索</a>,在专栏中搜索您需要的表情包。</li> <li><strong>提取链接</strong>:<br>点击进入具体的某个专栏帖子,屏幕靠近右下角会有一个绿色的【提取链接】按钮。点击该按钮,即可下载包含当前专栏所有图片的 URL 的 txt 文件。并且按下一次后会变成红色,防止误触,不可二次触发。如需再次下载,请刷新页面。</li> <li><strong>配置 emojihub-bili</strong>:<br>将同一类表情包图片的 URL 整合到同一个 txt 文件中。然后,在 Koishi 的配置项中填入相应的指令名称与 txt 文件路径。(无需像自带的txt一样省略前缀,写完整URL即可)</li> <li><strong>保存并重载</strong>:<br>完成配置后,保存您的配置并重载插件,您就可以使用自定义的指令发送表情包啦!🌟📚</li> </ol> </details> --- <p> </p> <h2>温馨提示:</h2> <p>请勿将自定义的txt文件与本插件放置在同一目录下,以免插件更新导致文件丢失。</p> <p>emojihub-bili 默认提供 <code>44套</code> 表情包。若您的配置内容有误差,请点击 <code>MoreEmojiHubList</code> 表格右上角按钮内的 <code>恢复默认值</code>。</p> <p>若开启插件后,指令不出现,<a href="/market?keyword=commands">请重新开关commands插件</a></p> --- `; var defaultMoreEmojiHubList = [ // 下面实际有效为 44套 { command: "随机emojihub表情包", source_url: "无效路径/内容会调用随机表情包。注意与【随机表情包】指令的功能一致,但【随机表情包】不可被填入表格使用,【随机emojihub表情包】可以,因为在这个配置项里。" }, { command: "本地图库示例", source_url: import_node_path.default.join(__dirname, "txts") }, { command: "网络图片示例", source_url: "https://i0.hdslb.com/bfs/article/afc31d0e398204d94478473a497028e6352074746.gif" }, { command: "2233娘小剧场表情包", source_url: import_node_path.default.join(__dirname, "../txts/2233娘小剧场.txt") }, { command: "acomu414表情包", source_url: import_node_path.default.join(__dirname, "../txts/acomu414.txt") }, { command: "ba表情包", source_url: import_node_path.default.join(__dirname, "../txts/ba.txt") }, { command: "capoo表情包", source_url: import_node_path.default.join(__dirname, "../txts/capoo.txt") }, { command: "chiikawa表情包", source_url: import_node_path.default.join(__dirname, "../txts/chiikawa.txt") }, { command: "downvote表情包", source_url: import_node_path.default.join(__dirname, "../txts/Downvote.txt") }, { command: "doro表情包", source_url: import_node_path.default.join(__dirname, "../txts/doro.txt") }, { command: "eveonecat表情包", source_url: import_node_path.default.join(__dirname, "../txts/eveonecat.txt") }, { command: "fufu表情包", source_url: import_node_path.default.join(__dirname, "../txts/fufu.txt") }, { command: "girlsbandcry", source_url: import_node_path.default.join(__dirname, "../txts/GirlsBandCry.txt") }, { command: "kemomimi表情包", source_url: import_node_path.default.join(__dirname, "../txts/kemomimi酱表情包.txt") }, { command: "PigHub表情包", source_url: import_node_path.default.join(__dirname, "../txts/PigHub.txt") }, { command: "koishi-meme表情包", source_url: import_node_path.default.join(__dirname, "../txts/koimeme.txt") }, { command: "mygo表情包", source_url: import_node_path.default.join(__dirname, "../txts/mygo.txt") }, { command: "seseren表情包", source_url: import_node_path.default.join(__dirname, "../txts/seseren.txt") }, { command: "阿夸表情包", source_url: import_node_path.default.join(__dirname, "../txts/阿夸.txt") }, { command: "阿尼亚表情包", source_url: import_node_path.default.join(__dirname, "../txts/阿尼亚.txt") }, { command: "白圣女表情包", source_url: import_node_path.default.join(__dirname, "../txts/白圣女.txt") }, { command: "白圣女漫画表情包", source_url: import_node_path.default.join(__dirname, "../txts/白圣女黑白.txt") }, { command: "败犬女主表情包", source_url: import_node_path.default.join(__dirname, "../txts/败犬女主.txt") }, { command: "柴郡表情包", source_url: import_node_path.default.join(__dirname, "../txts/柴郡.txt") }, { command: "初音Q版表情包", source_url: import_node_path.default.join(__dirname, "../txts/初音未来Q.txt") }, { command: "甘城猫猫表情包", source_url: import_node_path.default.join(__dirname, "../txts/甘城猫猫.txt") }, { command: "孤独摇滚表情包", source_url: import_node_path.default.join(__dirname, "../txts/孤独摇滚.txt") }, { command: "狗妈表情包", source_url: import_node_path.default.join(__dirname, "../txts/狗妈.txt") }, { command: "滑稽表情包", source_url: import_node_path.default.join(__dirname, "../txts/滑稽.txt") }, { command: "疾旋鼬表情包", source_url: import_node_path.default.join(__dirname, "../txts/疾旋鼬.txt") }, { command: "卡拉彼丘表情包", source_url: import_node_path.default.join(__dirname, "../txts/卡拉彼丘.txt") }, { command: "流萤表情包", source_url: import_node_path.default.join(__dirname, "../txts/流萤.txt") }, { command: "龙图表情包", source_url: import_node_path.default.join(__dirname, "../txts/龙图.txt") }, { command: "鹿乃子表情包", source_url: import_node_path.default.join(__dirname, "../txts/鹿乃子.txt") }, { command: "玛丽猫表情包", source_url: import_node_path.default.join(__dirname, "../txts/玛丽猫.txt") }, { command: "小c表情包", source_url: import_node_path.default.join(__dirname, "../txts/蜜汁工坊.txt") }, { command: "男娘武器库表情包", source_url: import_node_path.default.join(__dirname, "../txts/男娘武器库.txt") }, { command: "千恋万花表情包", source_url: import_node_path.default.join(__dirname, "../txts/0721.txt") }, { command: "赛马娘表情包", source_url: import_node_path.default.join(__dirname, "../txts/赛马娘.txt") }, { command: "瑟莉亚表情包", source_url: import_node_path.default.join(__dirname, "../txts/瑟莉亚.txt") }, { command: "藤田琴音表情包", source_url: import_node_path.default.join(__dirname, "../txts/藤田琴音.txt") }, { command: "小黑子表情包", source_url: import_node_path.default.join(__dirname, "../txts/小黑子.txt") }, { command: "心海表情包", source_url: import_node_path.default.join(__dirname, "../txts/心海.txt") }, { command: "绪山真寻表情包", source_url: import_node_path.default.join(__dirname, "../txts/绪山真寻.txt") }, { command: "亚托莉表情包", source_url: import_node_path.default.join(__dirname, "../txts/亚托莉表情包.txt") }, { command: "永雏小菲表情包", source_url: import_node_path.default.join(__dirname, "../txts/永雏小菲.txt") }, { command: "宇佐紀表情包", source_url: import_node_path.default.join(__dirname, "../txts/宇佐紀.txt") } // { command: '', source_url: path.join(__dirname, '../txts/.txt') }, // 以后添加其他的命令...未完待续 ]; var Config = import_koishi.Schema.intersect([ import_koishi.Schema.object({ emojihub_bili_command: import_koishi.Schema.string().default("emojihub-bili").description("`父级指令`的指令名称").pattern(/^\S+$/), emojihub_onemore: import_koishi.Schema.string().default("再来一张").description("`再来一张`的指令名称").pattern(/^\S+$/), emojihub_randompic: import_koishi.Schema.string().default("随机表情包").description("`随机表情包`的指令名称").pattern(/^\S+$/), MoreEmojiHubList: import_koishi.Schema.array(import_koishi.Schema.object({ command: import_koishi.Schema.string().description("注册的指令名称"), source_url: import_koishi.Schema.string().description("表情包文件地址") })).role("table").default(defaultMoreEmojiHubList).description("表情包指令映射表<br>▶ 若出现配置问题 请点击右方按钮 可以恢复到默认值<br>右列`文件地址`可以填入`txt绝对路径`、`文件夹绝对路径`、`图片直链`、`图片文件绝对路径`。支持格式 详见[➩项目README](https://github.com/koishi-shangxue-plugins/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)"), maxexecutetime: import_koishi.Schema.number().description("`-n 选项`指定 允许单次返回的 表情包最大数<br>例如默认10 :`ba表情包 -n 30`,可以返回10张").default(10), deleteMsg: import_koishi.Schema.boolean().description("`开启后`自动撤回表情").default(false) }).description("表情包设置"), import_koishi.Schema.union([ import_koishi.Schema.object({ deleteMsg: import_koishi.Schema.const(true).required(), deleteMsgtime: import_koishi.Schema.number().default(30).description("若干`秒`后 撤回表情").min(1) }), import_koishi.Schema.object({}) ]), import_koishi.Schema.object({ repeatCommandDifferentiation: import_koishi.Schema.union([ import_koishi.Schema.const("channelId").description("按频道ID区分"), import_koishi.Schema.const("userId").description("按用户ID区分") ]).role("radio").default("channelId").description("`再来一张`指令的区分逻辑。<br>按频道ID区分:触发指令后发送当前频道最后触发的表情包<br>按用户ID区分:触发指令后发送当前用户最后触发的表情包"), searchSubfolders: import_koishi.Schema.boolean().description("本地发图,输入文件名称参数时,是否递归搜索文件夹。<br>`开启后 对于本地文件夹地址 会搜索其子文件夹内全部的图片`").default(true), searchSubfoldersWithfilename: import_koishi.Schema.boolean().description("递归搜索时,是否把`子文件夹`的名称纳入名称匹配范围<br>例如:`C:/中文/456.png`被视作`中文456.png`文件名处理匹配").default(false), localPictureToName: import_koishi.Schema.string().role("textarea", { rows: [4, 4] }).description("对于本地图片/文件,是否输出文件名<br>仅图片:`${IMAGE}`<br>图+文件名:`${IMAGE}\\n${NAME}`<br>文件名+图:`${NAME}\\n${IMAGE}`<br>文本+变量:`今天你的幸运神:${NAME}\\n${IMAGE}`<br>全部变量示例:`${IMAGE}\\n文件名称:${NAME}\\n文件大小:${SIZE}\\n修改日期:${TIME}\\n文件路径:${PATH}`<br>其中`\\n`就是换行,可以写字也可以直接回车。<br>可用替换变量有:IMAGE、 NAME、 SIZE、 TIME、 PATH<br>仅对指令发送本地图片有效。<br>更多说明,详见[➩项目README](https://github.com/koishi-shangxue-plugins/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)").default("${IMAGE}") }).description("进阶设置"), import_koishi.Schema.object({ autoEmoji: import_koishi.Schema.union(["取消应用", "定量消息发送", "定时发送"]).description("打开后,开启自动表情包功能 <br>▶ 定量消息发送: `达到一定消息数量 自动触发表情包`<br>▶ 定时发送: `使用cron表达式定时触发表情包`此项需要`cron`服务").default("取消应用") }).description("自动表情包设置"), import_koishi.Schema.union([ import_koishi.Schema.object({ autoEmoji: import_koishi.Schema.const("定量消息发送").required(), middleware: import_koishi.Schema.boolean().description("开启后使用前置中间件").default(true), triggerprobability: import_koishi.Schema.percent().default(0.8).description("达到消息数量阈值时,发送表情包的概率 `范围为 0 到 1 `"), groupListmapping: import_koishi.Schema.array(import_koishi.Schema.object({ groupList: import_koishi.Schema.string().description("开启自动表情包的群组ID"), defaultemojicommand: import_koishi.Schema.string().description("表情包指令名称 `应与上方指令表格对应`"), count: import_koishi.Schema.number().description("触发自动表情包的消息数量的阈值").default(30), enable: import_koishi.Schema.boolean().description("勾选后 屏蔽该群 的自动表情包").default(false) })).role("table").description("表情包指令映射 `注意群组ID不要多空格什么的`<br>私聊频道有`private:`前缀<br>表情包名称请通过逗号分隔").default([ { "groupList": "114514", "defaultemojicommand": "koishi-meme,白圣女表情包,男娘武器库", "count": 30, "enable": false }, { "groupList": "private:1919810", "defaultemojicommand": "随机emojihub表情包", "count": 30, "enable": true } ]), allgroupautoEmoji: import_koishi.Schema.boolean().description("`全部群组` 开启自动表情包").default(false), count: import_koishi.Schema.number().description("`全部群组` 触发自动表情包的消息数量的阈值").default(30), allgroupemojicommand: import_koishi.Schema.string().role("textarea", { rows: [2, 4] }).description("`全部群组的` 表情包指令映射`一行一个指令 或者 逗号分隔` <br> 可以同时在`groupListmapping`指定群组的表情包内容").default(`宇佐紀表情包 白圣女表情包 白圣女漫画表情包`) }), import_koishi.Schema.object({ autoEmoji: import_koishi.Schema.const("定时发送").required(), botId: import_koishi.Schema.string().description("定时消息由哪个bot发出?▶ 填入bot对应的Id"), triggerprobability: import_koishi.Schema.percent().default(0.8).description("达到预定时间时,发送表情包的概率 `范围为 0 到 1 `"), groupListmapping: import_koishi.Schema.array(import_koishi.Schema.object({ groupList: import_koishi.Schema.string().description("开启自动表情包的群组ID"), defaultemojicommand: import_koishi.Schema.string().description("表情包指令名称 `应与上方指令表格对应`"), cronTime: import_koishi.Schema.string().description("定时设置:cron语法"), enable: import_koishi.Schema.boolean().description("勾选后 屏蔽该群 的自动表情包").default(false) })).role("table").description("表情包指令映射 `注意群组ID不要多空格什么的`<br>私聊频道有`private:`前缀<br>表情包名称请通过逗号分隔<br>▶ cron 定时语法 见 https://cron.koishi.chat/").default([ { "groupList": "114514", "defaultemojicommand": "白圣女表情包,白圣女漫画表情包", "enable": false, "cronTime": "15,45 * * * *" }, { "groupList": "private:1919810", "defaultemojicommand": "白圣女表情包", "enable": true, "cronTime": "15,45 * * * *" } ]) }), import_koishi.Schema.object({}) ]), import_koishi.Schema.object({ markdown_button_mode: import_koishi.Schema.union([ import_koishi.Schema.const("unset").description("取消应用此配置项"), import_koishi.Schema.const("raw").description("markdown(需要assets服务,推荐`assets-qqbot-part-file`插件)") ]).role("radio").description("仅对QQ官方机器人生效,markdown模式选择").default("raw") }).description("QQ官方按钮设置"), import_koishi.Schema.union([ import_koishi.Schema.object({ markdown_button_mode: import_koishi.Schema.const("raw"), nested: import_koishi.Schema.object({ raw_markdown_button_content: import_koishi.Schema.string().role("textarea", { rows: [6, 6] }).collapse().default("## 表情包来咯~\n![${img_pxpx}](${img_url})").description("实现QQ官方bot的按钮效果,需要`canvas`服务。<br>在这里填入你的markdown内容。本插件会替换形如`{{.xxx}}`或`${xxx}`的参数为`xxx`。<br>本插件提供的参数有`command`、`img_pxpx`、`img_url`、`ctx`、`session`、`config`<br>`img_pxpx`会被替换为`img#...px #...px`<br>`img_url`会被替换为`一个链接`更多说明,详见[➩项目README](https://github.com/koishi-shangxue-plugins/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)"), raw_markdown_button_keyboard: import_koishi.Schema.string().role("textarea", { rows: [12, 12] }).collapse().default(`{ "rows": [ { "buttons": [ { "render_data": { "label": "再来一张", "style": 2 }, "action": { "type": 2, "permission": { "type": 2 }, "data": "/\${command}", "enter": true } }, { "render_data": { "label": "表情列表", "style": 2 }, "action": { "type": 2, "permission": { "type": 2 }, "data": "/\${config.emojihub_bili_command}", "enter": true } } ] } ] }`).description("实现QQ官方bot的按钮效果<br>在这里填入你的按钮内容,注意保持json格式,推荐在编辑器中编辑好后粘贴进来") }).collapse().description('➢表情包--按钮设置<br>更多说明,详见[➩项目README](https://github.com/koishi-shangxue-plugins/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">'), nestedlist: import_koishi.Schema.object({ raw_markdown_button_content: import_koishi.Schema.string().role("textarea", { rows: [6, 6] }).collapse().default("### 点击按钮触发哦!").description("实现QQ官方bot的按钮效果,需要`canvas`服务。<br>在这里填入你的markdown内容。本插件会替换形如`{{.xxx}}`或`${xxx}`的参数为`xxx`。<br>本插件提供的参数有`command`、`img_pxpx`、`img_url`、`ctx`、`session`、`config`<br>`img_pxpx`会被替换为`img#...px #...px`<br>`img_url`会被替换为`一个链接`更多说明,详见[➩项目README](https://github.com/koishi-shangxue-plugins/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)") }).collapse().description('➣表情包列表--按钮设置<br>更多说明,详见[➩项目README](https://github.com/koishi-shangxue-plugins/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">') }), import_koishi.Schema.object({ markdown_button_mode: import_koishi.Schema.const("unset").required() }) ]), import_koishi.Schema.object({ LocalSendNetworkPicturesList: import_koishi.Schema.string().role("textarea", { rows: [2, 4] }).description("将`下列指令`对应的内容下载至本地,作为本地图片发送<br>请使用逗号分隔指令").default("").experimental(), deletePictime: import_koishi.Schema.number().default(10).description("若干`秒`后 删除下载的本地临时文件").experimental(), consoleinfo: import_koishi.Schema.boolean().default(false).description("日志调试模式`日常使用无需开启`") }).description("调试选项") ]); // src/utils.ts var import_koishi2 = require("koishi"); var import_node_fs = __toESM(require("node:fs")); var import_node_path2 = __toESM(require("node:path")); var logger = new import_koishi2.Logger("emojihub-bili"); function logInfoformat(config, USER, command, message) { if (config.consoleinfo) { if (USER) { logger.info(` ${USER} 频道触发表情包 使用指令: ${command} ${message}`); } else { logger.info(message); } } } __name(logInfoformat, "logInfoformat"); function logError(message) { logger.error(message); } __name(logError, "logError"); function logInfo(config, ...args) { if (config.consoleinfo) { if (args.length > 0 && args[0].toString().length < 500) { logger.info(args[0], ...args.slice(1)); } } } __name(logInfo, "logInfo"); function replacePlaceholders(content, context, isRawMode = false) { if (typeof content === "string") { if (!/\{\{\.([^}]+)\}\}|\$\{([^}]+)\}/.test(content)) { return isRawMode ? content : [content]; } const value = content.replace(/\{\{\.([^}]+)\}\}|\$\{([^}]+)\}/g, (match, p1, p2) => { const key = p1 || p2; const replacement = key.split(".").reduce((obj, k) => { if (typeof obj === "object" && obj !== null && k in obj) { return obj[k]; } return void 0; }, context) || match; return replacement; }); return isRawMode ? value : [value]; } if (typeof content === "object" && content !== null) { if (Array.isArray(content)) { return content.map((item) => replacePlaceholders(item, context, isRawMode)); } else { const result = {}; for (const key in content) { result[key] = replacePlaceholders(content[key], context, isRawMode); } return result; } } return content; } __name(replacePlaceholders, "replacePlaceholders"); function getAllFiles(dir, fileList = []) { const files = import_node_fs.default.readdirSync(dir); files.forEach((file) => { const filePath = import_node_path2.default.join(dir, file); if (import_node_fs.default.statSync(filePath).isDirectory()) { fileList = getAllFiles(filePath, fileList); } else { fileList.push(filePath); } }); return fileList; } __name(getAllFiles, "getAllFiles"); function getVirtualFilename(filePath, rootFolderPath) { const relativePath = import_node_path2.default.relative(rootFolderPath, filePath); const parts = relativePath.split(import_node_path2.default.sep); const filename = parts.join(""); return filename; } __name(getVirtualFilename, "getVirtualFilename"); // src/core.ts var import_node_fs2 = __toESM(require("node:fs")); var import_node_path4 = __toESM(require("node:path")); // src/path.ts var import_node_path3 = __toESM(require("node:path")); var import_node_url = require("node:url"); function resolveLocalPath(input) { const value = input.trim().replace(/^['"]|['"]$/g, ""); try { return import_node_path3.default.normalize((0, import_node_url.fileURLToPath)(value)); } catch { if (import_node_path3.default.isAbsolute(value) || import_node_path3.default.win32.isAbsolute(value) || import_node_path3.default.posix.isAbsolute(value)) { return import_node_path3.default.normalize(value); } return null; } } __name(resolveLocalPath, "resolveLocalPath"); function isHttpUrl(input) { try { const { protocol } = new URL(input); return protocol === "http:" || protocol === "https:"; } catch { return false; } } __name(isHttpUrl, "isHttpUrl"); // src/media-proxy.ts var import_node_url2 = require("node:url"); var LOCAL_IMAGE_PROXY_ROUTE = "/emojihub-bili/media"; var registeredProxyContexts = /* @__PURE__ */ new Set(); function getProxyBaseUrl(ctx) { const server = ctx.server; const selfUrl = server.config?.selfUrl?.trim(); if (selfUrl) { return selfUrl.endsWith("/") ? selfUrl : `${selfUrl}/`; } const host = server.host?.trim(); const normalizedHost = !host || host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host; return `http://${normalizedHost}:${server.port || 5140}/`; } __name(getProxyBaseUrl, "getProxyBaseUrl"); function getLocalImageProxyUrl(ctx, source) { const localPath = resolveLocalPath(source); if (!ctx.server || !localPath) return null; const url = new URL(LOCAL_IMAGE_PROXY_ROUTE, getProxyBaseUrl(ctx)); url.searchParams.set("path", localPath); return url.toString(); } __name(getLocalImageProxyUrl, "getLocalImageProxyUrl"); function registerLocalImageProxyRoute(ctx) { if (!ctx.server || registeredProxyContexts.has(ctx)) return false; registeredProxyContexts.add(ctx); ctx.server.get(LOCAL_IMAGE_PROXY_ROUTE, async (koaCtx) => { const rawSource = typeof koaCtx.query.path === "string" ? koaCtx.query.path : typeof koaCtx.query.source === "string" ? koaCtx.query.source : ""; const localPath = resolveLocalPath(rawSource); if (!localPath) { koaCtx.status = 400; koaCtx.body = "invalid local path"; return; } try { const file = await ctx.http.file((0, import_node_url2.pathToFileURL)(localPath).href); koaCtx.type = file.type || file.mime || "application/octet-stream"; koaCtx.body = Buffer.from(file.data); } catch (error) { koaCtx.status = 404; koaCtx.body = error instanceof Error ? error.message : "failed to proxy local image"; } }); ctx.on("dispose", () => { registeredProxyContexts.delete(ctx); }); return true; } __name(registerLocalImageProxyRoute, "registerLocalImageProxyRoute"); // src/core.ts function isImageFile(filePath) { const lower = filePath.toLowerCase(); return lower.endsWith(".jpg") || lower.endsWith(".png") || lower.endsWith(".gif") || lower.endsWith(".bmp") || lower.endsWith(".webp"); } __name(isImageFile, "isImageFile"); function isAbsoluteLikePath(input) { return import_node_path4.default.isAbsolute(input) || import_node_path4.default.win32.isAbsolute(input) || import_node_path4.default.posix.isAbsolute(input); } __name(isAbsoluteLikePath, "isAbsoluteLikePath"); function toLocalImageUrl(ctx, localPath) { return getLocalImageProxyUrl(ctx, localPath); } __name(toLocalImageUrl, "toLocalImageUrl"); async function determineImagePath(txtPath, config, channelId, command, ctx, local_picture_name = null) { const localPath = resolveLocalPath(txtPath); if (localPath) { try { const stats = import_node_fs2.default.lstatSync(localPath); if (stats.isDirectory()) { return await getRandomImageFromFolder(localPath, config, channelId, command, ctx, local_picture_name); } if (stats.isFile()) { if (isImageFile(localPath)) { logInfoformat(config, channelId, command, `本地图片的绝对路径: ${txtPath}`); const imageUrl = toLocalImageUrl(ctx, localPath); if (!imageUrl) return { imageUrl: null, isLocal: false }; return { imageUrl, isLocal: true, imageName: import_node_path4.default.basename(localPath), imageTime: stats.mtime, imageSize: stats.size, imagePath: localPath }; } if (localPath.toLowerCase().endsWith(".txt")) { return await getRandomImageUrlFromFile(localPath, config, channelId, command, ctx); } } } catch { return { imageUrl: null, isLocal: false }; } } if (isHttpUrl(txtPath)) { logInfoformat(config, channelId, command, `直接的图片链接 ${txtPath}`); return { imageUrl: txtPath, isLocal: false }; } const allValidPaths = getAllValidPaths(config); if (allValidPaths.length === 0) { return { imageUrl: null, isLocal: false }; } const pickedPath = allValidPaths[Math.floor(Math.random() * allValidPaths.length)]; const pickedLocalPath = resolveLocalPath(pickedPath); if (!pickedLocalPath) return { imageUrl: null, isLocal: false }; try { const stats = import_node_fs2.default.lstatSync(pickedLocalPath); if (stats.isDirectory()) { return await getRandomImageFromFolder(pickedLocalPath, config, channelId, command, ctx, local_picture_name); } if (stats.isFile()) { if (isImageFile(pickedLocalPath)) { logInfoformat(config, channelId, command, `随机选择的本地图片路径: ${pickedPath}`); const imageUrl = toLocalImageUrl(ctx, pickedLocalPath); if (!imageUrl) return { imageUrl: null, isLocal: false }; return { imageUrl, isLocal: true, imageName: import_node_path4.default.basename(pickedLocalPath), imageTime: stats.mtime, imageSize: stats.size, imagePath: pickedLocalPath }; } if (pickedLocalPath.toLowerCase().endsWith(".txt")) { return await getRandomImageUrlFromFile(pickedLocalPath, config, channelId, command, ctx); } } } catch { return { imageUrl: null, isLocal: false }; } return { imageUrl: null, isLocal: false }; } __name(determineImagePath, "determineImagePath"); function getRandomEmojiHubCommand(config) { const commands = config.MoreEmojiHubList.map((emoji) => emoji.command); if (commands.length > 0) { return commands[Math.floor(Math.random() * commands.length)]; } return null; } __name(getRandomEmojiHubCommand, "getRandomEmojiHubCommand"); function getAllValidPaths(config) { return config.MoreEmojiHubList.filter((emoji) => { const sourceUrl = emoji.source_url; return isAbsoluteLikePath(sourceUrl) || isHttpUrl(sourceUrl) || sourceUrl.startsWith("file:"); }).map((emoji) => emoji.source_url); } __name(getAllValidPaths, "getAllValidPaths"); async function getRandomImageFromFolder(folderPath, config, channelId, command, ctx, local_picture_name) { if (!import_node_fs2.default.existsSync(folderPath)) { logError(`错误:路径不存在: ${folderPath}`); return { imageUrl: null, isLocal: false }; } let files = config.searchSubfolders ? getAllFiles(folderPath) : import_node_fs2.default.readdirSync(folderPath).map((file) => import_node_path4.default.join(folderPath, file)); files = files.filter((file) => isImageFile(file)); if (files.length === 0) { logError("文件夹中未找到有效图片文件(jpg,png,gif,webp,bmp)"); return { imageUrl: null, isLocal: false }; } if (local_picture_name?.length > 0) { files = files.filter((file) => { let filenameToMatch; if (config.searchSubfoldersWithfilename && config.searchSubfolders) { filenameToMatch = getVirtualFilename(file, folderPath); } else { filenameToMatch = import_node_path4.default.basename(file); } const filenameLower = filenameToMatch.toLowerCase(); return local_picture_name.every((keyword) => filenameLower.includes(keyword.toLowerCase())); }); if (files.length === 0) { logError(`未找到匹配关键词 "${local_picture_name.join(" ")}" 的图片文件`); return { imageUrl: null, isLocal: false }; } } const imagePath = files[Math.floor(Math.random() * files.length)]; logInfoformat(config, channelId, command, `使用文件夹 ${folderPath} 发送本地图片为 ${imagePath}`); const stats = import_node_fs2.default.statSync(imagePath); const imageUrl = toLocalImageUrl(ctx, imagePath); if (!imageUrl) return { imageUrl: null, isLocal: false }; return { imageUrl, isLocal: true, imageName: import_node_path4.default.basename(imagePath), imageTime: stats.mtime, imageSize: stats.size, imagePath }; } __name(getRandomImageFromFolder, "getRandomImageFromFolder"); async function getRandomImageUrlFromFile(txtPath, config, channelId, command, ctx) { let urls; let imageUrl; try { urls = import_node_fs2.default.readFileSync(txtPath, "utf8").split("\n").filter((url) => url.trim() !== ""); } catch (error) { if (error instanceof Error && error.message === "ENOENT") { return { imageUrl: null, isLocal: false }; } logError(error); return { imageUrl: null, isLocal: false }; } if (urls.length === 0) { logError(`错误!无有效URL可用:${txtPath}`); return { imageUrl: null, isLocal: false }; } const index = Math.floor(Math.random() * urls.length); let txtUrl = urls[index].trim(); const extraPrefix = "https:"; const bilibiliPrefix = "https://i0.hdslb.com/bfs/"; if (txtUrl.startsWith(extraPrefix + bilibiliPrefix)) { txtUrl = txtUrl.replace(extraPrefix, ""); } if (!txtUrl.startsWith("https://") && !txtUrl.startsWith("http://")) { txtUrl = bilibiliPrefix + txtUrl; } imageUrl = txtUrl.trim(); if (config.LocalSendNetworkPicturesList && config.LocalSendNetworkPicturesList.length > 0) { const normalizedList = config.LocalSendNetworkPicturesList.split(/\n|,|,/).map((item) => item.trim().toLowerCase()); const lowerCaseCommand = command.toLowerCase(); if (normalizedList.includes(lowerCaseCommand)) { const outputPath = import_node_path4.default.join(__dirname, `${Date.now()}.png`); try { const localOutputPath = await downloadImage(txtUrl, outputPath, ctx); ctx.setTimeout(() => { import_node_fs2.default.unlinkSync(localOutputPath); logInfoformat(config, null, null, `临时文件已删除:${localOutputPath}`); }, config.deletePictime * 1e3); logInfoformat(config, channelId, command, `下载并发送本地图片: ${localOutputPath}`); const localImageUrl = toLocalImageUrl(ctx, localOutputPath); return { imageUrl: localImageUrl, isLocal: true, imagePath: localOutputPath }; } catch (downloadError) { logError(`图片下载失败:${downloadError.message}`); return { imageUrl: null, isLocal: false }; } } } logInfoformat(config, channelId, command, `使用文件 ${txtPath} 发送URL为:${imageUrl}`); return { imageUrl, isLocal: false }; } __name(getRandomImageUrlFromFile, "getRandomImageUrlFromFile"); async function downloadImage(url, outputPath, ctx) { try { const response = await ctx.http.get(url, { responseType: "arraybuffer" }); const buffer = Buffer.from(response); await import_node_fs2.default.promises.writeFile(outputPath, buffer); return outputPath; } catch (error) { logError(`下载图片失败: ${error.message}`); throw error; } } __name(downloadImage, "downloadImage"); function listAllCommands(config) { const allCommands = config.MoreEmojiHubList.map((emoji) => emoji.command); if (allCommands.length === 0) { logError("未找到任何表情包指令。"); } return allCommands; } __name(listAllCommands, "listAllCommands"); // src/canvas-source.ts async function loadCanvasImageSource(ctx, source) { const localPath = resolveLocalPath(source); if (!localPath) { return ctx.canvas.loadImage(source); } const proxyUrl = getLocalImageProxyUrl(ctx, localPath); if (!proxyUrl) { throw new Error("local image proxy unavailable"); } return ctx.canvas.loadImage(proxyUrl); } __name(loadCanvasImageSource, "loadCanvasImageSource"); // src/markdown.ts function clampPage(page, totalPages) { if (!Number.isFinite(page) || page < 1) return 1; return Math.min(Math.floor(page), totalPages); } __name(clampPage, "clampPage"); function pickColumns(total) { if (total < 5) return 1; if (total <= 10) return 2; if (total <= 15) return 3; return 4; } __name(pickColumns, "pickColumns"); function chunk(items, size) { const rows = []; for (let i = 0; i < items.length; i += size) { rows.push(items.slice(i, i + size)); } return rows; } __name(chunk, "chunk"); function createMarkdownMessage(session) { const markdownMessage = { msg_type: 2, markdown: {}, keyboard: {} }; if (session.messageId) { markdownMessage.msg_id = session.messageId; } return markdownMessage; } __name(createMarkdownMessage, "createMarkdownMessage"); function getCommandLabel(rootCommandName, command) { const fullName = String(command?.name || command?.displayName || "").trim(); const root = String(rootCommandName || "").trim(); if (!fullName) return ""; if (root && fullName.startsWith(`${root}/`)) return fullName.slice(root.length + 1); return fullName.split("/").pop() || fullName; } __name(getCommandLabel, "getCommandLabel"); function collectVisibleCommands(ctx, session, rootCommandName) { const rootCommand = ctx.$commander.get(rootCommandName, session); const children = Array.isArray(rootCommand?.children) ? rootCommand.children : []; const entries = []; for (const command of children) { if (typeof command?.match === "function" && !command.match(session)) continue; const label = getCommandLabel(rootCommandName, command); if (!label) continue; if (entries.some(([existing]) => existing === label)) continue; entries.push([label, command]); } return entries; } __name(collectVisibleCommands, "collectVisibleCommands"); function buildKeyboardRows(ctx, session, rootCommandName, page) { const commands = collectVisibleCommands(ctx, session, rootCommandName); const columns = pickColumns(commands.length); const pageSize = columns * 5; const totalPages = Math.max(1, Math.ceil(commands.length / pageSize)); const currentPage = clampPage(page, totalPages); const pageItems = commands.slice((currentPage - 1) * pageSize, currentPage * pageSize); const rows = chunk(pageItems, columns).map((row) => ({ buttons: row.map(([label]) => ({ render_data: { label, style: 1 }, action: { type: 2, permission: { type: 2 }, data: `/${label}`, enter: true } })) })); if (totalPages > 1) { const navButtons = []; if (currentPage > 1) { navButtons.push({ render_data: { label: "上一页", style: 2 }, action: { type: 2, permission: { type: 2 }, data: `/${rootCommandName} ${currentPage - 1}`, enter: true } }); } if (currentPage < totalPages) { navButtons.push({ render_data: { label: "下一页", style: 2 }, action: { type: 2, permission: { type: 2 }, data: `/${rootCommandName} ${currentPage + 1}`, enter: true } }); } if (navButtons.length) rows.push({ buttons: navButtons }); } return rows; } __name(buildKeyboardRows, "buildKeyboardRows"); function command_list_markdown(ctx, session, config, rootCommandName, page = 1) { const markdownMessage = createMarkdownMessage(session); markdownMessage.markdown = { content: config.nestedlist.raw_markdown_button_content || "" }; try { const rows = buildKeyboardRows(ctx, session, rootCommandName, Number(page) || 1); markdownMessage.keyboard = { content: { rows } }; } catch (error) { logError(`解析列表 Markdown 出错: ${error}`); return null; } logInfo(config, `列表 Markdown 参数: ${JSON.stringify(markdownMessage, null, 2)}`); return markdownMessage; } __name(command_list_markdown, "command_list_markdown"); async function markdown(ctx, session, command, imageUrl, config, localimage, imageSize) { const markdownMessage = createMarkdownMessage(session); let originalWidth; let originalHeight; const sizeMatch = typeof imageUrl === "string" ? imageUrl.match(/\?px=(\d+)x(\d+)$/) : null; if (imageSize?.width && imageSize?.height) { originalWidth = imageSize.width; originalHeight = imageSize.height; } else if (sizeMatch) { originalWidth = parseInt(sizeMatch[1], 10); originalHeight = parseInt(sizeMatch[2], 10); } else { const loadTarget = localimage || imageUrl; const canvasimage = await loadCanvasImageSource(ctx, String(loadTarget)); originalWidth = canvasimage.naturalWidth || canvasimage.width; originalHeight = canvasimage.naturalHeight || canvasimage.height; } try { const rawMarkdownContent = config.nested.raw_markdown_button_content; const rawMarkdownKeyboard = config.nested.raw_markdown_button_keyboard; const replacedMarkdownContent = replacePlaceholders(rawMarkdownContent, { session, config, img_pxpx: `img#${originalWidth}px #${originalHeight}px`, img_url: imageUrl, command }, true); const replacedMarkdownKeyboard = replacePlaceholders(rawMarkdownKeyboard, { session, config, command }, true).replace(/^[\s\S]*?"keyboard":\s*/, "").replace(/\\n/g, "").replace(/\\"/g, '"').trim(); const keyboard = JSON.parse(replacedMarkdownKeyboard); markdownMessage.markdown = { content: replacedMarkdownContent }; markdownMessage.keyboard = { content: keyboard }; } catch (error) { logError(`解析原生 Markdown 出错: ${error}`); return null; } logInfo(config, `Markdown 模板参数: ${JSON.stringify(markdownMessage, null, 2)}`); return markdownMessage; } __name(markdown, "markdown"); async function sendmarkdownMessage(ctx, session, message, config) { if (!message) return; if (config) logInfo(config, "正在调用sendmarkdownMessage发送md"); try { const { guild, user } = session.event; const { qq, qqguild, channelId } = session; if (guild?.id) { if (qq) { await qq.sendMessage(channelId, message); } else if (qqguild) { await qqguild.sendMessage(channelId, message); } } else if (user?.id && qq) { await qq.sendPrivateMessage(user.id, message); } } catch (error) { ctx.logger.error(`发送 markdown 消息时出错`, error); } } __name(sendmarkdownMessage, "sendmarkdownMessage"); // src/index.ts var inject = { required: ["server"], optional: ["canvas", "assets"] }; var name = "emojihub-bili"; var reusable = true; function apply(ctx, config) { const emojihub_bili_codecommand = config.emojihub_bili_command; registerLocalImageProxyRoute(ctx); async function getLocalImageSize(canvasSource) { if (!ctx.canvas) throw new Error("canvas service not available"); logInfo(config, `获取本地图片尺寸,canvasSource: ${canvasSource}`); const canvasimage = await loadCanvasImageSource(ctx, canvasSource); const width = canvasimage.naturalWidth || canvasimage.width; const height = canvasimage.naturalHeight || canvasimage.height; return { width, height }; } __name(getLocalImageSize, "getLocalImageSize"); async function resolveAssetsUrlForMarkdown(imageUrl, imagePath) { if (!ctx.assets) throw new Error("assets service not available"); const localTarget = imageUrl || imagePath; const transformed = await ctx.assets.transform(String(import_koishi3.h.image(localTarget))); const match = transformed.match(/<img\s+src="([^"]+)"/i); if (!match?.[1]) throw new Error(`assets.transform did not return an image url: ${transformed}`); return import_koishi3.h.unescape(match[1]); } __name(resolveAssetsUrlForMarkdown, "resolveAssetsUrlForMarkdown"); ctx.i18n.define( "zh-CN", { commands: { [emojihub_bili_codecommand]: { description: `表情包功能`, messages: { "notfound_txt": "ERROR!找不到文件或文件为空!指令:{0}", "List_of_emojis": "可用的表情包指令:{0}", "notallowednum": `{0}次超出单次返回最大值 请使用指令:{1} -n {2}` } }, [config.emojihub_onemore]: { description: `触发上次的表情包`, messages: { "nocommand": `没有找到上一个命令,请先执行一个命令! ➣例如: ${config.emojihub_randompic}` } }, [config.emojihub_randompic]: { description: `从全部表情包里随机抽`, messages: { "noemoji": "没有任何表情包配置,请检查插件配置项!" } } } } ); const lastCommandByChannel = {}; function updateLastCommand(differentiationID, command) { lastCommandByChannel[differentiationID] = command; logInfo(config, "记录到command为: " + command + " 区别ID: " + differentiationID); } __name(updateLastCommand, "updateLastCommand"); async function sendMultipleEmojis(session, command, num) { const maxAllowed = config.maxexecutetime || 10; if (num > maxAllowed) { await session.send(import_koishi3.h.text(session.text(`commands.${emojihub_bili_codecommand}.messages.notallowednum`, [num, command, maxAllowed]))); return; } const numToSend = Math.min(num || 1, maxAllowed); for (let i = 0; i < numToSend; i++) { if (command === config.emojihub_onemore) { const identifier = config.repeatCommandDifferentiation === "userId" ? session.userId : session.channelId; const lastCommand = lastCommandByChannel[identifier]; if (lastCommand) { await session.execute(lastCommand); } else { await session.send(session.text(".nocommand")); return; } } else { await session.execute(command); } } } __name(sendMultipleEmojis, "sendMultipleEmojis"); ctx.command(config.emojihub_bili_command).action(async ({ session }) => { const txtCommandList = listAllCommands(config); logInfo(config, `指令列表txtCommandList: ` + txtCommandList); if (session.platform === "qq" || session.platform === "qqguild") { let markdownMessage = command_list_markdown(ctx, session, config, emojihub_bili_codecommand, 1); await sendmarkdownMessage(ctx, session, markdownMessage, config); } else { const commandText = txtCommandList.join("\n"); await session.send(import_koishi3.h.text(session.text(`commands.${emojihub_bili_codecommand}.messages.List_of_emojis`, [` ${commandText}`]))); } }); ctx.on("ready", () => { config.MoreEmojiHubList.forEach(({ command, source_url }) => { ctx.command(`${config.emojihub_bili_command}/${command} [local_picture_name...]`).example(`${command} 关键词1 关键词2 关键词3`).option("numpics", `-n <numpics:number> 指定返回数量`).action(async ({ session, options }, ...local_picture_name) => { if (options?.numpics) { await sendMultipleEmojis(session, `${command} ${local_picture_name.join(" ")}`.trim(), options.numpics); return; } const imageResult = await determineImagePath(source_url, config, session.channelId, command, ctx, local_picture_name); if (!imageResult.imageUrl) { await session.send(import_koishi3.h.text(session.text(`commands.${emojihub_bili_codecommand}.messages.notfound_txt`, [command]))); return; } const identifier = config.repeatCommandDifferentiation === "userId" ? session.userId : session.channelId; updateLastCommand(identifier, command); try { let message; if ((session.platform === "qq" || session.platform === "qqguild") && config.markdown_button_mode === "raw") { if (imageResult.isLocal) { const imageSize = imageResult.imageUrl ? await getLocalImageSize(imageResult.imageUrl) : void 0; const assetsUrl = await resolveAssetsUrlForMarkdown(imageResult.imageUrl, imageResult.imagePath); logInfo(config, `[assets] converted local image to url: ${assetsUrl}`); message = await markdown(ctx, session, command, assetsUrl, config, imageResult.imageUrl, imageSize); await sendmarkdownMessage(ctx, session, message, config); } else { message = await markdown(ctx, session, command, imageResult.imageUrl, config); await sendmarkdownMessage(ctx, session, message, config); } } else { if (imageResult.isLocal) { const format = config.localPictureToName; logInfo(config, imageResult.imageUrl); const fileSizeKB = (imageResult.imageSize / 1024).toFixed(2); const fileSizeMB = (imageResult.imageSize / (1024 * 1024)).toFixed(2); const formattedSize = imageResult.imageSize < 1024 * 1024 ? `${fileSizeKB} KB` : `${fileSizeMB} MB`; const formattedTime = imageResult.imageTime.toLocaleString("zh-CN", { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }).replace(/\//g, "-"); const imageBuffer = Buffer.from((await ctx.http.file(imageResult.imageUrl)).data); const base64Data = imageBuffer.toString("base64"); const assetsUrl = `data:image/png;base64,${base64Data}`; const context = { IMAGE: import_koishi3.h.image(assetsUrl), NAME: imageResult.imageName, TIME: formattedTime, SIZE: formattedSize, PATH: imageResult.imagePath }; const messageContent = replacePlaceholders(format, context); logInfo(config, "变量替换本地文件名称,messageContent:", messageContent); try { message = await session.send(import_koishi3.h.unescape(`${messageContent}`.replace(/\\n/g, "\n"))); } catch (error) { ctx.logger.error("发送本地图片失败:", error); } } else { try { message = await session.send(import_koishi3.h.image(imageResult.imageUrl)); } catch (error) { ctx.logger.error("发送网络图片失败:", error); } } } if (config.deleteMsg) { ctx.setTimeout(async () => { try { await session.bot.deleteMessage(session.channelId, message); } catch (error) { logError(`撤回消息失败: ${error}`); logError(error); } }, config.deleteMsgtime * 1e3); } } catch (error) { logError(`Error sending image: ${error}`); logError(error); } }); }); }); ctx.command(`${config.emojihub_bili_command}/${config.emojihub_onemore}`).action(async ({ session, options }) => { const identifier = config.repeatCommandDifferentiation === "userId" ? session.userId : session.channelId; const lastCommand = lastCommandByChannel[identifier]; logInfo(config, "尝试在区分ID " + identifier + " 中执行最后一个命令: " + lastCommand); if (lastCommand) { await session.execute(`${lastCommand}`); } else { await session.send(session.text(".nocommand")); } }); ctx.command(`${config.emojihub_bili_command}/${config.emojihub_randompic}`).action(async ({ session, options }) => { const randomEmojiHubCommand = getRandomEmojiHubCommand(config); if (randomEmojiHubCommand) { await session.execute(randomEmojiHubCommand); logInfoformat(config, session.channelId, randomEmojiHubCommand, config.emojihub_randompic); return; } else { await session.send(session.text(".noemoji")); } }); if (config.autoEmoji === "定量消息发送" && (config.groupListmapping.length || config.allgroupautoEmoji)) { const groups = {}; config.groupListmapping.forEach(({ groupList, defaultemojicommand, count, enable }) => { if (enable === true) { groups[groupList] = { blacklisted: true }; } else { groups[groupList] = { emojicommand: defaultemojicommand, threshold: count }; } }); ctx.middleware(async (session, next) => { const channelId = session.channelId; let groupConfig = groups[channelId]; if (groupConfig && groupConfig.blacklisted) { return next(); } if (!groupConfig && config.allgroupautoEmoji) { groupConfig = { count: 0, emojicommand: config.allgroupemojicommand, threshold: config.count }; groups[channelId] = groupConfig; } if (groupConfig) { groupConfig.count = (groupConfig.count || 0) + 1; logInfo(config, `${channelId} :${groupConfig.count} :${session.content}`); if (groupConfig.count >= groupConfig.threshold) { const randomNumber = Math.random(); if (randomNumber <= config.triggerprobability) { logInfo(config, `定量消息发送:概率判断:${randomNumber} <= ${config.triggerprobability} 触发表情包`); let emojicommands = groupConfig.emojicommand.split(/\n|,|,/).map((cmd) => cmd.trim()); const randomCommand = emojicommands[Math.floor(Math.random() * emojicommands.length)]; logInfo(config, `随机选择的指令: ${randomCommand}`); const emojiConfig = config.MoreEmojiHubList.find(({ command }) => command === randomCommand); if (emojiConfig) { const imageResult = await determineImagePath(emojiConfig.source_url, config, channelId, emojiConfig.command, ctx); if (image