koishi-plugin-dew-bot 1.0.258 → 1.0.259
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/lib/index.js +39 -18
- package/lib/plugin/server.d.ts +2 -1
- package/package.json +1 -1
- package/src/plugin/server.ts +16 -1
- package/src/plugin/serverHandel/qqPageMessage.ts +4 -2
- package/src/plugin/skillCard.ts +23 -14
package/lib/index.js
CHANGED
|
@@ -1422,6 +1422,7 @@ async function renderUncached(ctx, card, leaderboard, itemCard, characterCard, e
|
|
|
1422
1422
|
let timeout;
|
|
1423
1423
|
let workPromise;
|
|
1424
1424
|
let expired = false;
|
|
1425
|
+
let finished = false;
|
|
1425
1426
|
const release = /* @__PURE__ */ __name(() => {
|
|
1426
1427
|
const next = waiting.shift();
|
|
1427
1428
|
if (next) next();
|
|
@@ -1429,12 +1430,26 @@ async function renderUncached(ctx, card, leaderboard, itemCard, characterCard, e
|
|
|
1429
1430
|
}, "release");
|
|
1430
1431
|
try {
|
|
1431
1432
|
const work = /* @__PURE__ */ __name(async () => {
|
|
1432
|
-
const entryArtworkPromise = loadCardEntryArtwork(!titleCard && !bloodlineCard && !equipmentCard ? companionCard : void 0);
|
|
1433
1433
|
const remoteAddress = titleCard ? titleCard.artworkUrl : bloodlineCard ? bloodlineCard.artworkUrl : equipmentCard ? equipmentCard.artworkUrl : companionCard ? companionCard.artworkUrl : characterCard ? characterCard.artworkUrl : itemCard ? itemCard.artworkUrl : leaderboard ? leaderboard.backgroundUrl : card?.artworkUrl;
|
|
1434
|
-
const
|
|
1435
|
-
const
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1434
|
+
const isEquipment = !titleCard && !bloodlineCard && Boolean(equipmentCard);
|
|
1435
|
+
const isCharacter = !titleCard && !bloodlineCard && !equipmentCard && !companionCard && Boolean(characterCard);
|
|
1436
|
+
const [remoteArt, fallbackArt, entryArtwork, characterAssets, header] = await Promise.all([
|
|
1437
|
+
loadSkillArtwork(remoteAddress),
|
|
1438
|
+
isEquipment ? equipmentFallback() : void 0,
|
|
1439
|
+
loadCardEntryArtwork(!titleCard && !bloodlineCard && !equipmentCard ? companionCard : void 0),
|
|
1440
|
+
isCharacter ? Promise.all([characterAsset(), loadSkillArtwork(characterCard.titleImageUrl)]) : void 0,
|
|
1441
|
+
!titleCard && !bloodlineCard && !equipmentCard && !companionCard && !characterCard && !itemCard && leaderboard ? leaderboardHeader() : void 0,
|
|
1442
|
+
ctx.puppeteer.page().then(async (created) => {
|
|
1443
|
+
if (expired || finished) {
|
|
1444
|
+
await created.close();
|
|
1445
|
+
throw new Error(expired ? "绘图超时" : "绘图已结束");
|
|
1446
|
+
}
|
|
1447
|
+
page = created;
|
|
1448
|
+
})
|
|
1449
|
+
]);
|
|
1450
|
+
const dedicatedArt = isEquipment ? remoteArt : void 0;
|
|
1451
|
+
const unknownArt = isEquipment && !dedicatedArt ? fallbackArt : void 0;
|
|
1452
|
+
const art = isEquipment ? dedicatedArt ?? unknownArt : remoteArt;
|
|
1438
1453
|
let html;
|
|
1439
1454
|
let loadedCharacterTitle;
|
|
1440
1455
|
let localAssetsReady = true;
|
|
@@ -1448,27 +1463,19 @@ async function renderUncached(ctx, card, leaderboard, itemCard, characterCard, e
|
|
|
1448
1463
|
} else if (companionCard) {
|
|
1449
1464
|
html = companionCardHtml(companionCard, art, entryArtwork.icons);
|
|
1450
1465
|
} else if (characterCard) {
|
|
1451
|
-
const [defaultArtwork, titleArtwork] =
|
|
1452
|
-
characterAsset(),
|
|
1453
|
-
loadSkillArtwork(characterCard.titleImageUrl)
|
|
1454
|
-
]);
|
|
1466
|
+
const [defaultArtwork, titleArtwork] = characterAssets;
|
|
1455
1467
|
loadedCharacterTitle = titleArtwork;
|
|
1456
1468
|
localAssetsReady = Boolean(art || defaultArtwork) && (!characterCard.titleImageUrl || Boolean(titleArtwork));
|
|
1457
1469
|
html = characterCardHtml(characterCard, art, void 0, defaultArtwork, titleArtwork, entryArtwork.icons);
|
|
1458
1470
|
} else if (itemCard) {
|
|
1459
1471
|
html = itemCard.type === "形象卡" ? appearanceCardHtml(itemCard, art) : itemCardHtml(itemCard, art);
|
|
1460
1472
|
} else if (leaderboard) {
|
|
1461
|
-
html = leaderboardHtml(leaderboard,
|
|
1473
|
+
html = leaderboardHtml(leaderboard, header, art);
|
|
1462
1474
|
} else {
|
|
1463
1475
|
html = skillCardHtml(card, "", art);
|
|
1464
1476
|
}
|
|
1465
1477
|
if (expired) throw new Error("绘图超时");
|
|
1466
|
-
page
|
|
1467
|
-
if (expired) {
|
|
1468
|
-
await page.close();
|
|
1469
|
-
page = void 0;
|
|
1470
|
-
throw new Error("绘图超时");
|
|
1471
|
-
}
|
|
1478
|
+
if (!page) throw new Error("卡片页面缺失");
|
|
1472
1479
|
await page.setViewport({ width: 760, height: 1107, deviceScaleFactor: titleCard || (companionCard || characterCard) && !bloodlineCard && !equipmentCard ? 2 : 1 });
|
|
1473
1480
|
await page.setJavaScriptEnabled(false);
|
|
1474
1481
|
await page.setRequestInterception(true);
|
|
@@ -1518,6 +1525,7 @@ async function renderUncached(ctx, card, leaderboard, itemCard, characterCard, e
|
|
|
1518
1525
|
}, 15e3);
|
|
1519
1526
|
})]);
|
|
1520
1527
|
} finally {
|
|
1528
|
+
finished = true;
|
|
1521
1529
|
if (timeout) clearTimeout(timeout);
|
|
1522
1530
|
try {
|
|
1523
1531
|
await page?.close();
|
|
@@ -1851,7 +1859,7 @@ function renderQQInventoryMarkdown(data, showGreeting) {
|
|
|
1851
1859
|
const sections = [];
|
|
1852
1860
|
const items = [];
|
|
1853
1861
|
const flushItems = /* @__PURE__ */ __name(() => {
|
|
1854
|
-
if (items.length) sections.push(items.splice(0)
|
|
1862
|
+
if (items.length) sections.push(...items.splice(0));
|
|
1855
1863
|
}, "flushItems");
|
|
1856
1864
|
if (showGreeting) sections.push(escapeMarkdown("Ciallo~(∠・ω< )"));
|
|
1857
1865
|
for (const [lineIndex, item] of data.line.entries()) {
|
|
@@ -4455,6 +4463,7 @@ var Server = class {
|
|
|
4455
4463
|
ctx;
|
|
4456
4464
|
onConnectedCallback;
|
|
4457
4465
|
messageListenerSetup = false;
|
|
4466
|
+
lastUserMessageAt = /* @__PURE__ */ new Map();
|
|
4458
4467
|
_init = false;
|
|
4459
4468
|
async init(ctx, config) {
|
|
4460
4469
|
this.ctx = ctx;
|
|
@@ -4464,6 +4473,12 @@ var Server = class {
|
|
|
4464
4473
|
if (this.wsClient) {
|
|
4465
4474
|
await this.dispose();
|
|
4466
4475
|
}
|
|
4476
|
+
ctx.setInterval(() => {
|
|
4477
|
+
const now = performance.now();
|
|
4478
|
+
for (const [key, lastMessageAt] of this.lastUserMessageAt) {
|
|
4479
|
+
if (now - lastMessageAt > 500) this.lastUserMessageAt.delete(key);
|
|
4480
|
+
}
|
|
4481
|
+
}, 6e4);
|
|
4467
4482
|
const serverUrl = config.服务器地址;
|
|
4468
4483
|
if (!serverUrl || serverUrl.trim() === "") {
|
|
4469
4484
|
throw new Error("服务器地址不能为空");
|
|
@@ -4481,6 +4496,7 @@ var Server = class {
|
|
|
4481
4496
|
}
|
|
4482
4497
|
async dispose() {
|
|
4483
4498
|
this._init = false;
|
|
4499
|
+
this.lastUserMessageAt.clear();
|
|
4484
4500
|
if (this.wsClient) {
|
|
4485
4501
|
this.wsClient.unlistenMsgAll(/.*/);
|
|
4486
4502
|
this.wsClient.disconnect();
|
|
@@ -4556,7 +4572,7 @@ var Server = class {
|
|
|
4556
4572
|
}
|
|
4557
4573
|
this.ctx.logger.warn("请求出错", apiName, req.err.message);
|
|
4558
4574
|
}
|
|
4559
|
-
/**
|
|
4575
|
+
/** 过滤机器人发送者和用户连续消息后,缓存会话并把原始内容交给游戏服。 **/
|
|
4560
4576
|
uploadMessage(session) {
|
|
4561
4577
|
if (isBotMessage(session, this.ctx.bots)) return;
|
|
4562
4578
|
const userId = session.userId;
|
|
@@ -4565,6 +4581,11 @@ var Server = class {
|
|
|
4565
4581
|
this.ctx.logger.warn("消息缺少 userId 或 channelId,已忽略");
|
|
4566
4582
|
return;
|
|
4567
4583
|
}
|
|
4584
|
+
const userKey = JSON.stringify([session.platform ?? session.bot.platform, userId]);
|
|
4585
|
+
const now = performance.now();
|
|
4586
|
+
const lastMessageAt = this.lastUserMessageAt.get(userKey);
|
|
4587
|
+
this.lastUserMessageAt.set(userKey, now);
|
|
4588
|
+
if (lastMessageAt !== void 0 && now - lastMessageAt <= 500) return;
|
|
4568
4589
|
sessions_default.set(sessionKey(session.bot.userId, userId, channelId), session);
|
|
4569
4590
|
this.api("Message", {
|
|
4570
4591
|
bot: {
|
package/lib/plugin/server.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ declare class Server {
|
|
|
6
6
|
private ctx;
|
|
7
7
|
private onConnectedCallback?;
|
|
8
8
|
private messageListenerSetup;
|
|
9
|
+
private readonly lastUserMessageAt;
|
|
9
10
|
_init: boolean;
|
|
10
11
|
init(ctx: Context, config: Config): Promise<void>;
|
|
11
12
|
onConnected(callback: () => void): void;
|
|
@@ -14,7 +15,7 @@ declare class Server {
|
|
|
14
15
|
setWsUrl(link: string, maxRetries?: number): Promise<void>;
|
|
15
16
|
private reconnect;
|
|
16
17
|
api<T extends keyof ServiceType['api']>(apiName: T, posData: ServiceType['api'][T]['req']): Promise<ServiceType['api'][T]['res'] | undefined>;
|
|
17
|
-
/**
|
|
18
|
+
/** 过滤机器人发送者和用户连续消息后,缓存会话并把原始内容交给游戏服。 **/
|
|
18
19
|
uploadMessage(session: Session): void;
|
|
19
20
|
}
|
|
20
21
|
declare const _default: Server;
|
package/package.json
CHANGED
package/src/plugin/server.ts
CHANGED
|
@@ -10,6 +10,7 @@ class Server {
|
|
|
10
10
|
private ctx!: Context
|
|
11
11
|
private onConnectedCallback?: () => void
|
|
12
12
|
private messageListenerSetup = false
|
|
13
|
+
private readonly lastUserMessageAt = new Map<string, number>()
|
|
13
14
|
_init = false
|
|
14
15
|
|
|
15
16
|
async init(ctx: Context, config: Config) {
|
|
@@ -20,6 +21,12 @@ class Server {
|
|
|
20
21
|
if (this.wsClient) {
|
|
21
22
|
await this.dispose()
|
|
22
23
|
}
|
|
24
|
+
ctx.setInterval(() => {
|
|
25
|
+
const now = performance.now()
|
|
26
|
+
for (const [key, lastMessageAt] of this.lastUserMessageAt) {
|
|
27
|
+
if (now - lastMessageAt > 500) this.lastUserMessageAt.delete(key)
|
|
28
|
+
}
|
|
29
|
+
}, 60_000)
|
|
23
30
|
const serverUrl = config.服务器地址
|
|
24
31
|
if (!serverUrl || serverUrl.trim() === '') {
|
|
25
32
|
throw new Error('服务器地址不能为空')
|
|
@@ -39,6 +46,7 @@ class Server {
|
|
|
39
46
|
|
|
40
47
|
async dispose() {
|
|
41
48
|
this._init = false
|
|
49
|
+
this.lastUserMessageAt.clear()
|
|
42
50
|
if (this.wsClient) {
|
|
43
51
|
this.wsClient.unlistenMsgAll(/.*/)
|
|
44
52
|
this.wsClient.disconnect()
|
|
@@ -125,7 +133,7 @@ class Server {
|
|
|
125
133
|
this.ctx.logger.warn('请求出错', apiName, req.err.message)
|
|
126
134
|
}
|
|
127
135
|
|
|
128
|
-
/**
|
|
136
|
+
/** 过滤机器人发送者和用户连续消息后,缓存会话并把原始内容交给游戏服。 **/
|
|
129
137
|
uploadMessage(session: Session) {
|
|
130
138
|
if (isBotMessage(session, this.ctx.bots)) return
|
|
131
139
|
const userId = session.userId
|
|
@@ -134,6 +142,13 @@ class Server {
|
|
|
134
142
|
this.ctx.logger.warn('消息缺少 userId 或 channelId,已忽略')
|
|
135
143
|
return
|
|
136
144
|
}
|
|
145
|
+
const userKey = JSON.stringify([session.platform ?? session.bot.platform, userId])
|
|
146
|
+
const now = performance.now()
|
|
147
|
+
const lastMessageAt = this.lastUserMessageAt.get(userKey)
|
|
148
|
+
// 被忽略的消息也计入上一条,连续快速发送时只有第一条上传。
|
|
149
|
+
this.lastUserMessageAt.set(userKey, now)
|
|
150
|
+
if (lastMessageAt !== undefined && now - lastMessageAt <= 500) return
|
|
151
|
+
|
|
137
152
|
sessions.set(sessionKey(session.bot.userId, userId, channelId), session)
|
|
138
153
|
this.api('Message', {
|
|
139
154
|
bot: {
|
|
@@ -155,7 +155,7 @@ function renderQQTaskMarkdown(data: IMessage): string {
|
|
|
155
155
|
return sections.join('\n\n') || ' '
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
-
/** Backpack cards use a compact filter row and
|
|
158
|
+
/** Backpack cards use a compact filter row and separate clickable item paragraphs. */
|
|
159
159
|
const inventoryFilterCategories = [
|
|
160
160
|
{ name: '装备', emoji: '⚔️' },
|
|
161
161
|
{ name: '装备图纸', emoji: '📐' },
|
|
@@ -193,7 +193,9 @@ function renderQQInventoryMarkdown(data: IMessage, showGreeting: boolean): strin
|
|
|
193
193
|
const sections: string[] = []
|
|
194
194
|
const items: string[] = []
|
|
195
195
|
const flushItems = () => {
|
|
196
|
-
|
|
196
|
+
// Avoid a shared blockquote/paragraph around mobile QQ command links.
|
|
197
|
+
// Each item keeps its image and single command in an independent paragraph.
|
|
198
|
+
if (items.length) sections.push(...items.splice(0))
|
|
197
199
|
}
|
|
198
200
|
if (showGreeting) sections.push(escapeMarkdown('Ciallo~(∠・ω< )'))
|
|
199
201
|
for (const [lineIndex, item] of data.line.entries()) {
|
package/src/plugin/skillCard.ts
CHANGED
|
@@ -126,17 +126,29 @@ async function renderUncached(ctx: Context, card?: SkillCardData, leaderboard?:
|
|
|
126
126
|
let timeout: ReturnType<typeof setTimeout> | undefined
|
|
127
127
|
let workPromise: Promise<{ image: Buffer; cacheable: boolean }> | undefined
|
|
128
128
|
let expired = false
|
|
129
|
+
let finished = false
|
|
129
130
|
const release = () => { const next = waiting.shift(); if (next) next(); else running-- }
|
|
130
131
|
try {
|
|
131
132
|
const work = async () => {
|
|
132
|
-
const entryArtworkPromise = loadCardEntryArtwork(!titleCard && !bloodlineCard && !equipmentCard ? companionCard : undefined)
|
|
133
133
|
const remoteAddress = titleCard ? titleCard.artworkUrl : bloodlineCard ? bloodlineCard.artworkUrl : equipmentCard ? equipmentCard.artworkUrl : companionCard ? companionCard.artworkUrl : characterCard ? characterCard.artworkUrl : itemCard ? itemCard.artworkUrl : leaderboard ? leaderboard.backgroundUrl : card?.artworkUrl
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
134
|
+
const isEquipment = !titleCard && !bloodlineCard && Boolean(equipmentCard)
|
|
135
|
+
const isCharacter = !titleCard && !bloodlineCard && !equipmentCard && !companionCard && Boolean(characterCard)
|
|
136
|
+
// Download independent artwork and open the browser together. A cold portrait
|
|
137
|
+
// must not postpone its title download or browser startup.
|
|
138
|
+
const [remoteArt, fallbackArt, entryArtwork, characterAssets, header] = await Promise.all([
|
|
139
|
+
loadSkillArtwork(remoteAddress),
|
|
140
|
+
isEquipment ? equipmentFallback() : undefined,
|
|
141
|
+
loadCardEntryArtwork(!titleCard && !bloodlineCard && !equipmentCard ? companionCard : undefined),
|
|
142
|
+
isCharacter ? Promise.all([characterAsset(), loadSkillArtwork(characterCard!.titleImageUrl)]) : undefined,
|
|
143
|
+
!titleCard && !bloodlineCard && !equipmentCard && !companionCard && !characterCard && !itemCard && leaderboard ? leaderboardHeader() : undefined,
|
|
144
|
+
ctx.puppeteer.page().then(async created => {
|
|
145
|
+
if (expired || finished) { await created.close(); throw new Error(expired ? '绘图超时' : '绘图已结束') }
|
|
146
|
+
page = created
|
|
147
|
+
}),
|
|
148
|
+
])
|
|
149
|
+
const dedicatedArt = isEquipment ? remoteArt : undefined
|
|
150
|
+
const unknownArt = isEquipment && !dedicatedArt ? fallbackArt : undefined
|
|
151
|
+
const art = isEquipment ? dedicatedArt ?? unknownArt : remoteArt
|
|
140
152
|
let html: string
|
|
141
153
|
let loadedCharacterTitle: string | undefined
|
|
142
154
|
let localAssetsReady = true
|
|
@@ -150,23 +162,19 @@ async function renderUncached(ctx: Context, card?: SkillCardData, leaderboard?:
|
|
|
150
162
|
} else if (companionCard) {
|
|
151
163
|
html = companionCardHtml(companionCard, art, entryArtwork.icons)
|
|
152
164
|
} else if (characterCard) {
|
|
153
|
-
const [defaultArtwork, titleArtwork] =
|
|
154
|
-
characterAsset(),
|
|
155
|
-
loadSkillArtwork(characterCard.titleImageUrl),
|
|
156
|
-
])
|
|
165
|
+
const [defaultArtwork, titleArtwork] = characterAssets!
|
|
157
166
|
loadedCharacterTitle = titleArtwork
|
|
158
167
|
localAssetsReady = Boolean(art || defaultArtwork) && (!characterCard.titleImageUrl || Boolean(titleArtwork))
|
|
159
168
|
html = characterCardHtml(characterCard, art, undefined, defaultArtwork, titleArtwork, entryArtwork.icons)
|
|
160
169
|
} else if (itemCard) {
|
|
161
170
|
html = itemCard.type === '形象卡' ? appearanceCardHtml(itemCard, art) : itemCardHtml(itemCard, art)
|
|
162
171
|
} else if (leaderboard) {
|
|
163
|
-
html = leaderboardHtml(leaderboard,
|
|
172
|
+
html = leaderboardHtml(leaderboard, header, art)
|
|
164
173
|
} else {
|
|
165
174
|
html = skillCardHtml(card!, '', art)
|
|
166
175
|
}
|
|
167
176
|
if (expired) throw new Error('绘图超时')
|
|
168
|
-
page
|
|
169
|
-
if (expired) { await page.close(); page = undefined; throw new Error('绘图超时') }
|
|
177
|
+
if (!page) throw new Error('卡片页面缺失')
|
|
170
178
|
await page.setViewport({ width:760,height:1107,deviceScaleFactor:titleCard || (companionCard || characterCard) && !bloodlineCard && !equipmentCard ? 2 : 1 })
|
|
171
179
|
await page.setJavaScriptEnabled(false)
|
|
172
180
|
await page.setRequestInterception(true)
|
|
@@ -206,6 +214,7 @@ async function renderUncached(ctx: Context, card?: SkillCardData, leaderboard?:
|
|
|
206
214
|
workPromise = work()
|
|
207
215
|
return await Promise.race([workPromise, new Promise<never>((_,reject) => {timeout=setTimeout(()=>{ expired=true; reject(new Error('绘图超时')) },15000)})])
|
|
208
216
|
} finally {
|
|
217
|
+
finished = true
|
|
209
218
|
if(timeout) clearTimeout(timeout)
|
|
210
219
|
try { await page?.close() } catch {}
|
|
211
220
|
if (expired && workPromise) {
|