koishi-plugin-dew-bot 1.0.257 → 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 CHANGED
@@ -319,7 +319,7 @@ var characterCardStyles = `
319
319
  .character-sheet .companion-portrait:before{inset:5px;border-radius:12px;border-color:#fffdf1cf;box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 43%,transparent)}.character-sheet .companion-portrait:after{inset:2px;border-radius:15px;border-color:#fffefa9c}.character-sheet .companion-portrait .panel-corner{width:var(--portrait-ornament-size);height:var(--portrait-ornament-size)}
320
320
  .character-sheet .portrait-window{height:auto;border:2px solid var(--edge);border-radius:6px;overflow:hidden;background:var(--paper)}.character-artwork{display:block;width:100%;height:auto;object-fit:contain}.character-sheet .portrait-placeholder,.character-sheet .placeholder{height:100%;width:100%;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;color:var(--subtle);font:21px/1.5 "Microsoft YaHei",sans-serif}.character-sheet .placeholder small{font-size:16px}.character-sheet .portrait-window>.placeholder{height:448px}
321
321
  .character-stats{padding:17px 18px 15px;display:flex;flex-direction:column}.character-stats>.character-identity,.character-stats>.combat-row,.character-stats>.resource,.character-stats>.attribute-panel{position:relative;z-index:4}
322
- .character-identity{text-align:center;min-width:0}.title-image-slot{display:flex;align-items:center;justify-content:center;min-width:0;margin:9px 10px 0}.title-artwork{display:block;width:auto;height:auto;max-width:100%;max-height:110px;object-fit:contain}.title-artwork[alt="称号图片占位"]{max-width:244px;max-height:44px}
322
+ .character-identity{text-align:center;min-width:0}.title-image-slot{position:absolute;inset:12px;display:flex;align-items:center;justify-content:center;min-width:0;z-index:4;pointer-events:none}.title-artwork{display:block;width:auto;height:auto;max-width:100%;max-height:min(110px,100%);object-fit:contain;filter:drop-shadow(0 2px 3px #17213a70)}.title-artwork[alt="称号图片占位"]{max-width:min(244px,100%);max-height:min(44px,100%)}
323
323
  .character-name{font-size:43px;line-height:1.17;font-weight:700;letter-spacing:3px;margin:0 0 3px;overflow-wrap:anywhere;color:var(--ink);text-shadow:0 1px 0 #ffffff}.character-level{font:27px/1.25 Georgia,"Microsoft YaHei",serif;margin:0;color:var(--subtle);display:flex;align-items:baseline;justify-content:center;gap:7px;min-width:0}.character-level strong{font-size:28px;font-weight:500;overflow-wrap:anywhere;min-width:0}
324
324
  .gene-panel{position:relative;margin:8px auto 0;padding:4px 12px 4px 7px;display:flex;align-items:center;justify-content:center;gap:6px;width:fit-content;max-width:100%;border:1px solid color-mix(in srgb,var(--edge) 80%,#ffffff);border-radius:7px;background:linear-gradient(#ffffffd6,color-mix(in srgb,var(--paper) 75%,#ffffff));box-shadow:inset 0 0 0 2px #ffffffaa,0 1px 2px #54628210}
325
325
  .gene-panel:before,.gene-panel:after{content:"";position:absolute;top:50%;width:6px;height:6px;background:var(--light-edge);border:1px solid var(--edge);transform:translateY(-50%) rotate(45deg)}.gene-panel:before{left:-4px}.gene-panel:after{right:-4px}.gene-crest{flex:0 0 26px;height:29px;color:var(--accent)}.gene-crest svg{display:block;width:100%;height:100%}.gene-rank{font-size:18px;line-height:1.35;letter-spacing:1px;overflow-wrap:anywhere;font-weight:700;text-align:center;min-width:0;color:var(--ink)}
@@ -440,8 +440,8 @@ function characterCardHtml(card, artwork, _frame, defaultArtwork, titleArtwork,
440
440
  const experience = Math.max(0, Math.min(100, finite(card.experiencePercent)));
441
441
  const geneRank = card.geneLockRankName || "基因锁未开启";
442
442
  return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><style>${characterCardStyles}</style></head><body><main id="capture" class="companion-sheet character-adventure character-sheet gene-tier-${tier}" data-gene-lock-tier="${tier}" data-decoration-level="${tier}"><span class="character-ambient" aria-hidden="true">${characterFloralWatermark}</span><div class="character-card companion-main">
443
- <div class="portrait-column"><div class="companion-panel companion-portrait character-portrait">${corners(tier)}<div class="portrait-window portrait">${portrait ? `<img id="character-image" class="character-artwork" src="${cardEscape(portrait)}" data-fallback="${cardEscape(defaultArtwork ?? "")}" alt="${cardEscape(card.name)}的形象图">` : '<div class="placeholder"><span>形象尚未生成</span><small>角色形象</small></div>'}</div></div>
444
- <div class="title-image-slot character-title-below"><img id="title-image" class="title-artwork" src="${cardEscape(titleArtwork || characterTitlePlaceholder)}" data-fallback="${cardEscape(characterTitlePlaceholder)}" alt="${titleArtwork ? "称号图片" : "称号图片占位"}"></div></div>
443
+ <div class="portrait-column"><div class="companion-panel companion-portrait character-portrait">${corners(tier)}<div class="portrait-window portrait">${portrait ? `<img id="character-image" class="character-artwork" src="${cardEscape(portrait)}" data-fallback="${cardEscape(defaultArtwork ?? "")}" alt="${cardEscape(card.name)}的形象图">` : '<div class="placeholder"><span>形象尚未生成</span><small>角色形象</small></div>'}</div>
444
+ <div class="title-image-slot character-title-centered"><img id="title-image" class="title-artwork" src="${cardEscape(titleArtwork || characterTitlePlaceholder)}" data-fallback="${cardEscape(characterTitlePlaceholder)}" alt="${titleArtwork ? "称号图片" : "称号图片占位"}"></div></div></div>
445
445
  <section class="companion-panel companion-stats character-stats" aria-label="角色属性"><span class="character-paper-pattern" aria-hidden="true">${characterFloralWatermark}</span>${corners(tier, ["tr", "br"])}<div class="identity character-identity"><h1 id="character-name" class="character-name" title="${cardEscape(card.name)}">${cardEscape(card.name)}</h1><p class="level character-level"><span>Lv.</span><strong id="character-level">${number2(card.level)}</strong></p><div class="gene-panel"><span class="gene-crest" aria-hidden="true">${characterGeneCrest(tier)}</span><div id="gene-rank" class="gene-rank" title="${cardEscape(geneRank)}">${cardEscape(geneRank)}</div></div></div>
446
446
  <div class="combat-row companion-power-row"><span class="power-emblem power-icon" aria-hidden="true"><svg viewBox="0 0 42 42"><path d="m9 5 5 1 16 21-4 4L9 11Zm24 0-5 1L12 27l4 4L33 11ZM14 27l14 10m0-10L14 37M9 35l4-4m20 4-4-4"/></svg></span><span class="power-label companion-power-label">战力</span><strong id="combat-power" class="power-value companion-power">${number2(card.power)}</strong></div>
447
447
  <div class="resource companion-resource hp-resource"><div class="resource-heading"><span class="resource-label"><svg viewBox="0 0 32 32" aria-hidden="true"><path d="M16 28C6 21 2 17 2 10a7 7 0 0 1 14-2 7 7 0 0 1 14 2c0 7-4 11-14 18Z"/></svg>生命值</span><span id="hp-numbers" class="resource-number">${number2(card.hp)} / ${number2(card.maxHp)}</span></div><div class="resource-track" role="progressbar" aria-label="生命值" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${hpPercent}"><span class="resource-fill fill" style="width:${hpPercent}%"></span><span class="bar-percent resource-percent">${number2(hpPercent)}%</span></div></div>
@@ -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 dedicatedArt = !titleCard && !bloodlineCard && equipmentCard ? await loadSkillArtwork(equipmentCard.artworkUrl) : void 0;
1435
- const unknownArt = !titleCard && !bloodlineCard && equipmentCard && !dedicatedArt ? await equipmentFallback() : void 0;
1436
- const art = !titleCard && !bloodlineCard && equipmentCard ? dedicatedArt ?? unknownArt : await loadSkillArtwork(remoteAddress);
1437
- const entryArtwork = await entryArtworkPromise;
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] = await Promise.all([
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, await leaderboardHeader(), art);
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 = await ctx.puppeteer.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).map((line) => "> " + line).join(" \n"));
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: {
@@ -1,2 +1,2 @@
1
1
  /** Character-only moon-white paper, metallic frames and six botanical gene-lock themes. */
2
- export declare const characterCardStyles = "\n*{box-sizing:border-box}body{margin:0;width:760px}#capture{width:760px;position:relative;overflow:hidden;font-family:\"KaiTi\",\"STKaiti\",\"Microsoft YaHei\",sans-serif}\n.character-sheet{--ink:#374556;--subtle:#748192;--accent:#9b927d;--edge:#aab4bd;--light-edge:#fffefa;--metal-shadow:#8f9ba8;--panel:#fffefa;--panel-deep:#f0f3f5;--paper:#e8edf0;--flower:#fffefa;--leaf:#9aaabb;--leaf-light:#e2e9ed;--gem:#bbc9d4;--track:#e2e7dd;--track-edge:#afbea9;--health:#759569;--health-light:#b4cea2;--radar:#8995ae;--radar-fill:#aebbdc66;--radar-grid:#c4cbd7;--background:#dce3e9;--background-deep:#bac7d3;--ornament-size:86px;--portrait-ornament-size:74px;--frame-radius:27px;--panel-border:2px;padding:22px;color:var(--ink);background:radial-gradient(ellipse at 12% 6%,#fffefaef,transparent 51%),radial-gradient(ellipse at 85% 90%,#f6f8ffc4,transparent 51%),radial-gradient(ellipse at 78% 12%,#ffffff80,transparent 38%),linear-gradient(145deg,var(--background-deep),var(--background) 48%,var(--background-deep))}\n.gene-tier-0{--ink:#435162;--subtle:#7c8795;--accent:#989081;--edge:#b3bac1;--leaf:#9caab8;--leaf-light:#e4e9ef;--gem:#cbd3de;--background:#e3e8ed;--background-deep:#c6cfd9}\n.gene-tier-1{--ink:#365762;--subtle:#718b90;--accent:#b29d77;--edge:#bbc1b2;--metal-shadow:#949f9f;--panel-deep:#f0f6f3;--paper:#e7f0ec;--leaf:#81a6ad;--leaf-light:#d5e9e6;--gem:#a8d4cf;--radar:#7e9baf;--radar-fill:#b6d2dd69;--radar-grid:#bfced5;--background:#e5f0ed;--background-deep:#bfd6d5;--ornament-size:98px;--portrait-ornament-size:84px}\n.gene-tier-2{--ink:#354c69;--subtle:#72869f;--accent:#afa187;--edge:#b4bdcc;--metal-shadow:#8d9bb0;--panel-deep:#eef3f9;--paper:#e7edf6;--leaf:#829bbd;--leaf-light:#d5e1f1;--gem:#aac6e5;--radar:#8596c1;--radar-fill:#aebcde70;--radar-grid:#c1c9db;--background:#e2e9f5;--background-deep:#bccbdf;--ornament-size:110px;--portrait-ornament-size:94px}\n.gene-tier-3{--ink:#494867;--subtle:#827e9c;--accent:#b3a18b;--edge:#bdb7ca;--metal-shadow:#9992ad;--panel-deep:#f2f0f9;--paper:#ede8f6;--leaf:#9995c1;--leaf-light:#e1def2;--gem:#c0b5e6;--radar:#9b91c5;--radar-fill:#c1b7e16e;--radar-grid:#cdc5dd;--background:#eae6f4;--background-deep:#c9c1df;--ornament-size:123px;--portrait-ornament-size:104px}\n.gene-tier-4{--ink:#644b66;--subtle:#9a7f96;--accent:#b89880;--edge:#c8bac5;--metal-shadow:#ad97a6;--panel-deep:#f7eff4;--paper:#f2e8ee;--leaf:#b797b4;--leaf-light:#f0dce8;--gem:#d5b0ca;--radar:#b498bd;--radar-fill:#d2bddf6b;--radar-grid:#d9c9dc;--background:#f0e6ee;--background-deep:#dbc3d4;--ornament-size:137px;--portrait-ornament-size:115px}\n.gene-tier-5{--ink:#3c416e;--subtle:#797fa1;--accent:#b6995f;--edge:#c2b48f;--metal-shadow:#9c976f;--panel-deep:#f0effa;--paper:#e8e9f5;--leaf:#858dbf;--leaf-light:#d7ddf4;--gem:#b4b6e4;--radar:#9087c8;--radar-fill:#b8b0e476;--radar-grid:#c8c6dd;--background:#e4e5f5;--background-deep:#bdc5e1;--ornament-size:149px;--portrait-ornament-size:126px}\n.character-ambient,.character-paper-pattern{position:absolute;inset:0;pointer-events:none;overflow:hidden;color:var(--leaf)}.character-ambient{opacity:.23}.character-ambient .floral-watermark{width:100%;height:100%;display:block}.character-paper-pattern{opacity:.075;border-radius:25px}.character-paper-pattern .floral-watermark{width:100%;height:100%;display:block}\n.character-card{display:grid;grid-template-columns:344px minmax(0,1fr);gap:16px;align-items:stretch;position:relative;z-index:1}.portrait-column{min-width:0;display:flex;flex-direction:column;justify-content:center}\n.character-sheet .companion-panel{position:relative;min-width:0;border:2px solid var(--edge);border-radius:var(--frame-radius);background:radial-gradient(ellipse at 15% 6%,#ffffffbd,transparent 49%),linear-gradient(155deg,var(--panel),var(--panel-deep));box-shadow:inset 0 0 0 2px #ffffffb5,inset 0 0 0 3px color-mix(in srgb,var(--metal-shadow) 35%,transparent),0 2px 8px #52608026}\n.character-sheet .companion-panel:before{content:\"\";position:absolute;inset:7px;border:1px solid color-mix(in srgb,var(--edge) 65%,#ffffff);border-radius:calc(var(--frame-radius) - 7px);pointer-events:none}\n.character-sheet .companion-panel:after{content:\"\";position:absolute;inset:3px;border:1px solid #fffdf3b3;border-radius:calc(var(--frame-radius) - 2px);pointer-events:none}\n.character-sheet .panel-corner{position:absolute;width:var(--ornament-size);height:var(--ornament-size);pointer-events:none;z-index:3;color:var(--accent)}.panel-corner svg{width:100%;height:100%;display:block;overflow:visible;filter:drop-shadow(0 1px 1px #52608035)}.corner-tl{top:-13px;left:-13px}.corner-tr{top:-13px;right:-13px;transform:rotate(90deg)}.corner-bl{bottom:-13px;left:-13px;transform:rotate(-90deg)}.corner-br{bottom:-13px;right:-13px;transform:rotate(180deg)}\n.character-sheet .companion-portrait{padding:10px;border-radius:18px;flex:0 0 auto;background:linear-gradient(135deg,#fffefa 2%,var(--edge) 18%,#ffffff 30%,var(--accent) 41%,#fffdf2 51%,var(--edge) 71%,#ffffff 82%,var(--metal-shadow) 100%);box-shadow:inset 0 0 0 1px #ffffffd9,0 3px 9px #51628038}\n.character-sheet .companion-portrait:before{inset:5px;border-radius:12px;border-color:#fffdf1cf;box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 43%,transparent)}.character-sheet .companion-portrait:after{inset:2px;border-radius:15px;border-color:#fffefa9c}.character-sheet .companion-portrait .panel-corner{width:var(--portrait-ornament-size);height:var(--portrait-ornament-size)}\n.character-sheet .portrait-window{height:auto;border:2px solid var(--edge);border-radius:6px;overflow:hidden;background:var(--paper)}.character-artwork{display:block;width:100%;height:auto;object-fit:contain}.character-sheet .portrait-placeholder,.character-sheet .placeholder{height:100%;width:100%;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;color:var(--subtle);font:21px/1.5 \"Microsoft YaHei\",sans-serif}.character-sheet .placeholder small{font-size:16px}.character-sheet .portrait-window>.placeholder{height:448px}\n.character-stats{padding:17px 18px 15px;display:flex;flex-direction:column}.character-stats>.character-identity,.character-stats>.combat-row,.character-stats>.resource,.character-stats>.attribute-panel{position:relative;z-index:4}\n.character-identity{text-align:center;min-width:0}.title-image-slot{display:flex;align-items:center;justify-content:center;min-width:0;margin:9px 10px 0}.title-artwork{display:block;width:auto;height:auto;max-width:100%;max-height:110px;object-fit:contain}.title-artwork[alt=\"\u79F0\u53F7\u56FE\u7247\u5360\u4F4D\"]{max-width:244px;max-height:44px}\n.character-name{font-size:43px;line-height:1.17;font-weight:700;letter-spacing:3px;margin:0 0 3px;overflow-wrap:anywhere;color:var(--ink);text-shadow:0 1px 0 #ffffff}.character-level{font:27px/1.25 Georgia,\"Microsoft YaHei\",serif;margin:0;color:var(--subtle);display:flex;align-items:baseline;justify-content:center;gap:7px;min-width:0}.character-level strong{font-size:28px;font-weight:500;overflow-wrap:anywhere;min-width:0}\n.gene-panel{position:relative;margin:8px auto 0;padding:4px 12px 4px 7px;display:flex;align-items:center;justify-content:center;gap:6px;width:fit-content;max-width:100%;border:1px solid color-mix(in srgb,var(--edge) 80%,#ffffff);border-radius:7px;background:linear-gradient(#ffffffd6,color-mix(in srgb,var(--paper) 75%,#ffffff));box-shadow:inset 0 0 0 2px #ffffffaa,0 1px 2px #54628210}\n.gene-panel:before,.gene-panel:after{content:\"\";position:absolute;top:50%;width:6px;height:6px;background:var(--light-edge);border:1px solid var(--edge);transform:translateY(-50%) rotate(45deg)}.gene-panel:before{left:-4px}.gene-panel:after{right:-4px}.gene-crest{flex:0 0 26px;height:29px;color:var(--accent)}.gene-crest svg{display:block;width:100%;height:100%}.gene-rank{font-size:18px;line-height:1.35;letter-spacing:1px;overflow-wrap:anywhere;font-weight:700;text-align:center;min-width:0;color:var(--ink)}\n.combat-row{display:flex;align-items:center;gap:8px;margin:14px 0 12px;min-width:0}.power-icon{flex:0 0 42px;height:42px;display:grid;place-items:center;border-radius:50%;border:1px solid var(--accent);box-shadow:inset 0 0 0 3px #fffef3b5;color:var(--accent);background:radial-gradient(ellipse at 38% 25%,#fffdf0,color-mix(in srgb,var(--accent) 14%,var(--panel)))}.power-icon svg{width:27px;height:27px;stroke:currentColor;fill:currentColor;stroke-width:2;stroke-linejoin:round}.power-label{font-size:21px;white-space:nowrap}.power-value{min-width:0;flex:1;text-align:right;overflow-wrap:anywhere;font:35px/1.2 Georgia,\"Microsoft YaHei\",serif;color:var(--accent);letter-spacing:-1px}\n.companion-resource{font-family:\"Microsoft YaHei\",sans-serif;margin-bottom:12px}.resource-heading{display:flex;justify-content:space-between;align-items:baseline;gap:8px;font-size:17px;color:var(--subtle);line-height:1.35}.resource-label{display:flex;align-items:center;gap:6px;font-weight:600;flex-shrink:0}.resource-label svg{width:18px;height:18px;fill:var(--health)}.resource-number{text-align:right;overflow-wrap:anywhere;min-width:0;font-size:15px}\n.resource-track{height:18px;position:relative;margin-top:5px;border:1px solid var(--track-edge);border-radius:10px;overflow:hidden;background:linear-gradient(var(--track),#f2f5ed);box-shadow:inset 0 1px 2px #6e805825}.resource-fill{display:block;position:absolute;top:0;bottom:0;left:0;border-radius:9px;background:linear-gradient(var(--health-light),var(--health) 55%,color-mix(in srgb,var(--health) 80%,var(--health-light)));border-right:1px solid #fff9}.resource-percent{position:absolute;inset:0;display:grid;place-items:center;font:11px/1 \"Microsoft YaHei\",sans-serif;color:#fffef7;text-shadow:0 1px 2px #435443}.exp-resource .resource-heading{color:var(--subtle)}.exp-resource .resource-label svg{fill:var(--radar)}.exp-resource .resource-track{height:16px;border-color:color-mix(in srgb,var(--radar) 60%,#fff);background:var(--paper)}.exp-resource .resource-fill{background:linear-gradient(var(--leaf-light),var(--radar) 56%,var(--leaf))}\n.companion-attributes{margin-top:5px;min-width:0;flex:1;display:flex;flex-direction:column;justify-content:center}.ornament-heading{display:flex;align-items:center;justify-content:center;gap:7px;margin:0 0 3px;font-size:22px;font-weight:700;letter-spacing:2px;line-height:1.3;color:var(--ink)}.ornament-heading:before,.ornament-heading:after{content:\"\";height:1px;flex:1;background:linear-gradient(90deg,transparent,var(--edge))}.ornament-heading:after{transform:rotate(180deg)}.heading-star{font-size:12px;color:var(--accent);letter-spacing:0}.companion-radar{width:100%;height:260px;display:block;overflow:visible}\n.radar-grid{fill:none;stroke:var(--radar-grid);stroke-width:1.1}.radar-axis{stroke:var(--radar-grid);stroke-width:1;opacity:.8}.radar-area{fill:var(--radar-fill);stroke:var(--radar);stroke-width:2.3;stroke-linejoin:round}.radar-dot{fill:#fffefa;stroke:var(--radar);stroke-width:2}.radar-label{font:19px \"KaiTi\",\"STKaiti\",\"Microsoft YaHei\",sans-serif;fill:var(--ink)}.radar-value{font:20px Georgia,\"Microsoft YaHei\",serif;fill:var(--ink)}.radar-scale{font:11px Georgia,serif;fill:var(--subtle)}.empty-attributes{padding:45px 10px;color:var(--subtle);font:18px/1.6 \"Microsoft YaHei\",sans-serif;text-align:center}.custom-attributes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:5px 10px;margin:6px 0 0;font:15px/1.5 \"Microsoft YaHei\",sans-serif;color:var(--subtle)}.custom-attributes span{overflow-wrap:anywhere}.custom-attributes b{font-weight:500;color:var(--ink)}\n";
2
+ export declare const characterCardStyles = "\n*{box-sizing:border-box}body{margin:0;width:760px}#capture{width:760px;position:relative;overflow:hidden;font-family:\"KaiTi\",\"STKaiti\",\"Microsoft YaHei\",sans-serif}\n.character-sheet{--ink:#374556;--subtle:#748192;--accent:#9b927d;--edge:#aab4bd;--light-edge:#fffefa;--metal-shadow:#8f9ba8;--panel:#fffefa;--panel-deep:#f0f3f5;--paper:#e8edf0;--flower:#fffefa;--leaf:#9aaabb;--leaf-light:#e2e9ed;--gem:#bbc9d4;--track:#e2e7dd;--track-edge:#afbea9;--health:#759569;--health-light:#b4cea2;--radar:#8995ae;--radar-fill:#aebbdc66;--radar-grid:#c4cbd7;--background:#dce3e9;--background-deep:#bac7d3;--ornament-size:86px;--portrait-ornament-size:74px;--frame-radius:27px;--panel-border:2px;padding:22px;color:var(--ink);background:radial-gradient(ellipse at 12% 6%,#fffefaef,transparent 51%),radial-gradient(ellipse at 85% 90%,#f6f8ffc4,transparent 51%),radial-gradient(ellipse at 78% 12%,#ffffff80,transparent 38%),linear-gradient(145deg,var(--background-deep),var(--background) 48%,var(--background-deep))}\n.gene-tier-0{--ink:#435162;--subtle:#7c8795;--accent:#989081;--edge:#b3bac1;--leaf:#9caab8;--leaf-light:#e4e9ef;--gem:#cbd3de;--background:#e3e8ed;--background-deep:#c6cfd9}\n.gene-tier-1{--ink:#365762;--subtle:#718b90;--accent:#b29d77;--edge:#bbc1b2;--metal-shadow:#949f9f;--panel-deep:#f0f6f3;--paper:#e7f0ec;--leaf:#81a6ad;--leaf-light:#d5e9e6;--gem:#a8d4cf;--radar:#7e9baf;--radar-fill:#b6d2dd69;--radar-grid:#bfced5;--background:#e5f0ed;--background-deep:#bfd6d5;--ornament-size:98px;--portrait-ornament-size:84px}\n.gene-tier-2{--ink:#354c69;--subtle:#72869f;--accent:#afa187;--edge:#b4bdcc;--metal-shadow:#8d9bb0;--panel-deep:#eef3f9;--paper:#e7edf6;--leaf:#829bbd;--leaf-light:#d5e1f1;--gem:#aac6e5;--radar:#8596c1;--radar-fill:#aebcde70;--radar-grid:#c1c9db;--background:#e2e9f5;--background-deep:#bccbdf;--ornament-size:110px;--portrait-ornament-size:94px}\n.gene-tier-3{--ink:#494867;--subtle:#827e9c;--accent:#b3a18b;--edge:#bdb7ca;--metal-shadow:#9992ad;--panel-deep:#f2f0f9;--paper:#ede8f6;--leaf:#9995c1;--leaf-light:#e1def2;--gem:#c0b5e6;--radar:#9b91c5;--radar-fill:#c1b7e16e;--radar-grid:#cdc5dd;--background:#eae6f4;--background-deep:#c9c1df;--ornament-size:123px;--portrait-ornament-size:104px}\n.gene-tier-4{--ink:#644b66;--subtle:#9a7f96;--accent:#b89880;--edge:#c8bac5;--metal-shadow:#ad97a6;--panel-deep:#f7eff4;--paper:#f2e8ee;--leaf:#b797b4;--leaf-light:#f0dce8;--gem:#d5b0ca;--radar:#b498bd;--radar-fill:#d2bddf6b;--radar-grid:#d9c9dc;--background:#f0e6ee;--background-deep:#dbc3d4;--ornament-size:137px;--portrait-ornament-size:115px}\n.gene-tier-5{--ink:#3c416e;--subtle:#797fa1;--accent:#b6995f;--edge:#c2b48f;--metal-shadow:#9c976f;--panel-deep:#f0effa;--paper:#e8e9f5;--leaf:#858dbf;--leaf-light:#d7ddf4;--gem:#b4b6e4;--radar:#9087c8;--radar-fill:#b8b0e476;--radar-grid:#c8c6dd;--background:#e4e5f5;--background-deep:#bdc5e1;--ornament-size:149px;--portrait-ornament-size:126px}\n.character-ambient,.character-paper-pattern{position:absolute;inset:0;pointer-events:none;overflow:hidden;color:var(--leaf)}.character-ambient{opacity:.23}.character-ambient .floral-watermark{width:100%;height:100%;display:block}.character-paper-pattern{opacity:.075;border-radius:25px}.character-paper-pattern .floral-watermark{width:100%;height:100%;display:block}\n.character-card{display:grid;grid-template-columns:344px minmax(0,1fr);gap:16px;align-items:stretch;position:relative;z-index:1}.portrait-column{min-width:0;display:flex;flex-direction:column;justify-content:center}\n.character-sheet .companion-panel{position:relative;min-width:0;border:2px solid var(--edge);border-radius:var(--frame-radius);background:radial-gradient(ellipse at 15% 6%,#ffffffbd,transparent 49%),linear-gradient(155deg,var(--panel),var(--panel-deep));box-shadow:inset 0 0 0 2px #ffffffb5,inset 0 0 0 3px color-mix(in srgb,var(--metal-shadow) 35%,transparent),0 2px 8px #52608026}\n.character-sheet .companion-panel:before{content:\"\";position:absolute;inset:7px;border:1px solid color-mix(in srgb,var(--edge) 65%,#ffffff);border-radius:calc(var(--frame-radius) - 7px);pointer-events:none}\n.character-sheet .companion-panel:after{content:\"\";position:absolute;inset:3px;border:1px solid #fffdf3b3;border-radius:calc(var(--frame-radius) - 2px);pointer-events:none}\n.character-sheet .panel-corner{position:absolute;width:var(--ornament-size);height:var(--ornament-size);pointer-events:none;z-index:3;color:var(--accent)}.panel-corner svg{width:100%;height:100%;display:block;overflow:visible;filter:drop-shadow(0 1px 1px #52608035)}.corner-tl{top:-13px;left:-13px}.corner-tr{top:-13px;right:-13px;transform:rotate(90deg)}.corner-bl{bottom:-13px;left:-13px;transform:rotate(-90deg)}.corner-br{bottom:-13px;right:-13px;transform:rotate(180deg)}\n.character-sheet .companion-portrait{padding:10px;border-radius:18px;flex:0 0 auto;background:linear-gradient(135deg,#fffefa 2%,var(--edge) 18%,#ffffff 30%,var(--accent) 41%,#fffdf2 51%,var(--edge) 71%,#ffffff 82%,var(--metal-shadow) 100%);box-shadow:inset 0 0 0 1px #ffffffd9,0 3px 9px #51628038}\n.character-sheet .companion-portrait:before{inset:5px;border-radius:12px;border-color:#fffdf1cf;box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 43%,transparent)}.character-sheet .companion-portrait:after{inset:2px;border-radius:15px;border-color:#fffefa9c}.character-sheet .companion-portrait .panel-corner{width:var(--portrait-ornament-size);height:var(--portrait-ornament-size)}\n.character-sheet .portrait-window{height:auto;border:2px solid var(--edge);border-radius:6px;overflow:hidden;background:var(--paper)}.character-artwork{display:block;width:100%;height:auto;object-fit:contain}.character-sheet .portrait-placeholder,.character-sheet .placeholder{height:100%;width:100%;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;color:var(--subtle);font:21px/1.5 \"Microsoft YaHei\",sans-serif}.character-sheet .placeholder small{font-size:16px}.character-sheet .portrait-window>.placeholder{height:448px}\n.character-stats{padding:17px 18px 15px;display:flex;flex-direction:column}.character-stats>.character-identity,.character-stats>.combat-row,.character-stats>.resource,.character-stats>.attribute-panel{position:relative;z-index:4}\n.character-identity{text-align:center;min-width:0}.title-image-slot{position:absolute;inset:12px;display:flex;align-items:center;justify-content:center;min-width:0;z-index:4;pointer-events:none}.title-artwork{display:block;width:auto;height:auto;max-width:100%;max-height:min(110px,100%);object-fit:contain;filter:drop-shadow(0 2px 3px #17213a70)}.title-artwork[alt=\"\u79F0\u53F7\u56FE\u7247\u5360\u4F4D\"]{max-width:min(244px,100%);max-height:min(44px,100%)}\n.character-name{font-size:43px;line-height:1.17;font-weight:700;letter-spacing:3px;margin:0 0 3px;overflow-wrap:anywhere;color:var(--ink);text-shadow:0 1px 0 #ffffff}.character-level{font:27px/1.25 Georgia,\"Microsoft YaHei\",serif;margin:0;color:var(--subtle);display:flex;align-items:baseline;justify-content:center;gap:7px;min-width:0}.character-level strong{font-size:28px;font-weight:500;overflow-wrap:anywhere;min-width:0}\n.gene-panel{position:relative;margin:8px auto 0;padding:4px 12px 4px 7px;display:flex;align-items:center;justify-content:center;gap:6px;width:fit-content;max-width:100%;border:1px solid color-mix(in srgb,var(--edge) 80%,#ffffff);border-radius:7px;background:linear-gradient(#ffffffd6,color-mix(in srgb,var(--paper) 75%,#ffffff));box-shadow:inset 0 0 0 2px #ffffffaa,0 1px 2px #54628210}\n.gene-panel:before,.gene-panel:after{content:\"\";position:absolute;top:50%;width:6px;height:6px;background:var(--light-edge);border:1px solid var(--edge);transform:translateY(-50%) rotate(45deg)}.gene-panel:before{left:-4px}.gene-panel:after{right:-4px}.gene-crest{flex:0 0 26px;height:29px;color:var(--accent)}.gene-crest svg{display:block;width:100%;height:100%}.gene-rank{font-size:18px;line-height:1.35;letter-spacing:1px;overflow-wrap:anywhere;font-weight:700;text-align:center;min-width:0;color:var(--ink)}\n.combat-row{display:flex;align-items:center;gap:8px;margin:14px 0 12px;min-width:0}.power-icon{flex:0 0 42px;height:42px;display:grid;place-items:center;border-radius:50%;border:1px solid var(--accent);box-shadow:inset 0 0 0 3px #fffef3b5;color:var(--accent);background:radial-gradient(ellipse at 38% 25%,#fffdf0,color-mix(in srgb,var(--accent) 14%,var(--panel)))}.power-icon svg{width:27px;height:27px;stroke:currentColor;fill:currentColor;stroke-width:2;stroke-linejoin:round}.power-label{font-size:21px;white-space:nowrap}.power-value{min-width:0;flex:1;text-align:right;overflow-wrap:anywhere;font:35px/1.2 Georgia,\"Microsoft YaHei\",serif;color:var(--accent);letter-spacing:-1px}\n.companion-resource{font-family:\"Microsoft YaHei\",sans-serif;margin-bottom:12px}.resource-heading{display:flex;justify-content:space-between;align-items:baseline;gap:8px;font-size:17px;color:var(--subtle);line-height:1.35}.resource-label{display:flex;align-items:center;gap:6px;font-weight:600;flex-shrink:0}.resource-label svg{width:18px;height:18px;fill:var(--health)}.resource-number{text-align:right;overflow-wrap:anywhere;min-width:0;font-size:15px}\n.resource-track{height:18px;position:relative;margin-top:5px;border:1px solid var(--track-edge);border-radius:10px;overflow:hidden;background:linear-gradient(var(--track),#f2f5ed);box-shadow:inset 0 1px 2px #6e805825}.resource-fill{display:block;position:absolute;top:0;bottom:0;left:0;border-radius:9px;background:linear-gradient(var(--health-light),var(--health) 55%,color-mix(in srgb,var(--health) 80%,var(--health-light)));border-right:1px solid #fff9}.resource-percent{position:absolute;inset:0;display:grid;place-items:center;font:11px/1 \"Microsoft YaHei\",sans-serif;color:#fffef7;text-shadow:0 1px 2px #435443}.exp-resource .resource-heading{color:var(--subtle)}.exp-resource .resource-label svg{fill:var(--radar)}.exp-resource .resource-track{height:16px;border-color:color-mix(in srgb,var(--radar) 60%,#fff);background:var(--paper)}.exp-resource .resource-fill{background:linear-gradient(var(--leaf-light),var(--radar) 56%,var(--leaf))}\n.companion-attributes{margin-top:5px;min-width:0;flex:1;display:flex;flex-direction:column;justify-content:center}.ornament-heading{display:flex;align-items:center;justify-content:center;gap:7px;margin:0 0 3px;font-size:22px;font-weight:700;letter-spacing:2px;line-height:1.3;color:var(--ink)}.ornament-heading:before,.ornament-heading:after{content:\"\";height:1px;flex:1;background:linear-gradient(90deg,transparent,var(--edge))}.ornament-heading:after{transform:rotate(180deg)}.heading-star{font-size:12px;color:var(--accent);letter-spacing:0}.companion-radar{width:100%;height:260px;display:block;overflow:visible}\n.radar-grid{fill:none;stroke:var(--radar-grid);stroke-width:1.1}.radar-axis{stroke:var(--radar-grid);stroke-width:1;opacity:.8}.radar-area{fill:var(--radar-fill);stroke:var(--radar);stroke-width:2.3;stroke-linejoin:round}.radar-dot{fill:#fffefa;stroke:var(--radar);stroke-width:2}.radar-label{font:19px \"KaiTi\",\"STKaiti\",\"Microsoft YaHei\",sans-serif;fill:var(--ink)}.radar-value{font:20px Georgia,\"Microsoft YaHei\",serif;fill:var(--ink)}.radar-scale{font:11px Georgia,serif;fill:var(--subtle)}.empty-attributes{padding:45px 10px;color:var(--subtle);font:18px/1.6 \"Microsoft YaHei\",sans-serif;text-align:center}.custom-attributes{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:5px 10px;margin:6px 0 0;font:15px/1.5 \"Microsoft YaHei\",sans-serif;color:var(--subtle)}.custom-attributes span{overflow-wrap:anywhere}.custom-attributes b{font-weight:500;color:var(--ink)}\n";
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-dew-bot",
3
3
  "description": "基于《无限恐怖》小说改编的 RPG 机器人。踏入主神空间,与伙伴迎接轮回试炼,书写属于你的无限冒险。",
4
- "version": "1.0.257",
4
+ "version": "1.0.259",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -18,7 +18,7 @@ export const characterCardStyles = `
18
18
  .character-sheet .companion-portrait:before{inset:5px;border-radius:12px;border-color:#fffdf1cf;box-shadow:0 0 0 1px color-mix(in srgb,var(--accent) 43%,transparent)}.character-sheet .companion-portrait:after{inset:2px;border-radius:15px;border-color:#fffefa9c}.character-sheet .companion-portrait .panel-corner{width:var(--portrait-ornament-size);height:var(--portrait-ornament-size)}
19
19
  .character-sheet .portrait-window{height:auto;border:2px solid var(--edge);border-radius:6px;overflow:hidden;background:var(--paper)}.character-artwork{display:block;width:100%;height:auto;object-fit:contain}.character-sheet .portrait-placeholder,.character-sheet .placeholder{height:100%;width:100%;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;color:var(--subtle);font:21px/1.5 "Microsoft YaHei",sans-serif}.character-sheet .placeholder small{font-size:16px}.character-sheet .portrait-window>.placeholder{height:448px}
20
20
  .character-stats{padding:17px 18px 15px;display:flex;flex-direction:column}.character-stats>.character-identity,.character-stats>.combat-row,.character-stats>.resource,.character-stats>.attribute-panel{position:relative;z-index:4}
21
- .character-identity{text-align:center;min-width:0}.title-image-slot{display:flex;align-items:center;justify-content:center;min-width:0;margin:9px 10px 0}.title-artwork{display:block;width:auto;height:auto;max-width:100%;max-height:110px;object-fit:contain}.title-artwork[alt="称号图片占位"]{max-width:244px;max-height:44px}
21
+ .character-identity{text-align:center;min-width:0}.title-image-slot{position:absolute;inset:12px;display:flex;align-items:center;justify-content:center;min-width:0;z-index:4;pointer-events:none}.title-artwork{display:block;width:auto;height:auto;max-width:100%;max-height:min(110px,100%);object-fit:contain;filter:drop-shadow(0 2px 3px #17213a70)}.title-artwork[alt="称号图片占位"]{max-width:min(244px,100%);max-height:min(44px,100%)}
22
22
  .character-name{font-size:43px;line-height:1.17;font-weight:700;letter-spacing:3px;margin:0 0 3px;overflow-wrap:anywhere;color:var(--ink);text-shadow:0 1px 0 #ffffff}.character-level{font:27px/1.25 Georgia,"Microsoft YaHei",serif;margin:0;color:var(--subtle);display:flex;align-items:baseline;justify-content:center;gap:7px;min-width:0}.character-level strong{font-size:28px;font-weight:500;overflow-wrap:anywhere;min-width:0}
23
23
  .gene-panel{position:relative;margin:8px auto 0;padding:4px 12px 4px 7px;display:flex;align-items:center;justify-content:center;gap:6px;width:fit-content;max-width:100%;border:1px solid color-mix(in srgb,var(--edge) 80%,#ffffff);border-radius:7px;background:linear-gradient(#ffffffd6,color-mix(in srgb,var(--paper) 75%,#ffffff));box-shadow:inset 0 0 0 2px #ffffffaa,0 1px 2px #54628210}
24
24
  .gene-panel:before,.gene-panel:after{content:"";position:absolute;top:50%;width:6px;height:6px;background:var(--light-edge);border:1px solid var(--edge);transform:translateY(-50%) rotate(45deg)}.gene-panel:before{left:-4px}.gene-panel:after{right:-4px}.gene-crest{flex:0 0 26px;height:29px;color:var(--accent)}.gene-crest svg{display:block;width:100%;height:100%}.gene-rank{font-size:18px;line-height:1.35;letter-spacing:1px;overflow-wrap:anywhere;font-weight:700;text-align:center;min-width:0;color:var(--ink)}
@@ -58,8 +58,8 @@ export function characterCardHtml(card: CharacterCardData, artwork?: string, _fr
58
58
  const experience = Math.max(0, Math.min(100, finite(card.experiencePercent)))
59
59
  const geneRank = card.geneLockRankName || '基因锁未开启'
60
60
  return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><style>${characterCardStyles}</style></head><body><main id="capture" class="companion-sheet character-adventure character-sheet gene-tier-${tier}" data-gene-lock-tier="${tier}" data-decoration-level="${tier}"><span class="character-ambient" aria-hidden="true">${characterFloralWatermark}</span><div class="character-card companion-main">
61
- <div class="portrait-column"><div class="companion-panel companion-portrait character-portrait">${corners(tier)}<div class="portrait-window portrait">${portrait ? `<img id="character-image" class="character-artwork" src="${cardEscape(portrait)}" data-fallback="${cardEscape(defaultArtwork ?? '')}" alt="${cardEscape(card.name)}的形象图">` : '<div class="placeholder"><span>形象尚未生成</span><small>角色形象</small></div>'}</div></div>
62
- <div class="title-image-slot character-title-below"><img id="title-image" class="title-artwork" src="${cardEscape(titleArtwork || characterTitlePlaceholder)}" data-fallback="${cardEscape(characterTitlePlaceholder)}" alt="${titleArtwork ? '称号图片' : '称号图片占位'}"></div></div>
61
+ <div class="portrait-column"><div class="companion-panel companion-portrait character-portrait">${corners(tier)}<div class="portrait-window portrait">${portrait ? `<img id="character-image" class="character-artwork" src="${cardEscape(portrait)}" data-fallback="${cardEscape(defaultArtwork ?? '')}" alt="${cardEscape(card.name)}的形象图">` : '<div class="placeholder"><span>形象尚未生成</span><small>角色形象</small></div>'}</div>
62
+ <div class="title-image-slot character-title-centered"><img id="title-image" class="title-artwork" src="${cardEscape(titleArtwork || characterTitlePlaceholder)}" data-fallback="${cardEscape(characterTitlePlaceholder)}" alt="${titleArtwork ? '称号图片' : '称号图片占位'}"></div></div></div>
63
63
  <section class="companion-panel companion-stats character-stats" aria-label="角色属性"><span class="character-paper-pattern" aria-hidden="true">${characterFloralWatermark}</span>${corners(tier, ['tr', 'br'])}<div class="identity character-identity"><h1 id="character-name" class="character-name" title="${cardEscape(card.name)}">${cardEscape(card.name)}</h1><p class="level character-level"><span>Lv.</span><strong id="character-level">${number(card.level)}</strong></p><div class="gene-panel"><span class="gene-crest" aria-hidden="true">${characterGeneCrest(tier)}</span><div id="gene-rank" class="gene-rank" title="${cardEscape(geneRank)}">${cardEscape(geneRank)}</div></div></div>
64
64
  <div class="combat-row companion-power-row"><span class="power-emblem power-icon" aria-hidden="true"><svg viewBox="0 0 42 42"><path d="m9 5 5 1 16 21-4 4L9 11Zm24 0-5 1L12 27l4 4L33 11ZM14 27l14 10m0-10L14 37M9 35l4-4m20 4-4-4"/></svg></span><span class="power-label companion-power-label">战力</span><strong id="combat-power" class="power-value companion-power">${number(card.power)}</strong></div>
65
65
  <div class="resource companion-resource hp-resource"><div class="resource-heading"><span class="resource-label"><svg viewBox="0 0 32 32" aria-hidden="true"><path d="M16 28C6 21 2 17 2 10a7 7 0 0 1 14-2 7 7 0 0 1 14 2c0 7-4 11-14 18Z"/></svg>生命值</span><span id="hp-numbers" class="resource-number">${number(card.hp)} / ${number(card.maxHp)}</span></div><div class="resource-track" role="progressbar" aria-label="生命值" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${hpPercent}"><span class="resource-fill fill" style="width:${hpPercent}%"></span><span class="bar-percent resource-percent">${number(hpPercent)}%</span></div></div>
@@ -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 one quoted, clickable item list. */
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
- if (items.length) sections.push(items.splice(0).map(line => '> ' + line).join(' \n'))
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()) {
@@ -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 dedicatedArt = !titleCard && !bloodlineCard && equipmentCard ? await loadSkillArtwork(equipmentCard.artworkUrl) : undefined
135
- const unknownArt = !titleCard && !bloodlineCard && equipmentCard && !dedicatedArt ? await equipmentFallback() : undefined
136
- const art = !titleCard && !bloodlineCard && equipmentCard
137
- ? dedicatedArt ?? unknownArt
138
- : await loadSkillArtwork(remoteAddress)
139
- const entryArtwork = await entryArtworkPromise
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] = await Promise.all([
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, await leaderboardHeader(), art)
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 = await ctx.puppeteer.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) {