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群即可发按钮!😍可自定义添加或删除任意表情包!基本每个指令都有上千张!支持定时/定量消息触发 • 99.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.apply = exports.Config = exports.usage = exports.inject = exports.name = void 0;
const fs = require('node:fs');
const url = require("node:url");
const path = require("node:path");
const crypto = require("node:crypto");
const { Schema, Logger, h } = require("koishi");
exports.inject = {
optional: ['canvas', "cron"]
};
exports.name = 'emojihub-bili';
exports.reusable = true; // 声明此插件可重用
exports.usage = `
<h2><a href="https://www.npmjs.com/package/koishi-plugin-emojihub-bili" target="_blank">如何额外添加自己喜欢的表情包</a></h2>
<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>
<p> </p>
<h2>温馨提示:</h2>
<p><br>请勿将自定义的txt文件与本插件放置在同一目录下,以免插件更新导致文件丢失。</p>
<p>目前EmojiHub-bili默认提供 <code>43套</code> 表情包。若您的配置内容有误差,请点击 <code>MoreEmojiHubList</code> 表格右上角按钮内的 <code>恢复默认值</code>。</p>
<p>若开启插件后,指令不出现,<a href="/market?keyword=commands">请重新开关commands插件</a></p>
`;
const logger = new Logger('emojihub-bili');
const defaultMoreEmojiHubList = [
// 下面实际有效为 43套
{ command: '随机emojihub表情包', source_url: "无效路径/内容会调用随机表情包。注意与【随机表情包】指令的功能一致,但【随机表情包】不可被填入表格使用,【随机emojihub表情包】可以,因为在这个配置项里。" },
{ command: '本地图库示例', source_url: path.join(__dirname, 'txts') },
{ command: '网络图片示例', source_url: 'https://i0.hdslb.com/bfs/article/afc31d0e398204d94478473a497028e6352074746.gif' },
{ command: '2233娘小剧场表情包', source_url: path.join(__dirname, '../txts/2233娘小剧场.txt') },
{ command: 'acomu414表情包', source_url: path.join(__dirname, '../txts/acomu414.txt') },
{ command: 'ba表情包', source_url: path.join(__dirname, '../txts/ba.txt') },
{ command: 'capoo表情包', source_url: path.join(__dirname, '../txts/capoo.txt') },
{ command: 'chiikawa表情包', source_url: path.join(__dirname, '../txts/chiikawa.txt') },
{ command: 'downvote表情包', source_url: path.join(__dirname, '../txts/Downvote.txt') },
{ command: 'doro表情包', source_url: path.join(__dirname, '../txts/doro.txt') },
{ command: 'eveonecat表情包', source_url: path.join(__dirname, '../txts/eveonecat.txt') },
{ command: 'fufu表情包', source_url: path.join(__dirname, '../txts/fufu.txt') },
{ command: 'girlsbandcry', source_url: path.join(__dirname, '../txts/GirlsBandCry.txt') },
{ command: 'kemomimi表情包', source_url: path.join(__dirname, '../txts/kemomimi酱表情包.txt') },
{ command: 'koishi-meme表情包', source_url: path.join(__dirname, '../txts/koimeme.txt') },
{ command: 'mygo表情包', source_url: path.join(__dirname, '../txts/mygo.txt') },
{ command: 'seseren表情包', source_url: path.join(__dirname, '../txts/seseren.txt') },
{ command: '阿夸表情包', source_url: path.join(__dirname, '../txts/阿夸.txt') },
{ command: '阿尼亚表情包', source_url: path.join(__dirname, '../txts/阿尼亚.txt') },
{ command: '白圣女表情包', source_url: path.join(__dirname, '../txts/白圣女.txt') },
{ command: '白圣女漫画表情包', source_url: path.join(__dirname, '../txts/白圣女黑白.txt') },
{ command: '败犬女主表情包', source_url: path.join(__dirname, '../txts/败犬女主.txt') },
{ command: '柴郡表情包', source_url: path.join(__dirname, '../txts/柴郡.txt') },
{ command: '初音Q版表情包', source_url: path.join(__dirname, '../txts/初音未来Q.txt') },
{ command: '甘城猫猫表情包', source_url: path.join(__dirname, '../txts/甘城猫猫.txt') },
{ command: '孤独摇滚表情包', source_url: path.join(__dirname, '../txts/孤独摇滚.txt') },
{ command: '狗妈表情包', source_url: path.join(__dirname, '../txts/狗妈.txt') },
{ command: '滑稽表情包', source_url: path.join(__dirname, '../txts/滑稽.txt') },
{ command: '疾旋鼬表情包', source_url: path.join(__dirname, '../txts/疾旋鼬.txt') },
{ command: '卡拉彼丘表情包', source_url: path.join(__dirname, '../txts/卡拉彼丘.txt') },
{ command: '流萤表情包', source_url: path.join(__dirname, '../txts/流萤.txt') },
{ command: '龙图表情包', source_url: path.join(__dirname, '../txts/龙图.txt') },
{ command: '鹿乃子表情包', source_url: path.join(__dirname, '../txts/鹿乃子.txt') },
{ command: '玛丽猫表情包', source_url: path.join(__dirname, '../txts/玛丽猫.txt') },
{ command: '小c表情包', source_url: path.join(__dirname, '../txts/蜜汁工坊.txt') },
{ command: '男娘武器库表情包', source_url: path.join(__dirname, '../txts/男娘武器库.txt') },
{ command: '千恋万花表情包', source_url: path.join(__dirname, '../txts/0721.txt') },
{ command: '赛马娘表情包', source_url: path.join(__dirname, '../txts/赛马娘.txt') },
{ command: '瑟莉亚表情包', source_url: path.join(__dirname, '../txts/瑟莉亚.txt') },
{ command: '藤田琴音表情包', source_url: path.join(__dirname, '../txts/藤田琴音.txt') },
{ command: '小黑子表情包', source_url: path.join(__dirname, '../txts/小黑子.txt') },
{ command: '心海表情包', source_url: path.join(__dirname, '../txts/心海.txt') },
{ command: '绪山真寻表情包', source_url: path.join(__dirname, '../txts/绪山真寻.txt') },
{ command: '亚托莉表情包', source_url: path.join(__dirname, '../txts/亚托莉表情包.txt') },
{ command: '永雏小菲表情包', source_url: path.join(__dirname, '../txts/永雏小菲.txt') },
{ command: '宇佐紀表情包', source_url: path.join(__dirname, '../txts/宇佐紀.txt') },
// { command: '', source_url: path.join(__dirname, '../txts/.txt') },
// 以后添加其他的命令...未完待续
];
exports.Config = Schema.intersect([
Schema.object({
emojihub_bili_command: Schema.string().default('emojihub-bili').description('`父级指令`的指令名称').pattern(/^\S+$/),
MoreEmojiHubList: Schema.array(Schema.object({
command: Schema.string().description('注册的指令名称'),
source_url: Schema.string().description('表情包文件地址'),
})).role('table').default(defaultMoreEmojiHubList)
.description('表情包指令映射表<br>▶ 若丢失了旧版本`MoreEmojiHub`配置 请先回退到 1.3.0 版本<br>▶ 若出现配置问题 请点击右方按钮 可以恢复到默认值<br>右列`文件地址`可以填入`txt绝对路径`、`文件夹绝对路径`、`图片直链`、`图片文件绝对路径`。支持格式 详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)'),
deleteMsg: Schema.boolean().description("`开启后`自动撤回表情").default(false),
maxexecutetime: Schema.number().description('`-n 选项`指定 允许单次返回的 表情包最大数<br>例如默认10 :`ba表情包 -n 30`,可以返回10张').default(10),
}).description('表情包设置'),
Schema.union([
Schema.object({
deleteMsg: Schema.const(true).required(),
deleteMsgtime: Schema.number().default(30).description('若干`秒`后 撤回表情').min(1),
}),
Schema.object({}),
]),
Schema.object({
repeatCommandDifferentiation: Schema.union([
Schema.const('channelId').description('按频道ID区分'),
Schema.const('userId').description('按用户ID区分'),
]).role('radio').default("channelId").description('`再来一张`指令的区分逻辑。<br>按频道ID区分:触发指令后发送当前频道最后触发的表情包<br>按用户ID区分:触发指令后发送当前用户最后触发的表情包'),
searchSubfolders: Schema.boolean().description("本地发图,输入提供文件名称参数时,是否递归搜索文件夹。<br>`开启后 对于本地文件夹地址 会搜索其子文件夹内全部的图片`").default(true),
localPictureToName: 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/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)")
.default("${IMAGE}"),
}).description('进阶设置'),
Schema.object({
autoEmoji: Schema.union(["取消应用", '定量消息发送', '定时发送']).description("打开后,开启自动表情包功能 <br>▶ 定量消息发送: `达到一定消息数量 自动触发表情包`<br>▶ 定时发送: `使用cron表达式定时触发表情包`此项需要`cron`服务").default("取消应用"),
}).description('自动表情包设置'),
Schema.union([
Schema.object({
autoEmoji: Schema.const("定量消息发送").required(),
middleware: Schema.boolean().description('开启后使用前置中间件').default(true),
triggerprobability: Schema.percent().default(0.8).description('达到消息数量阈值时,发送表情包的概率 `范围为 0 到 1 `'),
groupListmapping: Schema.array(Schema.object({
groupList: Schema.string().description('开启自动表情包的群组ID'),
defaultemojicommand: Schema.string().description('表情包指令名称 `应与上方指令表格对应`'),
count: Schema.number().description('触发自动表情包的消息数量的阈值').default(30),
enable: Schema.boolean().description('勾选后 屏蔽该群 的自动表情包').default(false),
})).role('table').description('表情包指令映射 `注意群组ID不要多空格什么的`<br>私聊频道有`private:`前缀<br>表情包名称请通过逗号分隔')
.default([
{
"groupList": "114514",
"defaultemojicommand": "koishi-meme,白圣女表情包,男娘武器库",
"enable": false
},
{
"groupList": "private:1919810",
"defaultemojicommand": "随机emojihub表情包",
"enable": true
}
]),
allgroupautoEmoji: Schema.boolean().description("`全部群组` 开启自动表情包").default(false),
count: Schema.number().description('`全部群组` 触发自动表情包的消息数量的阈值').default(30),
allgroupemojicommand: Schema.string().role('textarea', { rows: [2, 4] })
.description('`全部群组的` 表情包指令映射`一行一个指令 或者 逗号分隔` <br> 可以同时在`groupListmapping`指定群组的表情包内容').default(`宇佐紀表情包\n白圣女表情包\n白圣女漫画表情包`),
}),
Schema.object({
autoEmoji: Schema.const("定时发送").required(),
bot: Schema.number().description('定时消息由第几个bot发出?`第一个是0,第二个是1,...`<br>▶ 如果你只接了一个机器人,那么`0`即可').default(0).min(0),
triggerprobability: Schema.percent().default(0.8).description('达到预定时间时,发送表情包的概率 `范围为 0 到 1 `'),
groupListmapping: Schema.array(Schema.object({
groupList: Schema.string().description('开启自动表情包的群组ID'),
defaultemojicommand: Schema.string().description('表情包指令名称 `应与上方指令表格对应`'),
cronTime: Schema.string().description('定时设置:cron语法'),
enable: 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 * * * *"
}
]),
}),
Schema.object({}),
]),
Schema.object({
markdown_button_mode: Schema.union([
Schema.const('unset').description('取消应用此配置项'),
Schema.const('json').description('json按钮-----------20 群(频道不可用)'),
Schema.const('markdown').description('被动md模板--------2000 DAU / 私域'),
Schema.const('markdown_raw_json').description('被动md模板--------2000 DAU - 原生按钮'),
Schema.const('raw').description('原生md------------10000 DAU'),
]).role('radio').description('markdown/按钮模式选择').default("unset"),
}).description('QQ官方按钮设置'),
Schema.union([
Schema.object({
markdown_button_mode: Schema.const("json").required(),
markdown_button_mode_initiative: Schema.boolean().description("开启后,使用 主动消息 发送markdown。<br>即开启后不带`messageId`发送<br>适用于私域机器人频道使用。私域机器人需要使用`被动md模板、json模板`并且开启此配置项").default(false),
markdown_button_mode_keyboard: Schema.boolean().description("开启后,markdown加上按钮。关闭后,不加按钮内容哦<br>不影响markdown发送,多用于调试功能使用").default(true).experimental(),
markdown_button_mode_without_emojilist_keyboard: Schema.boolean().description("开启后,表情包列表使用下方`nestedlist`配置的表情包列表按钮。关闭后,仅发送普通的文字列表").default(true).experimental(),
nested: Schema.object({
json_button_template_id: Schema.string().description("模板ID<br>形如 `123456789_1234567890` 的ID编号<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)").pattern(/^\d+_\d+$/),
}).collapse().description('➢表情包--按钮设置<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">'),
nestedlist: Schema.object({
json_button_template_id: Schema.string().description("模板ID<br>形如 `123456789_1234567890` 的ID编号<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)").pattern(/^\d+_\d+$/),
}).collapse().description('➣表情包列表--按钮设置<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">'),
}),
Schema.object({
markdown_button_mode: Schema.const("markdown").required(),
markdown_button_mode_initiative: Schema.boolean().description("开启后,使用 主动消息 发送markdown。<br>即开启后不带`messageId`发送<br>适用于私域机器人频道使用。私域机器人需要使用`被动md模板、json模板`并且开启此配置项").default(false),
markdown_button_mode_keyboard: Schema.boolean().description("开启后,markdown加上按钮。关闭后,不加按钮内容哦<br>不影响markdown发送,多用于调试功能使用").default(true).experimental(),
markdown_button_mode_without_emojilist_keyboard: Schema.boolean().description("开启后,表情包列表使用下方`nestedlist`配置的表情包列表按钮。关闭后,仅发送普通的文字列表").default(true).experimental(),
nested: Schema.object({
markdown_button_template_id: Schema.string().description("md模板ID<br>形如 `123456789_1234567890` 的ID编号,发送markdown").pattern(/^\d+_\d+$/),
markdown_button_keyboard_id: Schema.string().description("按钮模板ID<br>形如 `123456789_1234567890` 的ID编号,发送按钮").pattern(/^\d+_\d+$/),
markdown_button_content_table: Schema.array(Schema.object({
raw_parameters: Schema.string().description("原始参数名称"),
replace_parameters: Schema.string().description("替换参数名称"),
})).role('table').default([
{
"raw_parameters": "your_markdown_text_1",
"replace_parameters": "表情包来啦!"
},
{
"raw_parameters": "your_markdown_text_2",
"replace_parameters": "这是你的表情包哦😽"
},
{
"raw_parameters": "your_markdown_img",
"replace_parameters": "${img_pxpx}"
},
{
"raw_parameters": "your_markdown_url",
"replace_parameters": "${img_url}"
}
]).description("替换参数映射表<br>本插件会替换模板变量,请在左侧填入模板变量,右侧填入真实变量值。<br>本插件提供的参数有`command`、`img_pxpx`、`img_url`、`ctx`、`session`、`config`<br>`img_pxpx`会被替换为`img#...px #...px`<br>`img_url`会被替换为`一个链接`,其中img_pxpx参数需要使用`canvas`服务<br>▶比如你可以使用`{{.session.userId}}`,这会被本插件替换为`真实的userId值`,若无匹配变量,则视为文本<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)"),
}).collapse().description('➢表情包--按钮设置<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">'),
nestedlist: Schema.object({
markdown_button_template_id: Schema.string().description("md模板ID<br>形如 `123456789_1234567890` 的ID编号,发送markdown").pattern(/^\d+_\d+$/),
markdown_button_keyboard_id: Schema.string().description("按钮模板ID<br>形如 `123456789_1234567890` 的ID编号,发送按钮").pattern(/^\d+_\d+$/),
markdown_button_content_table: Schema.array(Schema.object({
raw_parameters: Schema.string().description("原始参数名称"),
replace_parameters: Schema.string().description("替换参数名称"),
})).role('table').default([
{
"raw_parameters": "your_markdown_text_1",
"replace_parameters": "表情包列表~"
},
{
"raw_parameters": "your_markdown_text_2",
"replace_parameters": "点击下面的按钮触发哦!"
}
]).description("替换参数映射表<br>本插件会替换模板变量,请在左侧填入模板变量,右侧填入真实变量值。<br>本插件提供的参数有`command`、`img_pxpx`、`img_url`、`ctx`、`session`、`config`<br>`img_pxpx`会被替换为`img#...px #...px`<br>`img_url`会被替换为`一个链接`,其中img_pxpx参数需要使用`canvas`服务<br>▶比如你可以使用`{{.session.userId}}`,这会被本插件替换为`真实的userId值`,若无匹配变量,则视为文本<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)"),
}).collapse().description('➣表情包列表--按钮设置<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">'),
}),
Schema.object({
markdown_button_mode: Schema.const("markdown_raw_json").required(),
markdown_button_mode_initiative: Schema.boolean().description("开启后,使用 主动消息 发送markdown。<br>即开启后不带`messageId`发送<br>适用于私域机器人频道使用。私域机器人需要使用`被动md模板、json模板`并且开启此配置项").default(false),
markdown_button_mode_keyboard: Schema.boolean().description("开启后,markdown加上按钮。关闭后,不加按钮内容哦<br>不影响markdown发送,多用于调试功能使用").default(true).experimental(),
markdown_button_mode_without_emojilist_keyboard: Schema.boolean().description("开启后,表情包列表使用下方`nestedlist`配置的表情包列表按钮。关闭后,仅发送普通的文字列表").default(true).experimental(),
nested: Schema.object({
markdown_raw_json_button_template_id: Schema.string().description("markdown模板ID。**注意不是按钮模板ID**<br>形如 `123456789_1234567890` 的ID编号,发送markdown").pattern(/^\d+_\d+$/),
markdown_raw_json_button_content_table: Schema.array(Schema.object({
raw_parameters: Schema.string().description("原始参数名称"),
replace_parameters: Schema.string().description("替换参数名称"),
})).role('table').default([
{
"raw_parameters": "your_markdown_text_1",
"replace_parameters": "表情包来啦!"
},
{
"raw_parameters": "your_markdown_text_2",
"replace_parameters": "这是你的表情包哦😽"
},
{
"raw_parameters": "your_markdown_img",
"replace_parameters": "${img_pxpx}"
},
{
"raw_parameters": "your_markdown_url",
"replace_parameters": "${img_url}"
}
]).description("替换参数映射表<br>本插件会替换模板变量,请在左侧填入模板变量,右侧填入真实变量值。<br>本插件提供的参数有`command`、`img_pxpx`、`img_url`、`ctx`、`session`、`config`<br>`img_pxpx`会被替换为`img#...px #...px`<br>`img_url`会被替换为`一个链接`,其中img_pxpx参数需要使用`canvas`服务<br>▶比如你可以使用`{{.session.userId}}`,这会被本插件替换为`真实的userId值`,若无匹配变量,则视为文本<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)"),
markdown_raw_json_button_keyboard: Schema.string().role('textarea', { rows: [12, 12] }).collapse()
.default("{\n \"rows\": [\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"再来一张😺\",\n \"style\": 2\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/${command}\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"返回列表😽\",\n \"style\": 2\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/${config.emojihub_bili_command}\",\n \"enter\": true\n }\n }\n ]\n }\n ]\n}")
.description('实现QQ官方bot的按钮效果<br>在这里填入你的按钮内容,注意保持json格式,推荐在编辑器中编辑好后粘贴进来'),
}).collapse().description('➢表情包--按钮设置<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">'),
nestedlist: Schema.object({
markdown_raw_json_button_template_id: Schema.string().description("markdown模板ID。**注意不是按钮模板ID**<br>形如 `123456789_1234567890` 的ID编号,发送markdown").pattern(/^\d+_\d+$/),
markdown_raw_json_button_content_table: Schema.array(Schema.object({
raw_parameters: Schema.string().description("原始参数名称"),
replace_parameters: Schema.string().description("替换参数名称"),
})).role('table').default([
{
"raw_parameters": "your_markdown_text_1",
"replace_parameters": "表情包列表~"
},
{
"raw_parameters": "your_markdown_text_2",
"replace_parameters": "点击下面的按钮触发哦!"
}
]).description("替换参数映射表<br>本插件会替换模板变量,请在左侧填入模板变量,右侧填入真实变量值。<br>本插件提供的参数有`command`、`img_pxpx`、`img_url`、`ctx`、`session`、`config`<br>`img_pxpx`会被替换为`img#...px #...px`<br>`img_url`会被替换为`一个链接`,其中img_pxpx参数需要使用`canvas`服务<br>▶比如你可以使用`{{.session.userId}}`,这会被本插件替换为`真实的userId值`,若无匹配变量,则视为文本<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)"),
markdown_raw_json_button_keyboard: Schema.string().role('textarea', { rows: [12, 12] }).collapse()
.default("{\n \"rows\": [\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"随机emojihub表情包\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/随机emojihub表情包\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"acomu414\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/acomu414\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"ba表情包\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/ba表情包\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"downvote\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/downvote\",\n \"enter\": true\n }\n }\n ]\n },\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"doro\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/doro\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"eveonecat\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/eveonecat\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"fufu\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/fufu\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"mygo\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/mygo\",\n \"enter\": true\n }\n }\n ]\n },\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"seseren\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/seseren\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"白圣女\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/白圣女\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"白圣女漫画\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/白圣女漫画\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"柴郡\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/柴郡\",\n \"enter\": true\n }\n }\n ]\n },\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"初音Q版\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/初音Q版\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"甘城猫猫\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/甘城猫猫\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"疾旋鼬\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/疾旋鼬\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"流萤\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/流萤\",\n \"enter\": true\n }\n }\n ]\n },\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"赛马娘\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/赛马娘\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"瑟莉亚\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/瑟莉亚\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"藤田琴音\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/藤田琴音\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"亚托莉\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/亚托莉\",\n \"enter\": true\n }\n }\n ]\n }\n ]\n}")
.description('实现QQ官方bot的按钮效果<br>在这里填入你的按钮内容,注意保持json格式,推荐在编辑器中编辑好后粘贴进来'),
}).collapse().description('➣表情包列表--按钮设置<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">'),
}),
Schema.object({
markdown_button_mode: Schema.const("raw").required(),
markdown_button_mode_initiative: Schema.boolean().description("开启后,使用 主动消息 发送markdown。<br>即开启后不带`messageId`发送<br>适用于私域机器人频道使用。私域机器人需要使用`被动md模板、json模板`并且开启此配置项").default(false),
markdown_button_mode_keyboard: Schema.boolean().description("开启后,markdown加上按钮。关闭后,不加按钮内容哦<br>不影响markdown发送,多用于调试功能使用").default(true).experimental(),
markdown_button_mode_without_emojilist_keyboard: Schema.boolean().description("开启后,表情包列表使用下方`nestedlist`配置的表情包列表按钮。关闭后,仅发送普通的文字列表").default(true).experimental(),
nested: Schema.object({
raw_markdown_button_content: Schema.string().role('textarea', { rows: [6, 6] }).collapse().default("## **表情包~😺**\n### 😽来了哦!\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/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)'),
raw_markdown_button_keyboard: Schema.string().role('textarea', { rows: [12, 12] }).collapse()
.default("{\n \"rows\": [\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"再来一张😺\",\n \"style\": 2\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/${command}\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"返回列表😽\",\n \"style\": 2\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/${config.emojihub_bili_command}\",\n \"enter\": true\n }\n }\n ]\n }\n ]\n}")
.description('实现QQ官方bot的按钮效果<br>在这里填入你的按钮内容,注意保持json格式,推荐在编辑器中编辑好后粘贴进来'),
}).collapse().description('➢表情包--按钮设置<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">'),
nestedlist: Schema.object({
raw_markdown_button_content: 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/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)'),
raw_markdown_button_keyboard: Schema.string().role('textarea', { rows: [12, 12] }).collapse()
.default("{\n \"rows\": [\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"随机emojihub表情包\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/随机emojihub表情包\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"acomu414\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/acomu414\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"ba表情包\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/ba表情包\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"downvote\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/downvote\",\n \"enter\": true\n }\n }\n ]\n },\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"doro\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/doro\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"eveonecat\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/eveonecat\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"fufu\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/fufu\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"mygo\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/mygo\",\n \"enter\": true\n }\n }\n ]\n },\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"seseren\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/seseren\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"白圣女\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/白圣女\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"白圣女漫画\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/白圣女漫画\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"柴郡\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/柴郡\",\n \"enter\": true\n }\n }\n ]\n },\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"初音Q版\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/初音Q版\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"甘城猫猫\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/甘城猫猫\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"疾旋鼬\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/疾旋鼬\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"流萤\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/流萤\",\n \"enter\": true\n }\n }\n ]\n },\n {\n \"buttons\": [\n {\n \"render_data\": {\n \"label\": \"赛马娘\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/赛马娘\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"瑟莉亚\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/瑟莉亚\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"藤田琴音\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/藤田琴音\",\n \"enter\": true\n }\n },\n {\n \"render_data\": {\n \"label\": \"亚托莉\",\n \"style\": 1\n },\n \"action\": {\n \"type\": 2,\n \"permission\": {\n \"type\": 2\n },\n \"data\": \"/亚托莉\",\n \"enter\": true\n }\n }\n ]\n }\n ]\n}")
.description('实现QQ官方bot的按钮效果<br>在这里填入你的按钮内容,注意保持json格式,推荐在编辑器中编辑好后粘贴进来'),
}).collapse().description('➣表情包列表--按钮设置<br>更多说明,详见[➩项目README](https://github.com/shangxueink/koishi-shangxue-apps/tree/main/plugins/emojihub-bili)<hr style="border: 2px solid red;"><hr style="border: 2px solid red;">'),
}),
Schema.object({}),
]),
Schema.object({
LocalSendNetworkPicturesList: Schema.string().role('textarea', { rows: [2, 4] }).description('将`下列指令`对应的内容下载至本地,作为本地图片发送<br>请使用逗号分隔指令').default().experimental(),
deletePictime: Schema.number().default(10).description('若干`秒`后 删除下载的本地临时文件').experimental(),
localPicToBase64: Schema.boolean().description("`开启后`本地图片以base64发出 `日常使用无需开启,且不建议发送markdown的时候使用(直接发图还是可以考虑的)`").experimental().default(false),
QQPicToChannelUrl: Schema.boolean().description("`开启后`, `img_url`会先上传QQ频道,拿到频道URL,用于发送markdown<br>被动md需要URL白名单,仅对原生发本地文件夹的图有意义。").experimental().default(false),
QQchannelId: Schema.string().description('`填入QQ频道的频道ID`,将该ID的频道作为中转频道 <br> 频道ID可以用[inspect插件来查看](/market?keyword=inspect) `频道ID应为纯数字`').experimental().pattern(/^\S+$/),
}).description('调试选项'),
Schema.object({
consoleinfo: Schema.boolean().default(false).description("日志调试模式`日常使用无需开启`"),
}).description('日志调试选项'),
Schema.union([
Schema.object({
consoleinfo: Schema.const(true).required(),
allfileinfo: Schema.boolean().description("输出allfile调试内容`MoreEmojiHubList 列表详细内容`"),
}),
Schema.object({})
]),
])
/**
* 刷新机器人的令牌并上传图片到指定频道
* @param ctx
* @param data - 图片数据或者文件路径
* @param appId - 机器人AppID
* @param secret - 机器人Secret
* @param channelId - 频道ID
* @returns {Promise<{ url: string }>} - 上传图片后的URL
*/
async function uploadImageToChannel(ctx, consoleinfo, data, appId, secret, channelId) {
async function refreshToken(bot) {
const { access_token: accessToken, expires_in: expiresIn } = await ctx.http.post('https://bots.qq.com/app/getAppAccessToken', {
appId: bot.appId,
clientSecret: bot.secret
});
bot.token = accessToken;
ctx.setTimeout(() => refreshToken(bot), (expiresIn - 30) * 1000);
}
// 临时的bot对象
const bot = { appId, secret, channelId };
// 刷新令牌
await refreshToken(bot);
// 处理图片数据
if (typeof data === 'string') {
if (new URL(data).protocol === 'file:') {
data = await fs.promises.readFile(url.fileURLToPath(data));
} else {
data = await ctx.http.get(data, { responseType: 'arraybuffer' });
data = Buffer.from(data);
}
}
const payload = new FormData();
payload.append('msg_id', '0');
payload.append('file_image', new Blob([data], { type: 'image/png' }), 'image.jpg');
await ctx.http.post(`https://api.sgroup.qq.com/channels/${bot.channelId}/messages`, payload, {
headers: {
Authorization: `QQBot ${bot.token}`,
'X-Union-Appid': bot.appId
}
});
// 计算MD5并返回图片URL
const md5 = crypto.createHash('md5').update(data).digest('hex').toUpperCase();
if (channelId !== undefined && consoleinfo) {
logger.info(`使用本地图片*QQ频道 发送URL为: https://gchat.qpic.cn/qmeetpic/0/0-0-${md5}/0`)
};
return { url: `https://gchat.qpic.cn/qmeetpic/0/0-0-${md5}/0` };
}
async function getImageAsBase64(imagePath) {
try {
const imageBuffer = fs.readFileSync(imagePath);
// 将图片 buffer 转换为 Base64 字符串
const base64String = imageBuffer.toString('base64');
return base64String;
} catch (error) {
logger.error('Error converting image to base64:', error);
return null;
}
}
async function determineImagePath(txtPath, config, channelId, command, ctx, local_picture_name = null) {
// 判断是否是本地文件夹的绝对路径 (优先判断文件夹,解决linux路径识别问题)
if (isLocalDirectory(txtPath)) {
let filePath = txtPath;
if (txtPath.startsWith('file:///')) {
filePath = decodeURIComponent(txtPath.substring(8)); // 去除 file:/// 并解码 URL
}
return await getRandomImageFromFolder(filePath, config, channelId, command, ctx, local_picture_name);
}
// 判断是否是直接的图片链接
if (txtPath.startsWith('http://') || txtPath.startsWith('https://')) {
logInfoformat(config, channelId, command, `直接的图片链接: ${txtPath}`);
return { imageUrl: txtPath, isLocal: false };
}
// 判断是否是本地图片的绝对路径
if (isLocalImagePath(txtPath)) {
let filePath = txtPath;
if (txtPath.startsWith('file:///')) {
filePath = decodeURIComponent(txtPath.substring(8)); // 去除 file:/// 并解码 URL
}
if (!fs.existsSync(filePath)) {
logError(`错误:路径不存在: ${txtPath}`);
return { imageUrl: null, isLocal: false };
}
logInfoformat(config, channelId, command, `本地图片的绝对路径: ${t