koishi-plugin-cfmrmod 1.1.2 → 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.
@@ -0,0 +1,310 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Config = exports.name = void 0;
4
+ exports.apply = apply;
5
+ const { h, Schema } = require('koishi');
6
+ const cards_1 = require("./cards");
7
+ const constants_1 = require("./constants");
8
+ const http_1 = require("./http");
9
+ const rendering_1 = require("./rendering");
10
+ const search_1 = require("./search");
11
+ const utils_1 = require("./utils");
12
+ // ================= 状态管理和常量 =================
13
+ const searchStates = new Map();
14
+ // ================= Koishi =================
15
+ exports.name = 'mcmod-search';
16
+ exports.Config = Schema.object({
17
+ sendLink: Schema.boolean().default(true).description('发送卡片后是否附带链接'),
18
+ cookie: Schema.string().description('【可选】手动填写 mcmod.cn 的 Cookie'),
19
+ fontPath: Schema.string().role('path').description('可选:自定义字体文件路径'),
20
+ debug: Schema.boolean().default(false).description('输出渲染调试日志'),
21
+ render: Schema.object({
22
+ emoji: Schema.object({
23
+ twemoji: Schema.boolean().default(true).description('启用 Twemoji 图形兜底'),
24
+ cdn: Schema.string().default('https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/72x72').description('Twemoji CDN 前缀')
25
+ }).default({}),
26
+ image: Schema.object({
27
+ fetchWithHeaders: Schema.boolean().default(true).description('图片先用 HTTP(带 Referer/Cookie)抓取后解码')
28
+ }).default({})
29
+ }).default({})
30
+ });
31
+ function apply(ctx, config) {
32
+ var _a;
33
+ const logger = ctx.logger('mcmod');
34
+ if (!(0, rendering_1.configureRenderer)(config === null || config === void 0 ? void 0 : config.canvas, config, logger)) {
35
+ return;
36
+ }
37
+ // 初始化 Cookie
38
+ if (config.cookie) {
39
+ (0, http_1.setMcmodCookie)(config.cookie);
40
+ logger.info('使用手动配置的 Cookie');
41
+ }
42
+ else if (config.autoCookie) {
43
+ (0, http_1.loadManagedCookie)(logger);
44
+ }
45
+ // --- 状态管理 (严格隔离) ---
46
+ function clearState(cid) {
47
+ const state = searchStates.get(cid);
48
+ if (state && state.timer)
49
+ clearTimeout(state.timer);
50
+ searchStates.delete(cid);
51
+ }
52
+ // --- 排队系统 ---
53
+ const queue = [];
54
+ let isProcessing = false;
55
+ async function processQueue() {
56
+ if (isProcessing || queue.length === 0)
57
+ return;
58
+ isProcessing = true;
59
+ const { session, task } = queue.shift();
60
+ try {
61
+ await task();
62
+ }
63
+ catch (e) {
64
+ logger.error('任务执行出错:', e);
65
+ await session.send(`执行出错: ${e.message}`);
66
+ }
67
+ finally {
68
+ isProcessing = false;
69
+ // 稍微延迟一下,给系统喘息时间
70
+ setTimeout(processQueue, 500);
71
+ }
72
+ }
73
+ // 入队函数
74
+ function enqueue(session, taskName, taskFunc) {
75
+ return new Promise((resolve, reject) => {
76
+ queue.push({
77
+ session,
78
+ task: async () => {
79
+ try {
80
+ // 如果队列较长,提示用户
81
+ if (queue.length > 1) {
82
+ // 可选:发送排队提示
83
+ // await session.send(`正在处理您的请求... (排队中)`);
84
+ }
85
+ await taskFunc();
86
+ resolve();
87
+ }
88
+ catch (e) {
89
+ reject(e);
90
+ }
91
+ }
92
+ });
93
+ processQueue();
94
+ });
95
+ }
96
+ // 辅助:尝试撤回消息
97
+ async function tryWithdraw(session, messageIds) {
98
+ if (!messageIds || !messageIds.length)
99
+ return;
100
+ try {
101
+ for (const id of messageIds) {
102
+ await session.bot.deleteMessage(session.channelId, id);
103
+ }
104
+ }
105
+ catch (e) { }
106
+ }
107
+ // --- 注册指令 ---
108
+ const prefix = ((_a = config === null || config === void 0 ? void 0 : config.prefixes) === null || _a === void 0 ? void 0 : _a.cnmc) || 'cnmc';
109
+ const commandTypes = ['mod', 'data', 'pack', 'tutorial', 'author', 'user'];
110
+ ctx.command(`${prefix}.help`).action(() => [
111
+ `${prefix} <关键词> | 默认搜索 Mod`,
112
+ `${prefix}.mod/.data/.pack/.tutorial/.author/.user <关键词>`,
113
+ '列表交互:输入序号查看,n 下一页,p 上一页,q 退出',
114
+ ].join('\n'));
115
+ commandTypes.forEach(type => {
116
+ ctx.command(`${prefix}.${type} <keyword:text>`)
117
+ .action(async ({ session }, keyword) => {
118
+ if (!keyword)
119
+ return '请输入关键词。';
120
+ // 将搜索任务加入队列
121
+ enqueue(session, `search-${type}`, async () => {
122
+ var _a;
123
+ try {
124
+ if (config.debug)
125
+ logger.debug(`[${session.userId}] 正在搜索 ${keyword} ...`);
126
+ let results = await (0, search_1.fetchSearch)(keyword, type);
127
+ if (!results.length) {
128
+ await session.send('未找到相关结果。(备用也没用,我劝你换个关键词试试)');
129
+ return;
130
+ }
131
+ // 单结果直接处理
132
+ if (results.length === 1) {
133
+ const item = results[0];
134
+ await (0, http_1.ensureValidCookie)();
135
+ let img;
136
+ if (type === 'author')
137
+ img = await (0, cards_1.drawAuthorCard)(item.link);
138
+ else if (type === 'user') {
139
+ const uid = ((_a = item.link.match(/\/(\d+)(?:\.html|\/)?$/)) === null || _a === void 0 ? void 0 : _a[1]) || '0';
140
+ img = await (0, cards_1.drawCenterCardImpl)(uid, logger);
141
+ }
142
+ else if (type === 'mod' || type === 'pack')
143
+ img = await (0, cards_1.drawModCard)(item.link);
144
+ else if (type === 'tutorial')
145
+ img = await (0, cards_1.drawTutorialCard)(item.link);
146
+ else
147
+ img = await (0, cards_1.createInfoCard)(item.link, type);
148
+ await session.send(h.image(await (0, utils_1.toImageSrc)(img)));
149
+ if (config.sendLink)
150
+ await session.send(`链接: ${item.link}`);
151
+ return;
152
+ }
153
+ // 多结果:初始化状态(隔离在 session.cid)
154
+ clearState(session.cid);
155
+ const listText = (0, search_1.formatListPage)(results, 0, type);
156
+ const sentMessageIds = await session.send(listText);
157
+ searchStates.set(session.cid, {
158
+ type,
159
+ results,
160
+ pageIndex: 0,
161
+ messageIds: sentMessageIds,
162
+ timer: setTimeout(() => searchStates.delete(session.cid), constants_1.TIMEOUT_MS)
163
+ });
164
+ }
165
+ catch (e) {
166
+ logger.error(e);
167
+ await session.send(`处理失败: ${e.message}`);
168
+ }
169
+ });
170
+ });
171
+ });
172
+ ctx.command(`${prefix} <keyword:text>`)
173
+ .action(async ({ session }, keyword) => {
174
+ if (!keyword)
175
+ return '请输入关键词。';
176
+ enqueue(session, 'search-mod', async () => {
177
+ try {
178
+ if (config.debug)
179
+ logger.debug(`[${session.userId}] 正在搜索 ${keyword} ...`);
180
+ const results = await (0, search_1.fetchSearch)(keyword, 'mod');
181
+ if (!results.length) {
182
+ await session.send('未找到相关结果。(备用也没用,我劝你换个关键词试试)');
183
+ return;
184
+ }
185
+ if (results.length === 1) {
186
+ const item = results[0];
187
+ await (0, http_1.ensureValidCookie)();
188
+ const img = await (0, cards_1.drawModCard)(item.link);
189
+ await session.send(h.image(await (0, utils_1.toImageSrc)(img)));
190
+ if (config.sendLink)
191
+ await session.send(`链接: ${item.link}`);
192
+ return;
193
+ }
194
+ clearState(session.cid);
195
+ const listText = (0, search_1.formatListPage)(results, 0, 'mod');
196
+ const sentMessageIds = await session.send(listText);
197
+ searchStates.set(session.cid, {
198
+ results,
199
+ pageIndex: 0,
200
+ type: 'mod',
201
+ messageIds: Array.isArray(sentMessageIds) ? sentMessageIds : [sentMessageIds],
202
+ timer: setTimeout(() => {
203
+ tryWithdraw(session, Array.isArray(sentMessageIds) ? sentMessageIds : [sentMessageIds]);
204
+ clearState(session.cid);
205
+ }, config.timeouts || 60000),
206
+ });
207
+ }
208
+ catch (e) {
209
+ logger.error('执行出错:', e);
210
+ await session.send(`执行出错: ${e.message}`);
211
+ }
212
+ });
213
+ });
214
+ // --- 中间件 (处理序号选择) ---
215
+ ctx.middleware(async (session, next) => {
216
+ // 1. 专一性检查:只处理当前有搜索状态的用户
217
+ const state = searchStates.get(session.cid);
218
+ if (!state)
219
+ return next();
220
+ const input = session.content.trim().toLowerCase();
221
+ // 退出
222
+ if (input === 'q' || input === '退出') {
223
+ clearState(session.cid);
224
+ await tryWithdraw(session, state.messageIds); // 退出时也可以顺手撤回列表
225
+ await session.send('已退出搜索。');
226
+ return;
227
+ }
228
+ // 翻页
229
+ if (input === 'p' || input === 'n') {
230
+ // 加入队列处理翻页,防止并发
231
+ enqueue(session, 'page-turn', async () => {
232
+ var _a, _b;
233
+ // 重新获取状态,防止排队期间状态丢失
234
+ const currentState = searchStates.get(session.cid);
235
+ if (!currentState)
236
+ return;
237
+ clearTimeout(currentState.timer);
238
+ currentState.timer = setTimeout(() => searchStates.delete(session.cid), constants_1.TIMEOUT_MS);
239
+ const total = Math.ceil(currentState.results.length / constants_1.PAGE_SIZE);
240
+ const currentPage = Number((_b = (_a = currentState.pageIndex) !== null && _a !== void 0 ? _a : currentState.page) !== null && _b !== void 0 ? _b : 0) || 0;
241
+ let newIndex = currentPage;
242
+ if (input === 'n' && currentPage < total - 1)
243
+ newIndex++;
244
+ else if (input === 'p' && currentPage > 0)
245
+ newIndex--;
246
+ else {
247
+ await session.send('没有更多页面了。');
248
+ return;
249
+ }
250
+ // 撤回旧列表(可选,为了整洁)
251
+ await tryWithdraw(session, currentState.messageIds);
252
+ currentState.pageIndex = newIndex;
253
+ const newMsgIds = await session.send((0, search_1.formatListPage)(currentState.results, newIndex, currentState.type));
254
+ currentState.messageIds = Array.isArray(newMsgIds) ? newMsgIds : [newMsgIds];
255
+ });
256
+ return;
257
+ }
258
+ // 选择序号
259
+ const choice = parseInt(input);
260
+ if (!isNaN(choice) && choice >= 1) {
261
+ // 加入队列处理生成卡片
262
+ enqueue(session, 'select-item', async () => {
263
+ var _a, _b, _c;
264
+ const currentState = searchStates.get(session.cid);
265
+ if (!currentState)
266
+ return; // 状态可能已过期
267
+ const idx = choice - 1;
268
+ const currentPage = Number((_b = (_a = currentState.pageIndex) !== null && _a !== void 0 ? _a : currentState.page) !== null && _b !== void 0 ? _b : 0) || 0;
269
+ const pageStart = currentPage * constants_1.PAGE_SIZE;
270
+ const pageEnd = Math.min(pageStart + constants_1.PAGE_SIZE, currentState.results.length);
271
+ if (choice < pageStart + 1 || choice > pageEnd) {
272
+ // 如果序号不在当前页,忽略或提示
273
+ // await session.send(`请输入当前页显示的序号 (${pageStart + 1}-${pageEnd})。`);
274
+ return;
275
+ }
276
+ if (idx >= 0 && idx < currentState.results.length) {
277
+ const item = currentState.results[idx];
278
+ // 撤回列表消息
279
+ await tryWithdraw(session, currentState.messageIds);
280
+ clearState(session.cid); // 完成交互,清除状态
281
+ try {
282
+ await (0, http_1.ensureValidCookie)();
283
+ let img;
284
+ if (currentState.type === 'author')
285
+ img = await (0, cards_1.drawAuthorCard)(item.link);
286
+ else if (currentState.type === 'user') {
287
+ const uid = ((_c = item.link.match(/\/(\d+)(?:\.html|\/)?$/)) === null || _c === void 0 ? void 0 : _c[1]) || '0';
288
+ img = await (0, cards_1.drawCenterCardImpl)(uid, logger);
289
+ }
290
+ else if (currentState.type === 'mod' || currentState.type === 'pack')
291
+ img = await (0, cards_1.drawModCard)(item.link);
292
+ else if (currentState.type === 'tutorial')
293
+ img = await (0, cards_1.drawTutorialCard)(item.link);
294
+ else
295
+ img = await (0, cards_1.createInfoCard)(item.link, currentState.type);
296
+ await session.send(h.image(await (0, utils_1.toImageSrc)(img)));
297
+ if (config.sendLink)
298
+ await session.send(`链接: ${item.link}`);
299
+ }
300
+ catch (e) {
301
+ logger.error(e);
302
+ await session.send(`生成失败: ${e.message}`);
303
+ }
304
+ }
305
+ });
306
+ return;
307
+ }
308
+ return next();
309
+ });
310
+ }