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群即可发按钮!😍可自定义添加或删除任意表情包!基本每个指令都有上千张!支持定时/定量消息触发 • 60.5 kB
JavaScript
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").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