koishi-plugin-vsinger-gacha 1.1.1 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -25
- package/lib/index.js +41 -20
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -19,14 +19,12 @@ VSinger 收藏集抽卡插件(Koishi 4.x):六位角色的官方收藏集
|
|
|
19
19
|
- **图鉴长图**:液态玻璃背景 + 官方「大隐藏/小隐藏」角标 + 镭射标,超大图自动分张防 OOM
|
|
20
20
|
- **管理指令**:给卡(管理员可凭空发,普通用户须自己拥有)、设/加积分、查积分
|
|
21
21
|
|
|
22
|
-
##
|
|
22
|
+
## 安装(选择一种)
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
### 方式 A:一条命令装(不用 npm 帐号)
|
|
24
|
+
### 方式 A:npm 一行装(推荐)
|
|
27
25
|
|
|
28
26
|
```bash
|
|
29
|
-
npm i
|
|
27
|
+
npm i koishi-plugin-vsinger-gacha
|
|
30
28
|
```
|
|
31
29
|
|
|
32
30
|
`koishi.yml` 加一行后重启:
|
|
@@ -36,16 +34,16 @@ plugins:
|
|
|
36
34
|
vsinger-gacha: {}
|
|
37
35
|
```
|
|
38
36
|
|
|
39
|
-
### 方式 B:下载
|
|
40
|
-
|
|
41
|
-
下载 [koishi-plugin-vsinger-gacha.zip](https://github.com/zb23333ww/koishi-plugin-vsinger-gacha/releases/download/v1.0.0/koishi-plugin-vsinger-gacha.zip) → 解压 → 整个文件夹放进 Koishi 项目的 `node_modules/` → `koishi.yml` 加 `vsinger-gacha: {}` → 重启。
|
|
42
|
-
|
|
43
|
-
### 方式 C:npm 正式包(发布后可用)
|
|
37
|
+
### 方式 B:下载 tgz 直装(npm 不可用时)
|
|
44
38
|
|
|
45
39
|
```bash
|
|
46
|
-
npm i koishi-plugin-vsinger-gacha
|
|
40
|
+
npm i https://github.com/zb23333ww/koishi-plugin-vsinger-gacha/releases/latest/download/koishi-plugin-vsinger-gacha.tgz
|
|
47
41
|
```
|
|
48
42
|
|
|
43
|
+
### 方式 C:下载 zip 手动放
|
|
44
|
+
|
|
45
|
+
[koishi-plugin-vsinger-gacha.zip](https://github.com/zb23333ww/koishi-plugin-vsinger-gacha/releases/latest/download/koishi-plugin-vsinger-gacha.zip) → 解压 → 整个文件夹放进 Koishi 的 `node_modules/` → 配置 → 重启。
|
|
46
|
+
|
|
49
47
|
### 图鉴(可选)
|
|
50
48
|
|
|
51
49
|
`/抽卡`、`/签到`、积分、管理指令**零依赖**即可用。
|
|
@@ -63,6 +61,19 @@ npm i @napi-rs/canvas
|
|
|
63
61
|
- 想一次性缓存全部:`npm run fetch-assets -- --dir data/vsinger-gacha/cards`
|
|
64
62
|
- 要动态卡 mp4(1.1GB):下载 Release 的 `assets-videos.tar` 解压到 `cards/`
|
|
65
63
|
|
|
64
|
+
## 兼容性
|
|
65
|
+
|
|
66
|
+
| 项 | 说明 |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| **Koishi** | `>=4.17.0 <6`(4.18 与 5.x RC 均验证过) |
|
|
69
|
+
| **Node** | `>=18` |
|
|
70
|
+
| **适配器** | 任意 Satori 适配器可用;OneBot 额外支持**合并转发**,其它适配器自动退回普通消息 |
|
|
71
|
+
| **发图** | 默认 `buffer`(base64,跨容器最稳);可切 `file`(适配器能读同机文件时更省内存) |
|
|
72
|
+
| **数据库** | 不需要,数据存 `data/vsinger-gacha/*.json` |
|
|
73
|
+
| **图鉴** | 需要可选依赖 `@napi-rs/canvas`;没装时其它功能不受影响 |
|
|
74
|
+
| **字体** | 包内自带中文子集,不依赖系统字体 |
|
|
75
|
+
| **合并转发** | 配置 `forwardMode: auto / off` |
|
|
76
|
+
|
|
66
77
|
## 指令
|
|
67
78
|
|
|
68
79
|
| 指令 | 说明 |
|
|
@@ -119,25 +130,20 @@ npm i @napi-rs/canvas
|
|
|
119
130
|
MIT
|
|
120
131
|
|
|
121
132
|
|
|
122
|
-
##
|
|
133
|
+
## 素材库(卡图 + 动态卡)
|
|
123
134
|
|
|
124
|
-
|
|
135
|
+
插件包只带**缩略图**(图鉴用)+ 卡池数据,原图和动态卡单独放在 Releases 的「素材库」:
|
|
125
136
|
|
|
126
|
-
|
|
|
137
|
+
| 附件 | 大小 | 内容 |
|
|
127
138
|
| --- | --- | --- |
|
|
128
|
-
| `assets-cards.tar` | 1.6 GB | 423
|
|
129
|
-
| `assets-videos.tar` | 1.1 GB | 145 个动态卡 mp4 |
|
|
130
|
-
|
|
131
|
-
解压到 `cardsDir` 即可,或用 `npm run fetch-assets` 重新抓取。
|
|
139
|
+
| [`assets-cards.tar`](https://github.com/zb23333ww/koishi-plugin-vsinger-gacha/releases/download/v1.0.0/assets-cards.tar) | 1.6 GB | 423 张官方原图(1242×1863)+ 274 张镭射版 |
|
|
140
|
+
| [`assets-videos.tar`](https://github.com/zb23333ww/koishi-plugin-vsinger-gacha/releases/download/v1.0.0/assets-videos.tar) | 1.1 GB | 145 个动态卡 mp4 |
|
|
132
141
|
|
|
133
|
-
|
|
142
|
+
解压到 `cardsDir`(默认 `data/vsinger-gacha/cards`):
|
|
134
143
|
|
|
135
144
|
```bash
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
tar xf assets-cards.tar -C data/vsinger-gacha/cards
|
|
146
|
+
tar xf assets-videos.tar -C data/vsinger-gacha/cards
|
|
138
147
|
```
|
|
139
148
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
卡图、卡池信息来自哔哩哔哩「收藏集」公开接口,版权归哔哩哔哩及原作者所有;
|
|
143
|
-
本插件仅用于个人学习与同好群内的收藏展示,请勿用于商业用途。
|
|
149
|
+
**不装也能用**:卡图按需单张自动下载(约 1MB/张),图鉴直接用包内缩略图。
|
package/lib/index.js
CHANGED
|
@@ -31,6 +31,10 @@ const Config = Schema.intersect([
|
|
|
31
31
|
.description('可选:openid ↔ QQ 映射表(如 /root/qqbot/data/id-map.json),用于统一用户标识'),
|
|
32
32
|
debug: Schema.boolean().default(false)
|
|
33
33
|
.description('调试模式:把收到的每条消息打到日志里'),
|
|
34
|
+
forwardMode: Schema.union(['auto', 'off']).default('auto')
|
|
35
|
+
.description('合并转发:auto=支持就转发、不支持自动退回普通消息;off=永远普通消息'),
|
|
36
|
+
imageMode: Schema.union(['auto', 'buffer', 'file']).default('auto')
|
|
37
|
+
.description('发图方式:auto=buffer(base64,跨容器最稳);file=发本地路径(需适配器能读同机文件)'),
|
|
34
38
|
sendVideos: Schema.boolean().default(true)
|
|
35
39
|
.description('动态卡单独发送 mp4(需要本地 videos 目录有对应文件)'),
|
|
36
40
|
autoDownload: Schema.boolean().default(true)
|
|
@@ -114,16 +118,32 @@ const normUser = (raw, session) => {
|
|
|
114
118
|
* ★ 不用 file:// 路径:机器人框架 / OneBot 实现在容器里读不到宿主机路径
|
|
115
119
|
* → 直接用 buffer(Koishi 会编码成 data/base64,跨进程可用)
|
|
116
120
|
*/
|
|
117
|
-
|
|
121
|
+
/** 机器人自身 uin / 显示名(不同适配器字段不一样) */
|
|
122
|
+
function botUin(session) {
|
|
123
|
+
return String(session.selfId || (session.bot && session.bot.selfId) || (session.bot && session.bot.config && session.bot.config.selfId) || '');
|
|
124
|
+
}
|
|
125
|
+
function botName(session) {
|
|
126
|
+
return String((session.bot && (session.bot.username || session.bot.name)) || 'Bot');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function fileEl(file, mime, mode) {
|
|
118
130
|
const mt = mime || (/\.jpe?g$/i.test(file) ? 'image/jpeg' : 'image/png');
|
|
131
|
+
if (mode === 'file') {
|
|
132
|
+
try {
|
|
133
|
+
return h.image(require('node:url').pathToFileURL(file).href);
|
|
134
|
+
} catch { /* 退回 buffer */ }
|
|
135
|
+
}
|
|
119
136
|
return h.image(fs.readFileSync(file), mt);
|
|
120
|
-
}
|
|
137
|
+
}
|
|
121
138
|
|
|
122
139
|
|
|
123
140
|
/** 发合并转发(OneBot 私有 API);失败返回 false */
|
|
124
141
|
async function sendForward(session, nodes) {
|
|
125
142
|
const bot = session.bot;
|
|
126
|
-
if (!bot || !bot.internal)
|
|
143
|
+
if (!bot || !bot.internal) {
|
|
144
|
+
sendForward.lastError = 'adapter 不支持 internal API';
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
127
147
|
const gid = session.guildId || session.channelId;
|
|
128
148
|
if (session.isDirect || !gid) return false;
|
|
129
149
|
const internal = bot.internal;
|
|
@@ -151,9 +171,10 @@ async function sendForward(session, nodes) {
|
|
|
151
171
|
/** 本地视频 → 消息元素(OneBot 要求视频单独一条消息) */
|
|
152
172
|
function videoEl(file) {
|
|
153
173
|
const buf = fs.readFileSync(file);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
174
|
+
if (typeof h.video === 'function') {
|
|
175
|
+
try { return h.video(buf, 'video/mp4'); } catch { /* 退回通用元素 */ }
|
|
176
|
+
}
|
|
177
|
+
return h('video', { src: 'data:video/mp4;base64,' + buf.toString('base64') });
|
|
157
178
|
}
|
|
158
179
|
|
|
159
180
|
/** 本地图片 → OneBot base64 段 */
|
|
@@ -294,17 +315,17 @@ function apply(ctx, config) {
|
|
|
294
315
|
if (session.isDirect === false || session.guildId) {
|
|
295
316
|
nodes.push({
|
|
296
317
|
type: 'node',
|
|
297
|
-
data: { name:
|
|
318
|
+
data: { name: botName(session), uin: botUin(session), content: [{ type: 'text', data: { text: title } }] },
|
|
298
319
|
});
|
|
299
320
|
results.forEach((r, i) => {
|
|
300
321
|
const rate = r.card.rate_pct == null ? '' : ` ${r.card.rate_pct}%`;
|
|
301
322
|
const desc = `${r.tier}|${r.card.card_name}${rate}${r.laser ? '|✦镭射版' : ''}${r.card.video ? '|动态' : ''}`;
|
|
302
323
|
const content = [{ type: 'text', data: { text: desc } }];
|
|
303
324
|
if (pics[i]) content.push(imgSeg(pics[i]));
|
|
304
|
-
nodes.push({ type: 'node', data: { name:
|
|
325
|
+
nodes.push({ type: 'node', data: { name: botName(session), uin: botUin(session), content } });
|
|
305
326
|
});
|
|
306
327
|
}
|
|
307
|
-
const ok = nodes.length && await sendForward(session, nodes);
|
|
328
|
+
const ok = config.forwardMode !== 'off' && nodes.length && await sendForward(session, nodes);
|
|
308
329
|
if (!ok && nodes.length) logger.warn('合并转发不可用(%s),退回普通发送', sendForward.lastError || 'unknown');
|
|
309
330
|
|
|
310
331
|
// ★ 动态卡:单独发 mp4(OneBot 要求视频独占一条消息)
|
|
@@ -327,7 +348,7 @@ function apply(ctx, config) {
|
|
|
327
348
|
await session.send(title + '\n' + lines.join('\n'));
|
|
328
349
|
for (const f of pics) {
|
|
329
350
|
if (!f) continue;
|
|
330
|
-
try { await session.send(fileEl(f)); } catch (e) { logger.warn('发卡图失败:%s', e.message); }
|
|
351
|
+
try { await session.send(fileEl(f, null, config.imageMode)); } catch (e) { logger.warn('发卡图失败:%s', e.message); }
|
|
331
352
|
}
|
|
332
353
|
}
|
|
333
354
|
return;
|
|
@@ -359,18 +380,18 @@ function apply(ctx, config) {
|
|
|
359
380
|
}
|
|
360
381
|
lines.push('', '切换:/换池 序号(00 = 全部卡池)', '说明:/抽卡帮助');
|
|
361
382
|
|
|
362
|
-
const uin =
|
|
383
|
+
const uin = botUin(session);
|
|
363
384
|
const nodes = [];
|
|
364
385
|
const chunk = 18;
|
|
365
386
|
for (let k = 0; k < lines.length; k += chunk) {
|
|
366
|
-
nodes.push({ type: 'node', data: { name:
|
|
387
|
+
nodes.push({ type: 'node', data: { name: botName(session), uin, content: [{ type: 'text', data: { text: lines.slice(k, k + chunk).join('\n') } }] } });
|
|
367
388
|
}
|
|
368
389
|
if (nodes.length < 2) {
|
|
369
|
-
nodes.push({ type: 'node', data: { name:
|
|
390
|
+
nodes.push({ type: 'node', data: { name: botName(session), uin, content: [{ type: 'text', data: { text: '切换:/换池 序号(00 = 全部卡池)' } }] } });
|
|
370
391
|
}
|
|
371
|
-
const ok = await sendForward(session, nodes);
|
|
392
|
+
const ok = config.forwardMode !== 'off' && await sendForward(session, nodes);
|
|
372
393
|
if (!ok) {
|
|
373
|
-
logger.warn('卡池合并转发失败(%s),改发文本', sendForward.lastError);
|
|
394
|
+
if (config.forwardMode !== 'off') logger.warn('卡池合并转发失败(%s),改发文本', sendForward.lastError);
|
|
374
395
|
return lines.join('\n');
|
|
375
396
|
}
|
|
376
397
|
return;
|
|
@@ -414,7 +435,7 @@ function apply(ctx, config) {
|
|
|
414
435
|
const f = game.holoFile(c)
|
|
415
436
|
|| (config.autoDownload !== false ? await game.ensureImage(c) : (fs.existsSync(game.cardFile(c)) ? game.cardFile(c) : null));
|
|
416
437
|
if (!f) return `「${c.v.card_name}」的卡图下载失败(网络问题),稍后再试`;
|
|
417
|
-
await session.send(fileEl(f, /\.jpe?g$/i.test(f) ? 'image/jpeg' : 'image/png'));
|
|
438
|
+
await session.send(fileEl(f, /\.jpe?g$/i.test(f) ? 'image/jpeg' : 'image/png', config.imageMode));
|
|
418
439
|
return `🎴 ${c.v.card_name}\n档位:${tierName(c.v)} | 概率:${c.v.rate_pct}% | 你拥有 ×${u.owned[c.h]}${c.v.video ? ' | 动态卡' : ''}`;
|
|
419
440
|
});
|
|
420
441
|
|
|
@@ -431,10 +452,10 @@ function apply(ctx, config) {
|
|
|
431
452
|
tagsDir,
|
|
432
453
|
thumbsDir: path.join(assetsDir, 'thumbs'),
|
|
433
454
|
});
|
|
434
|
-
const nodes = [{ type: 'node', data: { name:
|
|
435
|
-
for (const f of files) nodes.push({ type: 'node', data: { name:
|
|
436
|
-
const ok = await sendForward(session, nodes);
|
|
437
|
-
if (!ok) for (const f of files) await session.send(fileEl(f, 'image/jpeg'));
|
|
455
|
+
const nodes = [{ type: 'node', data: { name: botName(session), uin: botUin(session), content: [{ type: 'text', data: { text: `${session.username || '你'} 的收藏集图鉴(${files.length} 张)` } }] } }];
|
|
456
|
+
for (const f of files) nodes.push({ type: 'node', data: { name: botName(session), uin: botUin(session), content: [imgSeg(f)] } });
|
|
457
|
+
const ok = config.forwardMode !== 'off' && await sendForward(session, nodes);
|
|
458
|
+
if (!ok) for (const f of files) await session.send(fileEl(f, 'image/jpeg', config.imageMode));
|
|
438
459
|
} catch (e) {
|
|
439
460
|
logger.warn('图鉴失败:%s', e.stack || e.message);
|
|
440
461
|
return `图鉴生成失败:${String(e.message).slice(0, 120)}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-vsinger-gacha",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "VSinger 收藏集抽卡 / 签到积分 / 图鉴(Koishi 插件,一次性安装)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"files": [
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"peerDependencies": {
|
|
25
|
-
"koishi": "
|
|
25
|
+
"koishi": ">=4.17.0 <6"
|
|
26
26
|
},
|
|
27
27
|
"koishi": {
|
|
28
28
|
"description": {
|
|
@@ -47,4 +47,4 @@
|
|
|
47
47
|
"optionalDependencies": {
|
|
48
48
|
"@napi-rs/canvas": "^0.1.52"
|
|
49
49
|
}
|
|
50
|
-
}
|
|
50
|
+
}
|