koishi-plugin-dew-bot 1.0.260 → 1.0.262

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.d.ts CHANGED
@@ -3,7 +3,7 @@ export declare const inject: {
3
3
  optional: string[];
4
4
  };
5
5
  export declare const name = "dew-bot";
6
- export declare const usage: string;
6
+ export declare const filter = false;
7
7
  export interface Config {
8
8
  服务器地址: string;
9
9
  }
package/lib/index.js CHANGED
@@ -67,7 +67,7 @@ var serviceProto;
67
67
  var init_serviceProto = __esm({
68
68
  "src/shared/bot/serviceProto.ts"() {
69
69
  serviceProto = {
70
- "version": 37,
70
+ "version": 38,
71
71
  "services": [
72
72
  {
73
73
  "id": 0,
@@ -751,6 +751,14 @@ var init_serviceProto = __esm({
751
751
  }
752
752
  },
753
753
  "optional": true
754
+ },
755
+ {
756
+ "id": 14,
757
+ "name": "explorationRank",
758
+ "type": {
759
+ "type": "String"
760
+ },
761
+ "optional": true
754
762
  }
755
763
  ]
756
764
  },
@@ -2374,9 +2382,9 @@ var src_exports = {};
2374
2382
  __export(src_exports, {
2375
2383
  Config: () => Config,
2376
2384
  apply: () => apply2,
2385
+ filter: () => filter,
2377
2386
  inject: () => inject,
2378
- name: () => name,
2379
- usage: () => usage
2387
+ name: () => name
2380
2388
  });
2381
2389
  module.exports = __toCommonJS(src_exports);
2382
2390
  var import_koishi2 = require("koishi");
@@ -2660,15 +2668,17 @@ var characterCardStyles = `
2660
2668
  .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}
2661
2669
  .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)}
2662
2670
  .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}
2663
- .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}
2671
+ .character-stats{padding:17px 18px 15px;display:flex;flex-direction:column}.character-stats>.character-identity,.character-stats>.resource,.character-stats>.attribute-panel{position:relative;z-index:4}
2664
2672
  .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%)}
2665
2673
  .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}
2666
2674
  .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}
2667
2675
  .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)}
2668
- .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}
2676
+ .exploration-rank-row{display:flex;align-items:center;justify-content:center;gap:7px;margin:10px 0 0;font:16px/1.4 "KaiTi","STKaiti","Microsoft YaHei",sans-serif;letter-spacing:1px;color:var(--subtle)}.exploration-rank-row>svg{width:17px;height:17px;flex-shrink:0;fill:none;stroke:var(--accent);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.exploration-rank-row>span{white-space:nowrap}.exploration-rank-row strong{font-size:18px;font-weight:700;color:var(--ink);overflow-wrap:anywhere;min-width:0}
2677
+ .combat-row{display:flex;align-items:center;justify-content:center;gap:8px;margin:12px 0 0;padding:0 6px;min-width:0;font:28px/1.2 "KaiTi","STKaiti","Microsoft YaHei",sans-serif;position:relative;z-index:4}.power-icon{flex:0 0 34px;height:34px;display:grid;place-items:center;color:var(--accent)}.power-icon svg{display:block;width:34px;height:34px;overflow:visible}.power-label{font:inherit;white-space:nowrap}.power-value{min-width:0;flex:0 1 auto;text-align:center;overflow-wrap:anywhere;font:inherit;color:var(--accent);letter-spacing:0}
2669
2678
  .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}
2670
- .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))}
2671
- .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}
2679
+ .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{width:calc(100% - 20px);align-self:center;margin:0 0 14px}.exp-resource .resource-track{height:6px;margin-top:0;border-color:#d5c48b;border-radius:4px;background:#eee9d7;box-shadow:inset 0 1px 1px #9d82351a}.exp-resource .resource-fill{border-radius:3px;border-right:0;background:linear-gradient(#fff0a0,#e8bd48 60%,#d6a934)}
2680
+ .hp-resource{display:flex;align-items:center;margin-top:16px;margin-bottom:0}.hp-resource .resource-track{flex:1;min-width:0;height:20px;margin-top:0}.hp-bar-value{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:0 8px;white-space:nowrap;font:12px/1 "Microsoft YaHei",sans-serif;color:#fffef7;text-shadow:0 1px 2px #435443,0 0 2px #435443}
2681
+ .companion-attributes{margin-top:5px;min-width:0;flex:1;display:flex;flex-direction:column;justify-content:center}.attribute-divider{height:28px;margin:0 0 3px;display:flex;align-items:center;justify-content:center}.attribute-divider svg{display:block;width:100%;height:36px}.companion-radar{width:100%;height:260px;display:block;overflow:visible}
2672
2682
  .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)}
2673
2683
  `;
2674
2684
 
@@ -2721,6 +2731,12 @@ function characterGeneCrest(tier) {
2721
2731
  return `<svg viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="m30 4 20 10v20L30 55 10 34V14Z" fill="var(--paper)" stroke="currentColor" stroke-width="1.6"/><path d="m30 9 15 8v16L30 49 15 33V17Z" fill="none" stroke="var(--light-edge)" stroke-width="1"/><path d="M23 27v-6a7 7 0 0 1 14 0v6" fill="none" stroke="currentColor" stroke-width="2.5"/><rect x="20" y="26" width="20" height="15" rx="3" fill="currentColor"/><circle cx="30" cy="32" r="2" fill="var(--light-edge)"/><path d="M30 33v4" stroke="var(--light-edge)" stroke-width="1.8"/>${tier >= 3 ? '<path d="m30 2 3 4-3 4-3-4Z" fill="var(--gem)" stroke="var(--light-edge)"/>' : ""}${tier >= 5 ? '<path d="m18 12 1-7 6 4 5-7 5 7 6-4 1 7Z" fill="var(--accent)" stroke="var(--light-edge)" stroke-width=".7"/>' : ""}</svg>`;
2722
2732
  }
2723
2733
  __name(characterGeneCrest, "characterGeneCrest");
2734
+ var characterAttributeDivider = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 310 36">
2735
+ ${ornamentPaint}
2736
+ <g fill="none" stroke="var(--edge)" stroke-width=".8"><path d="M5 18h91m118 0h91"/><path d="M75 18c18 0 29-8 47-8m66 0c18 0 29 8 47 8"/><path d="M102 18c12 0 21 6 31 6m44 0c10 0 19-6 31-6"/></g>
2737
+ <g fill="url(#character-leaf)" stroke="var(--light-edge)" stroke-width=".6"><path d="M120 11c-10-1-14-6-16-10 10 0 17 3 16 10Zm14 12c-10 2-18-1-22-6 10-2 18-1 22 6Zm56-12c10-1 14-6 16-10-10 0-17 3-16 10Zm-14 12c10 2 18-1 22-6-10-2-18-1-22 6Z"/></g>
2738
+ ${flower(155, 18, 0.5)}<g fill="var(--accent)"><path d="m94 18 3-3 3 3-3 3Zm116 0 3-3 3 3-3 3Z"/></g>
2739
+ </svg>`;
2724
2740
 
2725
2741
  // src/plugin/characterCardTemplate.ts
2726
2742
  var finite = /* @__PURE__ */ __name((value) => Number.isFinite(value) ? value : 0, "finite");
@@ -2735,7 +2751,6 @@ var primaryAttributes = [
2735
2751
  ["resistance", "抗性"]
2736
2752
  ];
2737
2753
  var primaryIds = new Set(primaryAttributes.map(([id]) => id));
2738
- var heading = /* @__PURE__ */ __name((label) => `<h2 class="ornament-heading"><span class="heading-star" aria-hidden="true">✦</span>${cardEscape(label)}<span class="heading-star" aria-hidden="true">✦</span></h2>`, "heading");
2739
2754
  var corners = /* @__PURE__ */ __name((tier, positions = ["tl", "tr", "bl", "br"]) => positions.map((position) => `<span class="panel-corner corner-${position}" data-decoration-level="${tier}">${characterTierOrnament(tier)}</span>`).join(""), "corners");
2740
2755
  function radar(attributes) {
2741
2756
  if (!attributes.length) return '<p class="empty-attributes">暂无一级属性</p>';
@@ -2781,14 +2796,16 @@ function characterCardHtml(card, artwork, _frame, defaultArtwork, titleArtwork,
2781
2796
  const hpPercent = ratio(card.hp, card.maxHp) * 100;
2782
2797
  const experience = Math.max(0, Math.min(100, finite(card.experiencePercent)));
2783
2798
  const geneRank = card.geneLockRankName || "基因锁未开启";
2799
+ const explorationRank = card.explorationRank?.trim();
2784
2800
  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">
2785
2801
  <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>
2786
- <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>
2787
- <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>
2788
- <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>
2789
- <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>
2790
- <div class="resource companion-resource exp-resource"><div class="resource-heading"><span class="resource-label"><svg viewBox="0 0 32 32" aria-hidden="true"><path d="m16 2 4.5 9.1 10 1.4-7.3 7.1 1.7 10-8.9-4.7-8.9 4.7 1.7-10-7.3-7.1 10-1.4Z"/></svg>经验进度</span><span id="exp-number" class="resource-number">${number2(experience)}%</span></div><div class="resource-track" role="progressbar" aria-label="经验进度" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${experience}"><span class="resource-fill" style="width:${experience}%"></span><span class="resource-percent">${number2(experience)}%</span></div></div>
2791
- <section class="attribute-panel companion-attributes" aria-label="一级属性">${heading("一级属性")}${radar(card.attributes)}</section></section>
2802
+ ${titleArtwork || card.titleImageUrl ? `<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>
2803
+ <div class="combat-row companion-power-row"><span class="power-emblem power-icon" aria-hidden="true"><svg class="crossed-weapons" viewBox="0 0 42 42"><g fill="currentColor" stroke="var(--panel)" stroke-width=".9" stroke-linejoin="round"><g transform="rotate(-45 21 21)"><path d="M21 2 26 9v14l-5 4-5-4V9Z"/><path d="M12 26h18l-1 4H13Zm7 4h4v8h-4Zm-1 8h6v3h-6Z"/><path d="M21 7v15" fill="none" stroke="var(--light-edge)" stroke-width="1.1"/></g><g transform="rotate(45 21 21)"><path d="M21 2 26 9v14l-5 4-5-4V9Z"/><path d="M12 26h18l-1 4H13Zm7 4h4v8h-4Zm-1 8h6v3h-6Z"/><path d="M21 7v15" fill="none" stroke="var(--light-edge)" stroke-width="1.1"/></g></g></svg></span><span class="power-label companion-power-label">战力</span><strong id="combat-power" class="power-value companion-power">${number2(card.power)}</strong></div>
2804
+ </div>
2805
+ <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><p class="exploration-rank-row"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19.8 7.7A8 8 0 1 0 20 16M20 3v5h-5M15.8 12a3.8 3.8 0 1 1-3.8-3.8"/></svg><span>轮回等级</span> <strong id="exploration-rank">${cardEscape(explorationRank ? explorationRank + "级" : "—")}</strong></p></div>
2806
+ <div class="resource companion-resource hp-resource"><div class="resource-track" role="progressbar" aria-label="生命值" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${hpPercent}" aria-valuetext="${number2(card.hp)} / ${number2(card.maxHp)}"><span class="resource-fill fill" style="width:${hpPercent}%"></span><span id="hp-numbers" class="hp-bar-value">${number2(card.hp)} / ${number2(card.maxHp)}</span></div></div>
2807
+ <div class="resource companion-resource exp-resource"><div id="experience-track" class="resource-track" role="progressbar" aria-label="经验进度" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${experience}"><span class="resource-fill" style="width:${experience}%"></span></div></div>
2808
+ <section class="attribute-panel companion-attributes" aria-label="一级属性"><div class="attribute-divider" aria-hidden="true">${characterAttributeDivider}</div>${radar(card.attributes)}</section></section>
2792
2809
  </div></main></body></html>`;
2793
2810
  }
2794
2811
  __name(characterCardHtml, "characterCardHtml");
@@ -2854,7 +2871,7 @@ var primaryAttributes2 = [
2854
2871
  ];
2855
2872
  var primaryIds2 = new Set(primaryAttributes2.map(([id]) => id));
2856
2873
  var corners2 = /* @__PURE__ */ __name((positions = ["tl", "tr", "bl", "br"]) => positions.map((position) => `<span class="panel-corner corner-${position}">${companionCornerDecoration}</span>`).join(""), "corners");
2857
- var heading2 = /* @__PURE__ */ __name((title) => `<h2 class="ornament-heading"><span class="heading-star" aria-hidden="true">✦</span>${cardEscape(title)}<span class="heading-star" aria-hidden="true">✦</span></h2>`, "heading");
2874
+ var heading = /* @__PURE__ */ __name((title) => `<h2 class="ornament-heading"><span class="heading-star" aria-hidden="true">✦</span>${cardEscape(title)}<span class="heading-star" aria-hidden="true">✦</span></h2>`, "heading");
2858
2875
  var paw = '<svg viewBox="0 0 28 28" aria-hidden="true"><ellipse cx="5" cy="10" rx="3" ry="4"/><ellipse cx="11" cy="5" rx="3" ry="4"/><ellipse cx="18" cy="5" rx="3" ry="4"/><ellipse cx="24" cy="10" rx="3" ry="4"/><path d="M5 21c0-5 4-4 6-9 1-3 5-3 6 0 2 5 6 4 6 9 0 5-6 4-9 2-3 2-9 3-9-2Z"/></svg>';
2859
2876
  var crest = '<svg viewBox="0 0 32 32" aria-hidden="true" fill="currentColor"><path d="M15 28C5 24 3 16 4 7c8 0 11 9 11 21ZM17 28c10-4 12-12 11-21-8 0-11 9-11 21ZM16 18C9 13 10 7 16 2c6 5 7 11 0 16Z"/></svg>';
2860
2877
  var sword = '<svg viewBox="0 0 42 42" aria-hidden="true"><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>';
@@ -2898,13 +2915,13 @@ function radar2(card) {
2898
2915
  }
2899
2916
  __name(radar2, "radar");
2900
2917
  function bloodlines(card, icons) {
2901
- return `<section class="companion-panel bloodline-panel" aria-label="血统">${heading2("血统")}${card.bloodlines.length ? `<div class="bloodline-list">${card.bloodlines.map((bloodline, index) => `<div class="bloodline-row"><span class="bloodline-crest${icons?.bloodlines?.[index] ? " has-entry-artwork" : ""}" aria-hidden="true">${bloodline.emoji ? cardEscape(bloodline.emoji) : crest}${icons?.bloodlines?.[index] ? `<img class="bloodline-entry-artwork" data-entry-index="${index}" src="${cardEscape(icons.bloodlines[index])}" alt="">` : ""}</span><span class="bloodline-name">${cardEscape(bloodline.name)}</span><span class="bloodline-quality">${cardEscape(bloodline.quality)}</span></div>`).join("")}</div>` : '<p class="empty-list">暂无血统</p>'}</section>`;
2918
+ return `<section class="companion-panel bloodline-panel" aria-label="血统">${heading("血统")}${card.bloodlines.length ? `<div class="bloodline-list">${card.bloodlines.map((bloodline, index) => `<div class="bloodline-row"><span class="bloodline-crest${icons?.bloodlines?.[index] ? " has-entry-artwork" : ""}" aria-hidden="true">${bloodline.emoji ? cardEscape(bloodline.emoji) : crest}${icons?.bloodlines?.[index] ? `<img class="bloodline-entry-artwork" data-entry-index="${index}" src="${cardEscape(icons.bloodlines[index])}" alt="">` : ""}</span><span class="bloodline-name">${cardEscape(bloodline.name)}</span><span class="bloodline-quality">${cardEscape(bloodline.quality)}</span></div>`).join("")}</div>` : '<p class="empty-list">暂无血统</p>'}</section>`;
2902
2919
  }
2903
2920
  __name(bloodlines, "bloodlines");
2904
2921
  function skills(card, icons) {
2905
2922
  const active = '<path d="m8 24 16-16 2-3 4 4-3 2-16 16M7 21l9 9M7 31l5-5M5 33l2-2"/>';
2906
2923
  const passive = '<path d="M17 30V14M17 24C7 22 4 14 5 8c9 1 13 7 12 16ZM17 19c1-9 5-14 13-15 0 8-4 14-13 15Z"/>';
2907
- return `<section class="companion-panel companion-skills" aria-label="技能">${corners2()}${heading2("技能")}${card.skills.length ? `<div class="skill-grid">${card.skills.map((skill, index) => `<div class="companion-skill" data-skill-id="${cardEscape(skill.id)}"><span class="skill-icon" aria-hidden="true"><svg viewBox="0 0 36 36">${skill.type === "passive" ? passive : active}</svg>${icons?.skills?.[index] ? `<img class="skill-entry-artwork" data-entry-index="${index}" src="${cardEscape(icons.skills[index])}" alt="">` : ""}</span><span class="skill-name">${cardEscape(skill.name)}</span><span class="skill-level">Lv.${number3(skill.rank)}</span></div>`).join("")}</div>` : '<p class="empty-list">暂无技能</p>'}</section>`;
2924
+ return `<section class="companion-panel companion-skills" aria-label="技能">${corners2()}${heading("技能")}${card.skills.length ? `<div class="skill-grid">${card.skills.map((skill, index) => `<div class="companion-skill" data-skill-id="${cardEscape(skill.id)}"><span class="skill-icon" aria-hidden="true"><svg viewBox="0 0 36 36">${skill.type === "passive" ? passive : active}</svg>${icons?.skills?.[index] ? `<img class="skill-entry-artwork" data-entry-index="${index}" src="${cardEscape(icons.skills[index])}" alt="">` : ""}</span><span class="skill-name">${cardEscape(skill.name)}</span><span class="skill-level">Lv.${number3(skill.rank)}</span></div>`).join("")}</div>` : '<p class="empty-list">暂无技能</p>'}</section>`;
2908
2925
  }
2909
2926
  __name(skills, "skills");
2910
2927
  function companionCardHtml(card, artwork, icons) {
@@ -2920,7 +2937,7 @@ function companionCardHtml(card, artwork, icons) {
2920
2937
  <div class="companion-power-row"><span class="power-icon" aria-hidden="true">${sword}</span><span class="companion-power-label">战力</span><strong id="companion-power" class="companion-power">${number3(card.power)}</strong></div>
2921
2938
  <div class="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="companion-hp" class="resource-number">${number3(card.hp)} / ${number3(card.maxHp)}</span></div><div class="resource-track" role="progressbar" aria-label="生命值" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${healthPercent}"><span class="resource-fill" style="width:${healthPercent}%"></span><span class="resource-percent">${number3(healthPercent)}%</span></div></div>
2922
2939
  ${pet ? `<div class="companion-resource exp-resource"><div class="resource-heading"><span class="resource-label"><svg viewBox="0 0 32 32" aria-hidden="true"><path d="m16 2 4.5 9.1 10 1.4-7.3 7.1 1.7 10-8.9-4.7-8.9 4.7 1.7-10-7.3-7.1 10-1.4Z"/></svg>经验进度</span><span class="resource-number">${number3(experience)}%</span></div><div class="resource-track" role="progressbar" aria-label="经验进度" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${experience}"><span class="resource-fill" style="width:${experience}%"></span><span class="resource-percent">${number3(experience)}%</span></div></div>` : ""}
2923
- <section class="companion-attributes" aria-label="一级属性">${heading2("一级属性")}${radar2(card)}</section></section>
2940
+ <section class="companion-attributes" aria-label="一级属性">${heading("一级属性")}${radar2(card)}</section></section>
2924
2941
  </div>${skills(card, icons)}</main></body></html>`;
2925
2942
  }
2926
2943
  __name(companionCardHtml, "companionCardHtml");
@@ -3419,15 +3436,13 @@ function radar4(card) {
3419
3436
  const attribute = card.attributes.find((value) => value.id === id);
3420
3437
  return { name: attribute?.name?.trim() || name2, value: finite4(attribute?.value ?? 0) };
3421
3438
  });
3422
- const max = Math.max(1, ...attributes.map((attribute) => Math.max(0, attribute.value)));
3423
- const magnitude = 10 ** Math.floor(Math.log10(max));
3424
- const ceiling = Math.ceil(max / magnitude) * magnitude;
3439
+ const ceiling = Math.max(0, ...attributes.map((attribute) => attribute.value));
3425
3440
  const point = /* @__PURE__ */ __name((index, radius) => {
3426
3441
  const angle = -Math.PI / 2 + index * Math.PI / 3;
3427
3442
  return [320 + Math.cos(angle) * radius, 190 + Math.sin(angle) * radius];
3428
3443
  }, "point");
3429
3444
  const polygon = /* @__PURE__ */ __name((radius) => attributes.map((_, index) => point(index, radius).join(",")).join(" "), "polygon");
3430
- const data = attributes.map((attribute, index) => point(index, 122 * Math.max(0, Math.min(1, attribute.value / ceiling))));
3445
+ const data = attributes.map((attribute, index) => point(index, 122 * (ceiling > 0 ? Math.max(0, attribute.value / ceiling) : 0)));
3431
3446
  return `<div class="radar-panel"><svg class="bloodline-radar" viewBox="0 0 640 396" role="img" aria-label="血统六维属性加成雷达图" xmlns="http://www.w3.org/2000/svg">
3432
3447
  <defs><linearGradient id="bloodline-radar-fill" x1="0" y1="0" x2="1" y2="1"><stop stop-color="var(--quality-accent)" stop-opacity=".35"/><stop offset="1" stop-color="var(--quality-accent)" stop-opacity=".12"/></linearGradient></defs>
3433
3448
  ${[0.2, 0.4, 0.6, 0.8, 1].map((scale) => `<polygon points="${polygon(122 * scale)}" fill="none" stroke="#b9bdad" stroke-width="1.2"/>`).join("")}
@@ -3945,6 +3960,52 @@ function renderQQTitle(content) {
3945
3960
  }
3946
3961
  __name(renderQQTitle, "renderQQTitle");
3947
3962
 
3963
+ // src/plugin/serverHandel/qqBattleLogLinks.ts
3964
+ function battleLogLinkSpans(content) {
3965
+ const links = [];
3966
+ const pattern = /\[([^\[\]\r\n]+)\]\((https?:\/\/[^\s<>()]+)\)/g;
3967
+ for (const match of content.matchAll(pattern)) {
3968
+ try {
3969
+ const url = new URL(match[2]);
3970
+ const id = url.searchParams.get("id") ?? "";
3971
+ if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.hash || url.pathname !== "/battlelog" || [...url.searchParams].length !== 1 || !/^[1-9]\d{0,15}$/.test(id) || !Number.isSafeInteger(Number(id))) continue;
3972
+ links.push({ start: match.index, end: match.index + match[0].length, label: match[1], url: url.href, id });
3973
+ } catch {
3974
+ }
3975
+ }
3976
+ return links;
3977
+ }
3978
+ __name(battleLogLinkSpans, "battleLogLinkSpans");
3979
+ function renderBattleLogLink(link) {
3980
+ return "[" + escapeMarkdown(link.label) + "](" + link.url + ")";
3981
+ }
3982
+ __name(renderBattleLogLink, "renderBattleLogLink");
3983
+ function renderBattleLogLinkText(content) {
3984
+ const parts = [];
3985
+ let offset = 0;
3986
+ for (const link of battleLogLinkSpans(content)) {
3987
+ parts.push(escapeMarkdown(content.slice(offset, link.start)), renderBattleLogLink(link));
3988
+ offset = link.end;
3989
+ }
3990
+ parts.push(escapeMarkdown(content.slice(offset)));
3991
+ return parts.join("");
3992
+ }
3993
+ __name(renderBattleLogLinkText, "renderBattleLogLinkText");
3994
+ function battleLogMessageLinks(data) {
3995
+ const links = /* @__PURE__ */ new Map();
3996
+ for (const line of data.line) {
3997
+ if (line.type !== 1 /* line */ || line.codeBlock || line.mailPart === "body") continue;
3998
+ for (const link of battleLogLinkSpans(line.content)) if (!links.has(link.url)) links.set(link.url, link);
3999
+ }
4000
+ return [...links.values()];
4001
+ }
4002
+ __name(battleLogMessageLinks, "battleLogMessageLinks");
4003
+ function appendMissingBattleLogLinks(markdown, data) {
4004
+ const missing = battleLogMessageLinks(data).map(renderBattleLogLink).filter((link) => !markdown.includes(link));
4005
+ return [markdown, ...missing].filter(Boolean).join("\n\n");
4006
+ }
4007
+ __name(appendMissingBattleLogLinks, "appendMissingBattleLogLinks");
4008
+
3948
4009
  // src/plugin/serverHandel/qqRewardMessage.ts
3949
4010
  function hasRewardLines(data) {
3950
4011
  return data.line.some((item) => item.type === 1 /* line */ && item.reward === true);
@@ -4005,9 +4066,9 @@ function renderQQMailMarkdown(data) {
4005
4066
  }
4006
4067
  __name(renderQQMailMarkdown, "renderQQMailMarkdown");
4007
4068
  function renderQQRewardMarkdown(data, showGreeting) {
4008
- if (data.mail) return renderQQMailMarkdown(data);
4069
+ if (data.mail) return appendMissingBattleLogLinks(renderQQMailMarkdown(data), data);
4009
4070
  const rewards = data.line.filter((item) => item.type === 1 /* line */ && item.reward).map((item) => renderRewardLine(item.content)).filter(Boolean);
4010
- if (rewards.length && !data.mail) return "🎁获得奖励\n\n" + rewards.join(" \n");
4071
+ if (rewards.length && !data.mail) return appendMissingBattleLogLinks("🎁获得奖励\n\n" + rewards.join(" \n"), data);
4011
4072
  const sections = [];
4012
4073
  const bodyLines = [];
4013
4074
  const flushBody = /* @__PURE__ */ __name(() => {
@@ -4022,7 +4083,7 @@ function renderQQRewardMarkdown(data, showGreeting) {
4022
4083
  } else if (item.type === 1 /* line */) {
4023
4084
  for (const line of item.content.replace(/\r\n?/g, "\n").split("\n")) {
4024
4085
  if (!line.trim()) flushBody();
4025
- else bodyLines.push(escapeMarkdown(line));
4086
+ else bodyLines.push(renderBattleLogLinkText(line));
4026
4087
  }
4027
4088
  } else if (item.type === 2 /* error */) {
4028
4089
  bodyLines.push(`❌ ${escapeMarkdown(item.content)}`);
@@ -4030,10 +4091,46 @@ function renderQQRewardMarkdown(data, showGreeting) {
4030
4091
  }
4031
4092
  flushBody();
4032
4093
  if (rewards.length) sections.push("🎁获得奖励\n\n" + rewards.join(" \n"));
4033
- return sections.join("\n\n") || " ";
4094
+ return appendMissingBattleLogLinks(sections.join("\n\n") || " ", data);
4034
4095
  }
4035
4096
  __name(renderQQRewardMarkdown, "renderQQRewardMarkdown");
4036
4097
 
4098
+ // src/shared/bot/inlineCommands.ts
4099
+ function inlineCommandSpans(content) {
4100
+ const spans = [];
4101
+ let depth = 0, start = 0, labelStart = 0;
4102
+ for (let index = 0; index < content.length; index++) {
4103
+ if (content[index] === "[") {
4104
+ if (depth === 0) {
4105
+ start = index > 0 && content[index - 1] === "\\" ? index - 1 : index;
4106
+ labelStart = index + 1;
4107
+ }
4108
+ depth++;
4109
+ } else if (content[index] === "]" && depth > 0 && --depth === 0) {
4110
+ const label = content.slice(labelStart, index).replace(/\\$/, "");
4111
+ if (!label.trim() || /[\[\]\r\n]/.test(label) || /^\d+$/.test(label.trim())) continue;
4112
+ const alias = /^\(command:([^()[\]\r\n]+)\)/.exec(content.slice(index + 1));
4113
+ const command = alias ? alias[1].trim() : label.split(/[++]/, 1)[0].trim().replace(/^🔄\s*/u, "");
4114
+ if (!command) continue;
4115
+ const end = index + 1 + (alias?.[0].length ?? 0);
4116
+ spans.push({ start, end, label, command, ...alias ? { aliased: true } : {} });
4117
+ if (alias) index = end - 1;
4118
+ }
4119
+ }
4120
+ return spans;
4121
+ }
4122
+ __name(inlineCommandSpans, "inlineCommandSpans");
4123
+ function renderPlainInlineCommandAliases(content) {
4124
+ let result = "", offset = 0;
4125
+ for (const span of inlineCommandSpans(content)) {
4126
+ if (!span.aliased) continue;
4127
+ result += content.slice(offset, span.start) + (span.label.trim() === span.command ? `[${span.command}]` : `${span.label}[${span.command}]`);
4128
+ offset = span.end;
4129
+ }
4130
+ return result + content.slice(offset);
4131
+ }
4132
+ __name(renderPlainInlineCommandAliases, "renderPlainInlineCommandAliases");
4133
+
4037
4134
  // src/plugin/serverHandel/qqPageMessage.ts
4038
4135
  function compactListLabel(value) {
4039
4136
  const label = compactDisplayText(value);
@@ -4053,36 +4150,25 @@ function inlineCommandUrl(command) {
4053
4150
  return `mqqapi://aio/inlinecmd?command=${encoded}&enter=true&reply=false`;
4054
4151
  }
4055
4152
  __name(inlineCommandUrl, "inlineCommandUrl");
4056
- function inlineCommandSpans(content) {
4057
- const spans = [];
4058
- let depth = 0, start = 0, labelStart = 0;
4059
- for (let index = 0; index < content.length; index++) {
4060
- if (content[index] === "[") {
4061
- if (depth === 0) {
4062
- start = index > 0 && content[index - 1] === "\\" ? index - 1 : index;
4063
- labelStart = index + 1;
4064
- }
4065
- depth++;
4066
- } else if (content[index] === "]" && depth > 0 && --depth === 0) {
4067
- const label = content.slice(labelStart, index).replace(/\\$/, "");
4068
- if (!label.trim() || /[\[\]\r\n]/.test(label) || /^\d+$/.test(label.trim())) continue;
4069
- const command = label.split(/[++]/, 1)[0].trim().replace(/^🔄\s*/u, "");
4070
- if (command) spans.push({ start, end: index + 1, label, command });
4071
- }
4072
- }
4073
- return spans;
4153
+ function inlineCommandSpans2(content) {
4154
+ const spans = inlineCommandSpans(content);
4155
+ const logLinks = battleLogLinkSpans(content);
4156
+ return spans.filter((span) => !logLinks.some((link) => span.start < link.end && span.end > link.start));
4074
4157
  }
4075
- __name(inlineCommandSpans, "inlineCommandSpans");
4158
+ __name(inlineCommandSpans2, "inlineCommandSpans");
4076
4159
  function hasInlineCommands(content) {
4077
- return inlineCommandSpans(content).length > 0;
4160
+ return inlineCommandSpans2(content).length > 0;
4078
4161
  }
4079
4162
  __name(hasInlineCommands, "hasInlineCommands");
4080
4163
  function renderInlineCommands(content) {
4081
4164
  const parts = [];
4082
4165
  let offset = 0;
4083
- for (const span of inlineCommandSpans(content)) {
4084
- parts.push(escapeMarkdown(content.slice(offset, span.start)));
4085
- parts.push("[" + escapeMarkdown(span.label) + "](" + inlineCommandUrl(span.command) + ")");
4166
+ const spans = [
4167
+ ...inlineCommandSpans2(content).map((span) => ({ ...span, rendered: "[" + escapeMarkdown(span.label) + "](" + inlineCommandUrl(span.command) + ")" })),
4168
+ ...battleLogLinkSpans(content).map((span) => ({ ...span, rendered: renderBattleLogLink(span) }))
4169
+ ].sort((a, b) => a.start - b.start);
4170
+ for (const span of spans) {
4171
+ parts.push(escapeMarkdown(content.slice(offset, span.start)), span.rendered);
4086
4172
  offset = span.end;
4087
4173
  }
4088
4174
  parts.push(escapeMarkdown(content.slice(offset)));
@@ -4381,7 +4467,8 @@ var AnalysisMsg = class {
4381
4467
  case 1 /* line */:
4382
4468
  const labeledRow = labeledEntries.get(lineIndex);
4383
4469
  const content = labeledRow ? labeledRow.map((entry) => entry.label + (entry.description ? " · " + entry.description : "") + ` [${entry.viewCommand}]`).join("") : withCommandHint(item.content, commands.get(lineIndex));
4384
- msg += `${content}
4470
+ const plainContent = item.codeBlock || item.mailPart === "body" || labeledRow ? content : renderPlainInlineCommandAliases(content);
4471
+ msg += `${plainContent}
4385
4472
  `;
4386
4473
  break;
4387
4474
  case 2 /* error */:
@@ -4407,12 +4494,12 @@ function renderQQCommandMarkdown(data, showGreeting) {
4407
4494
  return renderInlineCommands(onlyLine.content);
4408
4495
  }
4409
4496
  const title = data.line.find((item) => item.type === 0 /* title */)?.content;
4410
- const heading3 = title === void 0 ? "**🔔** 系统提示" : `**${escapeMarkdown(title)}**`;
4497
+ const heading2 = title === void 0 ? "**🔔** 系统提示" : `**${escapeMarkdown(title)}**`;
4411
4498
  const body = data.line.filter((item) => item.type !== 0 /* title */).flatMap((item) => {
4412
4499
  const content = item.type === 2 /* error */ ? `❌ ${item.content}` : item.content;
4413
4500
  return content.replace(/\r\n?/g, "\n").split("\n").map((line) => "> " + renderInlineCommands(line));
4414
4501
  }).join(" \n");
4415
- return heading3 + (body ? "\n\n" + body : "");
4502
+ return heading2 + (body ? "\n\n" + body : "");
4416
4503
  }
4417
4504
  const rewardMessage = isRewardMessage(data);
4418
4505
  if (!data.commandHelp && !data.pageNav && data.line.some((item) => item.type === 1 /* line */ && !item.reward && /^\[\d+\]\s*[^\r\n]+$/.test(item.content))) {
@@ -4444,8 +4531,10 @@ function renderQQCommandMarkdown(data, showGreeting) {
4444
4531
  break;
4445
4532
  }
4446
4533
  flushRewards();
4447
- if (!item.content.trim()) flushBody();
4448
- else bodyLines.push(renderInlineCommands(item.content));
4534
+ for (const line of item.content.replace(/\r\n?/g, "\n").split("\n")) {
4535
+ if (!line.trim()) flushBody();
4536
+ else bodyLines.push(renderInlineCommands(line));
4537
+ }
4449
4538
  break;
4450
4539
  case 2 /* error */:
4451
4540
  flushBody();
@@ -4592,14 +4681,14 @@ function renderQQStoryMarkdown(data, showGreeting) {
4592
4681
  } else {
4593
4682
  for (const line of item.content.replace(/\r\n?/g, "\n").split("\n")) {
4594
4683
  if (!line.trim()) flushBody();
4595
- else bodyLines.push(data.reward ? escapeMarkdown(line) : renderInlineCommands(line));
4684
+ else bodyLines.push(data.reward ? renderBattleLogLinkText(line) : renderInlineCommands(line));
4596
4685
  }
4597
4686
  }
4598
4687
  break;
4599
4688
  case 2 /* error */:
4600
4689
  flushBody();
4601
4690
  if (!data.reward) flushRewards();
4602
- sections.push("❌ 出错了!错误原因:" + (data.reward ? escapeMarkdown(item.content) : renderInlineCommands(item.content)) + ",请反馈至联系开发者");
4691
+ sections.push("❌ 出错了!错误原因:" + (data.reward ? renderBattleLogLinkText(item.content) : renderInlineCommands(item.content)) + ",请反馈至联系开发者");
4603
4692
  break;
4604
4693
  }
4605
4694
  }
@@ -4619,7 +4708,7 @@ function renderQQMessagePreview(data, showGreeting = false) {
4619
4708
  if (data.transaction) return { kind: "transaction", markdown: renderQQTransactionMarkdown(data, showGreeting), text };
4620
4709
  if (data.pageNav) return { kind: "page", markdown: renderQQPageMarkdown(data, showGreeting), text };
4621
4710
  if (isRewardMessage(data)) return { kind: "reward", markdown: renderQQRewardMarkdown(data, showGreeting), text };
4622
- if (data.line.some((item) => (item.type === 1 /* line */ || item.type === 2 /* error */) && hasInlineCommands(item.content))) {
4711
+ if (battleLogMessageLinks(data).length || data.line.some((item) => (item.type === 1 /* line */ || item.type === 2 /* error */) && hasInlineCommands(item.content))) {
4623
4712
  return { kind: "command", markdown: renderQQCommandMarkdown(data, showGreeting), text };
4624
4713
  }
4625
4714
  return { kind: "text", text };
@@ -4814,10 +4903,7 @@ __name(apply, "apply");
4814
4903
  var import_node_path2 = require("node:path");
4815
4904
  var inject = { optional: ["puppeteer", "console"] };
4816
4905
  var name = "dew-bot";
4817
- var usage = `
4818
- ## 🌀 达尔文进化岛 · 文字RPG机器人
4819
- **一款基于《无限恐怖》的文字RPG**
4820
- `;
4906
+ var filter = false;
4821
4907
  var Config = import_koishi2.Schema.object({
4822
4908
  服务器地址: import_koishi2.Schema.string().default("ws://dew-v7-test:3021").description("DEW 游戏服")
4823
4909
  });
@@ -4836,7 +4922,7 @@ __name(apply2, "apply");
4836
4922
  0 && (module.exports = {
4837
4923
  Config,
4838
4924
  apply,
4925
+ filter,
4839
4926
  inject,
4840
- name,
4841
- usage
4927
+ name
4842
4928
  });
@@ -1,4 +1,4 @@
1
- /** The unavailable title remains an independent image rather than a text badge. */
1
+ /** Requested but unavailable title artwork has an independent image fallback. */
2
2
  export declare const characterTitlePlaceholder: string;
3
3
  /** Each tier adds finer foliage, white petal layers and metallic vinework. */
4
4
  export declare function characterTierOrnament(tier: number): string;
@@ -6,3 +6,5 @@ export declare function characterTierOrnament(tier: number): string;
6
6
  export declare const characterFloralWatermark = "<svg class=\"floral-watermark\" viewBox=\"0 0 760 880\" preserveAspectRatio=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <g fill=\"currentColor\">\n <path d=\"M-9 138C39 105 55 49 78-10c-8 64-37 121-87 148ZM17 107C-5 94-8 61-4 38c21 23 36 43 21 69ZM44 60C32 29 40 10 57-12c9 30 9 51-13 72ZM63 24c23-16 49-13 65-2-23 11-46 16-65 2Z\"/>\n <path d=\"M719 787c-2-52 10-98 42-137-9 66-21 110-42 137Zm12-66c-21-16-17-40-19-65 19 20 27 40 19 65Zm10-28c14-26 36-35 56-34-17 18-35 34-56 34ZM2 780c8-17 33-27 51-20-19 19-34 25-51 20Zm34-20c9-24 29-35 47-32-10 22-27 36-47 32Z\"/>\n <g transform=\"translate(58 839) scale(1.5)\"><path d=\"M0 0c-31-14-41-45-23-61 27-7 32 37 23 61Z\"/><path d=\"M0 0c3-31 19-59 44-49 20 19-13 43-44 49Z\"/><path d=\"M0 0c30-10 56-6 55 18-14 25-40 2-55-18Z\"/><path d=\"M0 0c20 20 22 49 0 53-23-7-12-35 0-53Z\"/><path d=\"M0 0c-23 16-51 8-48-15 11-17 35-2 48 15Z\"/></g>\n <g transform=\"translate(696 71) scale(1.4)\"><path d=\"M0 0c-31-14-41-45-23-61 27-7 32 37 23 61Z\"/><path d=\"M0 0c3-31 19-59 44-49 20 19-13 43-44 49Z\"/><path d=\"M0 0c30-10 56-6 55 18-14 25-40 2-55-18Z\"/><path d=\"M0 0c20 20 22 49 0 53-23-7-12-35 0-53Z\"/><path d=\"M0 0c-23 16-51 8-48-15 11-17 35-2 48 15Z\"/></g>\n <g transform=\"translate(63 425) scale(.9)\"><path d=\"M0 0c-31-14-41-45-23-61 27-7 32 37 23 61Z\"/><path d=\"M0 0c3-31 19-59 44-49 20 19-13 43-44 49Z\"/><path d=\"M0 0c30-10 56-6 55 18-14 25-40 2-55-18Z\"/><path d=\"M0 0c20 20 22 49 0 53-23-7-12-35 0-53Z\"/><path d=\"M0 0c-23 16-51 8-48-15 11-17 35-2 48 15Z\"/></g>\n </g>\n <g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M-4 191C19 101 61 150 86 46M-8 803c84-50 100-82 93-147M705 868c-5-90 24-171 74-226M660-9c55 69 50 129 106 165\"/><path d=\"M85 45c17 2 20 27 7 30-9 1-13-14-5-16M85 674c-24 10-27 43-8 45 10-2 6-17-1-14\"/></g>\n</svg>";
7
7
  /** The name of the configured gene-lock rank stays separate from this small crest. */
8
8
  export declare function characterGeneCrest(tier: number): string;
9
+ /** Quiet botanical divider above the six-axis radar; carries no visible label. */
10
+ export declare const characterAttributeDivider: string;
@@ -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{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";
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>.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.exploration-rank-row{display:flex;align-items:center;justify-content:center;gap:7px;margin:10px 0 0;font:16px/1.4 \"KaiTi\",\"STKaiti\",\"Microsoft YaHei\",sans-serif;letter-spacing:1px;color:var(--subtle)}.exploration-rank-row>svg{width:17px;height:17px;flex-shrink:0;fill:none;stroke:var(--accent);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.exploration-rank-row>span{white-space:nowrap}.exploration-rank-row strong{font-size:18px;font-weight:700;color:var(--ink);overflow-wrap:anywhere;min-width:0}\n.combat-row{display:flex;align-items:center;justify-content:center;gap:8px;margin:12px 0 0;padding:0 6px;min-width:0;font:28px/1.2 \"KaiTi\",\"STKaiti\",\"Microsoft YaHei\",sans-serif;position:relative;z-index:4}.power-icon{flex:0 0 34px;height:34px;display:grid;place-items:center;color:var(--accent)}.power-icon svg{display:block;width:34px;height:34px;overflow:visible}.power-label{font:inherit;white-space:nowrap}.power-value{min-width:0;flex:0 1 auto;text-align:center;overflow-wrap:anywhere;font:inherit;color:var(--accent);letter-spacing:0}\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{width:calc(100% - 20px);align-self:center;margin:0 0 14px}.exp-resource .resource-track{height:6px;margin-top:0;border-color:#d5c48b;border-radius:4px;background:#eee9d7;box-shadow:inset 0 1px 1px #9d82351a}.exp-resource .resource-fill{border-radius:3px;border-right:0;background:linear-gradient(#fff0a0,#e8bd48 60%,#d6a934)}\n.hp-resource{display:flex;align-items:center;margin-top:16px;margin-bottom:0}.hp-resource .resource-track{flex:1;min-width:0;height:20px;margin-top:0}.hp-bar-value{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:0 8px;white-space:nowrap;font:12px/1 \"Microsoft YaHei\",sans-serif;color:#fffef7;text-shadow:0 1px 2px #435443,0 0 2px #435443}\n.companion-attributes{margin-top:5px;min-width:0;flex:1;display:flex;flex-direction:column;justify-content:center}.attribute-divider{height:28px;margin:0 0 3px;display:flex;align-items:center;justify-content:center}.attribute-divider svg{display:block;width:100%;height:36px}.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";
@@ -0,0 +1,16 @@
1
+ import { type IMessage } from '../../shared/face';
2
+ export type BattleLogLinkSpan = {
3
+ start: number;
4
+ end: number;
5
+ label: string;
6
+ url: string;
7
+ id: string;
8
+ };
9
+ /** Only the dedicated battle-log route can carry server-supplied external links. */
10
+ export declare function battleLogLinkSpans(content: string): BattleLogLinkSpan[];
11
+ export declare function renderBattleLogLink(link: BattleLogLinkSpan): string;
12
+ /** Keep every other Markdown token literal while preserving verified log links. */
13
+ export declare function renderBattleLogLinkText(content: string): string;
14
+ export declare function battleLogMessageLinks(data: Pick<IMessage, 'line'>): BattleLogLinkSpan[];
15
+ /** Compact reward cards omit ordinary details, so add their log links explicitly. */
16
+ export declare function appendMissingBattleLogLinks(markdown: string, data: Pick<IMessage, 'line'>): string;
@@ -0,0 +1,11 @@
1
+ export type InlineCommandSpan = {
2
+ start: number;
3
+ end: number;
4
+ label: string;
5
+ command: string;
6
+ aliased?: true;
7
+ };
8
+ /** Boxed commands may use [label](command:input) to show a different action label. */
9
+ export declare function inlineCommandSpans(content: string): InlineCommandSpan[];
10
+ /** Keep aliased actions usable when the platform falls back to ordinary text. */
11
+ export declare function renderPlainInlineCommandAliases(content: string): string;
@@ -57,7 +57,7 @@ export interface CharacterCardData {
57
57
  }>;
58
58
  /** 当前基因锁大阶的配置名称,不包含层级;未开启时为基因锁未开启。 */
59
59
  geneLockRankName?: string;
60
- /** 当前称号的图片链接;未获得称号或图片不可用时省略,客户端显示图片占位。 */
60
+ /** 当前称号的图片链接;未获得称号或图片不可用时省略,客户端不显示称号横幅。 */
61
61
  titleImageUrl?: string;
62
62
  /** 已获得的真实基因锁大阶(0~5);0 表示尚未解锁。 */
63
63
  geneLockTier?: number;
@@ -78,6 +78,8 @@ export interface CharacterCardData {
78
78
  type?: CompanionSkillType;
79
79
  artworkUrl?: string;
80
80
  }>;
81
+ /** 玩家当前的轮回探索品阶(F~SSS),与角色等级、基因锁阶级分开显示。 */
82
+ explorationRank?: string;
81
83
  }
82
84
  export declare enum CompanionCardKind {
83
85
  pet = "pet",
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.260",
4
+ "version": "1.0.262",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/src/index.ts CHANGED
@@ -6,11 +6,7 @@ import type {} from '@koishijs/plugin-console'
6
6
  export const inject = { optional: ['puppeteer', 'console'] }
7
7
 
8
8
  export const name = 'dew-bot'
9
-
10
- export const usage: string = `
11
- ## 🌀 达尔文进化岛 · 文字RPG机器人
12
- **一款基于《无限恐怖》的文字RPG**
13
- `
9
+ export const filter = false
14
10
 
15
11
  export interface Config {
16
12
  服务器地址: string
@@ -19,15 +19,13 @@ function radar(card: BloodlineCardData): string {
19
19
  const attribute = card.attributes.find(value => value.id === id)
20
20
  return { name: attribute?.name?.trim() || name, value: finite(attribute?.value ?? 0) }
21
21
  })
22
- const max = Math.max(1, ...attributes.map(attribute => Math.max(0, attribute.value)))
23
- const magnitude = 10 ** Math.floor(Math.log10(max))
24
- const ceiling = Math.ceil(max / magnitude) * magnitude
22
+ const ceiling = Math.max(0, ...attributes.map(attribute => attribute.value))
25
23
  const point = (index: number, radius: number) => {
26
24
  const angle = -Math.PI / 2 + index * Math.PI / 3
27
25
  return [320 + Math.cos(angle) * radius, 190 + Math.sin(angle) * radius]
28
26
  }
29
27
  const polygon = (radius: number) => attributes.map((_, index) => point(index, radius).join(',')).join(' ')
30
- const data = attributes.map((attribute, index) => point(index, 122 * Math.max(0, Math.min(1, attribute.value / ceiling))))
28
+ const data = attributes.map((attribute, index) => point(index, 122 * (ceiling > 0 ? Math.max(0, attribute.value / ceiling) : 0)))
31
29
  return `<div class="radar-panel"><svg class="bloodline-radar" viewBox="0 0 640 396" role="img" aria-label="血统六维属性加成雷达图" xmlns="http://www.w3.org/2000/svg">
32
30
  <defs><linearGradient id="bloodline-radar-fill" x1="0" y1="0" x2="1" y2="1"><stop stop-color="var(--quality-accent)" stop-opacity=".35"/><stop offset="1" stop-color="var(--quality-accent)" stop-opacity=".12"/></linearGradient></defs>
33
31
  ${[.2, .4, .6, .8, 1].map(scale => `<polygon points="${polygon(122 * scale)}" fill="none" stroke="#b9bdad" stroke-width="1.2"/>`).join('')}
@@ -26,7 +26,7 @@ export async function layoutCard(document: Document = globalThis.document) {
26
26
  portrait.replaceWith(placeholder)
27
27
  }
28
28
  }
29
- // Title artwork is independent of the frame and always has its own placeholder.
29
+ // Requested title artwork has an independent fallback; untitled cards have no title element.
30
30
  const titleArtwork = document.querySelector<HTMLImageElement>('.title-artwork')
31
31
  if (titleArtwork) {
32
32
  try { await titleArtwork.decode() } catch {
@@ -1,4 +1,4 @@
1
- /** The unavailable title remains an independent image rather than a text badge. */
1
+ /** Requested but unavailable title artwork has an independent image fallback. */
2
2
  export const characterTitlePlaceholder = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="420" height="84" viewBox="0 0 420 84">
3
3
  <defs>
4
4
  <linearGradient id="metal" x2="0" y2="1"><stop stop-color="#fffef4"/><stop offset=".3" stop-color="#bbb6a2"/><stop offset=".52" stop-color="#fffdf0"/><stop offset="1" stop-color="#9ba6bc"/></linearGradient>
@@ -53,3 +53,11 @@ export const characterFloralWatermark = `<svg class="floral-watermark" viewBox="
53
53
  export function characterGeneCrest(tier: number): string {
54
54
  return `<svg viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"><path d="m30 4 20 10v20L30 55 10 34V14Z" fill="var(--paper)" stroke="currentColor" stroke-width="1.6"/><path d="m30 9 15 8v16L30 49 15 33V17Z" fill="none" stroke="var(--light-edge)" stroke-width="1"/><path d="M23 27v-6a7 7 0 0 1 14 0v6" fill="none" stroke="currentColor" stroke-width="2.5"/><rect x="20" y="26" width="20" height="15" rx="3" fill="currentColor"/><circle cx="30" cy="32" r="2" fill="var(--light-edge)"/><path d="M30 33v4" stroke="var(--light-edge)" stroke-width="1.8"/>${tier >= 3 ? '<path d="m30 2 3 4-3 4-3-4Z" fill="var(--gem)" stroke="var(--light-edge)"/>' : ''}${tier >= 5 ? '<path d="m18 12 1-7 6 4 5-7 5 7 6-4 1 7Z" fill="var(--accent)" stroke="var(--light-edge)" stroke-width=".7"/>' : ''}</svg>`
55
55
  }
56
+
57
+ /** Quiet botanical divider above the six-axis radar; carries no visible label. */
58
+ export const characterAttributeDivider = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 310 36">
59
+ ${ornamentPaint}
60
+ <g fill="none" stroke="var(--edge)" stroke-width=".8"><path d="M5 18h91m118 0h91"/><path d="M75 18c18 0 29-8 47-8m66 0c18 0 29 8 47 8"/><path d="M102 18c12 0 21 6 31 6m44 0c10 0 19-6 31-6"/></g>
61
+ <g fill="url(#character-leaf)" stroke="var(--light-edge)" stroke-width=".6"><path d="M120 11c-10-1-14-6-16-10 10 0 17 3 16 10Zm14 12c-10 2-18-1-22-6 10-2 18-1 22 6Zm56-12c10-1 14-6 16-10-10 0-17 3-16 10Zm-14 12c10 2 18-1 22-6-10-2-18-1-22 6Z"/></g>
62
+ ${flower(155, 18, .5)}<g fill="var(--accent)"><path d="m94 18 3-3 3 3-3 3Zm116 0 3-3 3 3-3 3Z"/></g>
63
+ </svg>`
@@ -17,14 +17,16 @@ export const characterCardStyles = `
17
17
  .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}
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
- .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}
20
+ .character-stats{padding:17px 18px 15px;display:flex;flex-direction:column}.character-stats>.character-identity,.character-stats>.resource,.character-stats>.attribute-panel{position:relative;z-index:4}
21
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)}
25
- .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}
25
+ .exploration-rank-row{display:flex;align-items:center;justify-content:center;gap:7px;margin:10px 0 0;font:16px/1.4 "KaiTi","STKaiti","Microsoft YaHei",sans-serif;letter-spacing:1px;color:var(--subtle)}.exploration-rank-row>svg{width:17px;height:17px;flex-shrink:0;fill:none;stroke:var(--accent);stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}.exploration-rank-row>span{white-space:nowrap}.exploration-rank-row strong{font-size:18px;font-weight:700;color:var(--ink);overflow-wrap:anywhere;min-width:0}
26
+ .combat-row{display:flex;align-items:center;justify-content:center;gap:8px;margin:12px 0 0;padding:0 6px;min-width:0;font:28px/1.2 "KaiTi","STKaiti","Microsoft YaHei",sans-serif;position:relative;z-index:4}.power-icon{flex:0 0 34px;height:34px;display:grid;place-items:center;color:var(--accent)}.power-icon svg{display:block;width:34px;height:34px;overflow:visible}.power-label{font:inherit;white-space:nowrap}.power-value{min-width:0;flex:0 1 auto;text-align:center;overflow-wrap:anywhere;font:inherit;color:var(--accent);letter-spacing:0}
26
27
  .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}
27
- .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))}
28
- .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}
28
+ .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{width:calc(100% - 20px);align-self:center;margin:0 0 14px}.exp-resource .resource-track{height:6px;margin-top:0;border-color:#d5c48b;border-radius:4px;background:#eee9d7;box-shadow:inset 0 1px 1px #9d82351a}.exp-resource .resource-fill{border-radius:3px;border-right:0;background:linear-gradient(#fff0a0,#e8bd48 60%,#d6a934)}
29
+ .hp-resource{display:flex;align-items:center;margin-top:16px;margin-bottom:0}.hp-resource .resource-track{flex:1;min-width:0;height:20px;margin-top:0}.hp-bar-value{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;padding:0 8px;white-space:nowrap;font:12px/1 "Microsoft YaHei",sans-serif;color:#fffef7;text-shadow:0 1px 2px #435443,0 0 2px #435443}
30
+ .companion-attributes{margin-top:5px;min-width:0;flex:1;display:flex;flex-direction:column;justify-content:center}.attribute-divider{height:28px;margin:0 0 3px;display:flex;align-items:center;justify-content:center}.attribute-divider svg{display:block;width:100%;height:36px}.companion-radar{width:100%;height:260px;display:block;overflow:visible}
29
31
  .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)}
30
32
  `
@@ -1,7 +1,7 @@
1
1
  import type { CharacterCardData } from '../shared/face'
2
2
  import { cardEscape } from './detailCardTemplate'
3
3
  import { characterCardStyles } from './characterCardStyles'
4
- import { characterFloralWatermark, characterGeneCrest, characterTierOrnament, characterTitlePlaceholder } from './characterCardAssets'
4
+ import { characterFloralWatermark, characterGeneCrest, characterTierOrnament, characterTitlePlaceholder, characterAttributeDivider } from './characterCardAssets'
5
5
  import type { CardEntryArtwork } from './companionCardTemplate'
6
6
 
7
7
  const finite = (value: number) => Number.isFinite(value) ? value : 0
@@ -12,7 +12,6 @@ const primaryAttributes = [
12
12
  ['intelligence', '智力'], ['spirit', '精神力'], ['resistance', '抗性'],
13
13
  ] as const
14
14
  const primaryIds: Set<string> = new Set(primaryAttributes.map(([id]) => id))
15
- const heading = (label: string) => `<h2 class="ornament-heading"><span class="heading-star" aria-hidden="true">✦</span>${cardEscape(label)}<span class="heading-star" aria-hidden="true">✦</span></h2>`
16
15
  const corners = (tier: number, positions: string[] = ['tl', 'tr', 'bl', 'br']) => positions.map(position => `<span class="panel-corner corner-${position}" data-decoration-level="${tier}">${characterTierOrnament(tier)}</span>`).join('')
17
16
 
18
17
  function radar(attributes: CharacterCardData['attributes']): string {
@@ -57,13 +56,15 @@ export function characterCardHtml(card: CharacterCardData, artwork?: string, _fr
57
56
  const hpPercent = ratio(card.hp, card.maxHp) * 100
58
57
  const experience = Math.max(0, Math.min(100, finite(card.experiencePercent)))
59
58
  const geneRank = card.geneLockRankName || '基因锁未开启'
59
+ const explorationRank = card.explorationRank?.trim()
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
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
- <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
- <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
- <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>
66
- <div class="resource companion-resource exp-resource"><div class="resource-heading"><span class="resource-label"><svg viewBox="0 0 32 32" aria-hidden="true"><path d="m16 2 4.5 9.1 10 1.4-7.3 7.1 1.7 10-8.9-4.7-8.9 4.7 1.7-10-7.3-7.1 10-1.4Z"/></svg>经验进度</span><span id="exp-number" class="resource-number">${number(experience)}%</span></div><div class="resource-track" role="progressbar" aria-label="经验进度" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${experience}"><span class="resource-fill" style="width:${experience}%"></span><span class="resource-percent">${number(experience)}%</span></div></div>
67
- <section class="attribute-panel companion-attributes" aria-label="一级属性">${heading('一级属性')}${radar(card.attributes)}</section></section>
62
+ ${titleArtwork || card.titleImageUrl ? `<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>
63
+ <div class="combat-row companion-power-row"><span class="power-emblem power-icon" aria-hidden="true"><svg class="crossed-weapons" viewBox="0 0 42 42"><g fill="currentColor" stroke="var(--panel)" stroke-width=".9" stroke-linejoin="round"><g transform="rotate(-45 21 21)"><path d="M21 2 26 9v14l-5 4-5-4V9Z"/><path d="M12 26h18l-1 4H13Zm7 4h4v8h-4Zm-1 8h6v3h-6Z"/><path d="M21 7v15" fill="none" stroke="var(--light-edge)" stroke-width="1.1"/></g><g transform="rotate(45 21 21)"><path d="M21 2 26 9v14l-5 4-5-4V9Z"/><path d="M12 26h18l-1 4H13Zm7 4h4v8h-4Zm-1 8h6v3h-6Z"/><path d="M21 7v15" fill="none" stroke="var(--light-edge)" stroke-width="1.1"/></g></g></svg></span><span class="power-label companion-power-label">战力</span><strong id="combat-power" class="power-value companion-power">${number(card.power)}</strong></div>
64
+ </div>
65
+ <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><p class="exploration-rank-row"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M19.8 7.7A8 8 0 1 0 20 16M20 3v5h-5M15.8 12a3.8 3.8 0 1 1-3.8-3.8"/></svg><span>轮回等级</span> <strong id="exploration-rank">${cardEscape(explorationRank ? explorationRank + '级' : '—')}</strong></p></div>
66
+ <div class="resource companion-resource hp-resource"><div class="resource-track" role="progressbar" aria-label="生命值" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${hpPercent}" aria-valuetext="${number(card.hp)} / ${number(card.maxHp)}"><span class="resource-fill fill" style="width:${hpPercent}%"></span><span id="hp-numbers" class="hp-bar-value">${number(card.hp)} / ${number(card.maxHp)}</span></div></div>
67
+ <div class="resource companion-resource exp-resource"><div id="experience-track" class="resource-track" role="progressbar" aria-label="经验进度" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${experience}"><span class="resource-fill" style="width:${experience}%"></span></div></div>
68
+ <section class="attribute-panel companion-attributes" aria-label="一级属性"><div class="attribute-divider" aria-hidden="true">${characterAttributeDivider}</div>${radar(card.attributes)}</section></section>
68
69
  </div></main></body></html>`
69
70
  }
@@ -0,0 +1,52 @@
1
+ import { MessageType, type IMessage } from '../../shared/face'
2
+ import { escapeMarkdown } from './qqMarkdownHelpers'
3
+
4
+ export type BattleLogLinkSpan = { start: number; end: number; label: string; url: string; id: string }
5
+
6
+ /** Only the dedicated battle-log route can carry server-supplied external links. */
7
+ export function battleLogLinkSpans(content: string): BattleLogLinkSpan[] {
8
+ const links: BattleLogLinkSpan[] = []
9
+ const pattern = /\[([^\[\]\r\n]+)\]\((https?:\/\/[^\s<>()]+)\)/g
10
+ for (const match of content.matchAll(pattern)) {
11
+ try {
12
+ const url = new URL(match[2])
13
+ const id = url.searchParams.get('id') ?? ''
14
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.hash
15
+ || url.pathname !== '/battlelog' || [...url.searchParams].length !== 1
16
+ || !/^[1-9]\d{0,15}$/.test(id) || !Number.isSafeInteger(Number(id))) continue
17
+ links.push({ start: match.index!, end: match.index! + match[0].length, label: match[1], url: url.href, id })
18
+ } catch { /* Ordinary prose and malformed links remain escaped text. */ }
19
+ }
20
+ return links
21
+ }
22
+
23
+ export function renderBattleLogLink(link: BattleLogLinkSpan): string {
24
+ return '[' + escapeMarkdown(link.label) + '](' + link.url + ')'
25
+ }
26
+
27
+ /** Keep every other Markdown token literal while preserving verified log links. */
28
+ export function renderBattleLogLinkText(content: string): string {
29
+ const parts: string[] = []
30
+ let offset = 0
31
+ for (const link of battleLogLinkSpans(content)) {
32
+ parts.push(escapeMarkdown(content.slice(offset, link.start)), renderBattleLogLink(link))
33
+ offset = link.end
34
+ }
35
+ parts.push(escapeMarkdown(content.slice(offset)))
36
+ return parts.join('')
37
+ }
38
+
39
+ export function battleLogMessageLinks(data: Pick<IMessage, 'line'>): BattleLogLinkSpan[] {
40
+ const links = new Map<string, BattleLogLinkSpan>()
41
+ for (const line of data.line) {
42
+ if (line.type !== MessageType.line || line.codeBlock || line.mailPart === 'body') continue
43
+ for (const link of battleLogLinkSpans(line.content)) if (!links.has(link.url)) links.set(link.url, link)
44
+ }
45
+ return [...links.values()]
46
+ }
47
+
48
+ /** Compact reward cards omit ordinary details, so add their log links explicitly. */
49
+ export function appendMissingBattleLogLinks(markdown: string, data: Pick<IMessage, 'line'>): string {
50
+ const missing = battleLogMessageLinks(data).map(renderBattleLogLink).filter(link => !markdown.includes(link))
51
+ return [markdown, ...missing].filter(Boolean).join('\n\n')
52
+ }
@@ -52,8 +52,12 @@ export function renderQQCommandMarkdown(data: IMessage, showGreeting: boolean):
52
52
  break
53
53
  }
54
54
  flushRewards()
55
- if (!item.content.trim()) flushBody()
56
- else bodyLines.push(renderInlineCommands(item.content))
55
+ // A single message item may contain a complete multiline battle summary.
56
+ // Keep its line/paragraph boundaries when log links select native Markdown.
57
+ for (const line of item.content.replace(/\r\n?/g, '\n').split('\n')) {
58
+ if (!line.trim()) flushBody()
59
+ else bodyLines.push(renderInlineCommands(line))
60
+ }
57
61
  break
58
62
  case MessageType.error:
59
63
  flushBody()
@@ -4,6 +4,7 @@ import { hasInlineCommands, renderQQPageMarkdown } from './qqPageMessage'
4
4
  import { renderQQCommandMarkdown } from './qqCommandMessage'
5
5
  import { renderQQTransactionMarkdown } from './qqTransactionMessage'
6
6
  import { hasStoryCodeBlock, renderQQStoryMarkdown } from './qqStoryMessage'
7
+ import { battleLogMessageLinks } from './qqBattleLogLinks'
7
8
  import { isRewardMessage, renderQQRewardMarkdown } from './qqRewardMessage'
8
9
 
9
10
  export type QQMessageKind = 'text' | 'story' | 'command' | 'transaction' | 'page' | 'reward'
@@ -26,7 +27,7 @@ export function renderQQMessagePreview(data: IMessage, showGreeting = false): QQ
26
27
  if (data.transaction) return { kind: 'transaction', markdown: renderQQTransactionMarkdown(data, showGreeting), text }
27
28
  if (data.pageNav) return { kind: 'page', markdown: renderQQPageMarkdown(data, showGreeting), text }
28
29
  if (isRewardMessage(data)) return { kind: 'reward', markdown: renderQQRewardMarkdown(data, showGreeting), text }
29
- if (data.line.some(item => (item.type === MessageType.line || item.type === MessageType.error) && hasInlineCommands(item.content))) {
30
+ if (battleLogMessageLinks(data).length || data.line.some(item => (item.type === MessageType.line || item.type === MessageType.error) && hasInlineCommands(item.content))) {
30
31
  return { kind: 'command', markdown: renderQQCommandMarkdown(data, showGreeting), text }
31
32
  }
32
33
  return { kind: 'text', text }
@@ -4,6 +4,8 @@ import type { MsgMessage } from '../../shared/bot/MsgMessage'
4
4
  import { compactDisplayText, escapeMarkdown, renderQQTitle } from './qqMarkdownHelpers'
5
5
  import { isRewardMessage, renderRewardLine, renderRewardTitle } from './qqRewardMessage'
6
6
  import { artworkIdentity } from '../skillArtwork'
7
+ import { battleLogLinkSpans, renderBattleLogLink } from './qqBattleLogLinks'
8
+ import { inlineCommandSpans as bracketedCommandSpans, type InlineCommandSpan } from '../../shared/bot/inlineCommands'
7
9
 
8
10
  export { escapeMarkdown, renderQQTitle } from './qqMarkdownHelpers'
9
11
 
@@ -53,28 +55,11 @@ export function inlineCommandUrl(command: string): string {
53
55
  }
54
56
 
55
57
 
56
- type InlineCommandSpan = { start: number; end: number; label: string; command: string }
57
-
58
58
  /** Bracketed commands may appear anywhere in prose, including escaped brackets. */
59
59
  function inlineCommandSpans(content: string): InlineCommandSpan[] {
60
- const spans: InlineCommandSpan[] = []
61
- let depth = 0, start = 0, labelStart = 0
62
- for (let index = 0; index < content.length; index++) {
63
- if (content[index] === '[') {
64
- if (depth === 0) {
65
- start = index > 0 && content[index - 1] === '\\' ? index - 1 : index
66
- labelStart = index + 1
67
- }
68
- depth++
69
- } else if (content[index] === ']' && depth > 0 && --depth === 0) {
70
- const label = content.slice(labelStart, index).replace(/\\$/, '')
71
- if (!label.trim() || /[\[\]\r\n]/.test(label) || /^\d+$/.test(label.trim())) continue
72
- // The refresh icon is part of the displayed label, not the command to send.
73
- const command = label.split(/[++]/, 1)[0].trim().replace(/^🔄\s*/u, '')
74
- if (command) spans.push({ start, end: index + 1, label, command })
75
- }
76
- }
77
- return spans
60
+ const spans = bracketedCommandSpans(content)
61
+ const logLinks = battleLogLinkSpans(content)
62
+ return spans.filter(span => !logLinks.some(link => span.start < link.end && span.end > link.start))
78
63
  }
79
64
 
80
65
  export function hasInlineCommands(content: string): boolean {
@@ -85,9 +70,12 @@ export function hasInlineCommands(content: string): boolean {
85
70
  export function renderInlineCommands(content: string): string {
86
71
  const parts: string[] = []
87
72
  let offset = 0
88
- for (const span of inlineCommandSpans(content)) {
89
- parts.push(escapeMarkdown(content.slice(offset, span.start)))
90
- parts.push('[' + escapeMarkdown(span.label) + '](' + inlineCommandUrl(span.command) + ')')
73
+ const spans = [
74
+ ...inlineCommandSpans(content).map(span => ({ ...span, rendered: '[' + escapeMarkdown(span.label) + '](' + inlineCommandUrl(span.command) + ')' })),
75
+ ...battleLogLinkSpans(content).map(span => ({ ...span, rendered: renderBattleLogLink(span) })),
76
+ ].sort((a, b) => a.start - b.start)
77
+ for (const span of spans) {
78
+ parts.push(escapeMarkdown(content.slice(offset, span.start)), span.rendered)
91
79
  offset = span.end
92
80
  }
93
81
  parts.push(escapeMarkdown(content.slice(offset)))
@@ -1,5 +1,6 @@
1
1
  import { MessageType, type IMessage } from '../../shared/face'
2
2
  import { compactDisplayText, escapeMarkdown } from './qqMarkdownHelpers'
3
+ import { appendMissingBattleLogLinks, renderBattleLogLinkText } from './qqBattleLogLinks'
3
4
 
4
5
  export function hasRewardLines(data: Pick<IMessage, 'line'>): boolean {
5
6
  return data.line.some(item => item.type === MessageType.line && item.reward === true)
@@ -67,10 +68,10 @@ function renderQQMailMarkdown(data: IMessage): string {
67
68
 
68
69
  /** Reward cards stay compact; mail keeps its content before the attachment quote. */
69
70
  export function renderQQRewardMarkdown(data: IMessage, showGreeting: boolean): string {
70
- if (data.mail) return renderQQMailMarkdown(data)
71
+ if (data.mail) return appendMissingBattleLogLinks(renderQQMailMarkdown(data), data)
71
72
  const rewards = data.line.filter(item => item.type === MessageType.line && item.reward)
72
73
  .map(item => renderRewardLine(item.content)).filter(Boolean)
73
- if (rewards.length && !data.mail) return '🎁获得奖励\n\n' + rewards.join(' \n')
74
+ if (rewards.length && !data.mail) return appendMissingBattleLogLinks('🎁获得奖励\n\n' + rewards.join(' \n'), data)
74
75
 
75
76
  const sections: string[] = []
76
77
  const bodyLines: string[] = []
@@ -86,7 +87,7 @@ export function renderQQRewardMarkdown(data: IMessage, showGreeting: boolean): s
86
87
  } else if (item.type === MessageType.line) {
87
88
  for (const line of item.content.replace(/\r\n?/g, '\n').split('\n')) {
88
89
  if (!line.trim()) flushBody()
89
- else bodyLines.push(escapeMarkdown(line))
90
+ else bodyLines.push(renderBattleLogLinkText(line))
90
91
  }
91
92
  } else if (item.type === MessageType.error) {
92
93
  bodyLines.push(`❌ ${escapeMarkdown(item.content)}`)
@@ -94,5 +95,5 @@ export function renderQQRewardMarkdown(data: IMessage, showGreeting: boolean): s
94
95
  }
95
96
  flushBody()
96
97
  if (rewards.length) sections.push('🎁获得奖励\n\n' + rewards.join(' \n'))
97
- return sections.join('\n\n') || ' '
98
+ return appendMissingBattleLogLinks(sections.join('\n\n') || ' ', data)
98
99
  }
@@ -2,6 +2,7 @@ import type { Session } from 'koishi'
2
2
  import { MessageType, type IMessage } from '../../shared/face'
3
3
  import type { MsgMessage } from '../../shared/bot/MsgMessage'
4
4
  import { escapeMarkdown, renderInlineCommands, renderQQTitle, sendQQMarkdown } from './qqPageMessage'
5
+ import { renderBattleLogLinkText } from './qqBattleLogLinks'
5
6
  import { isRewardMessage, renderQQRewardMarkdown, renderRewardLine, renderRewardTitle } from './qqRewardMessage'
6
7
 
7
8
  export function hasStoryCodeBlock(data: Pick<IMessage, 'line'>): boolean {
@@ -57,14 +58,14 @@ export function renderQQStoryMarkdown(data: IMessage, showGreeting: boolean): st
57
58
  } else {
58
59
  for (const line of item.content.replace(/\r\n?/g, '\n').split('\n')) {
59
60
  if (!line.trim()) flushBody()
60
- else bodyLines.push(data.reward ? escapeMarkdown(line) : renderInlineCommands(line))
61
+ else bodyLines.push(data.reward ? renderBattleLogLinkText(line) : renderInlineCommands(line))
61
62
  }
62
63
  }
63
64
  break
64
65
  case MessageType.error:
65
66
  flushBody()
66
67
  if (!data.reward) flushRewards()
67
- sections.push('❌ 出错了!错误原因:' + (data.reward ? escapeMarkdown(item.content) : renderInlineCommands(item.content)) + ',请反馈至联系开发者')
68
+ sections.push('❌ 出错了!错误原因:' + (data.reward ? renderBattleLogLinkText(item.content) : renderInlineCommands(item.content)) + ',请反馈至联系开发者')
68
69
  break
69
70
  }
70
71
  }
@@ -1,4 +1,5 @@
1
1
  import { IMessage, MessageType, type PageNavData } from '../face'
2
+ import { renderPlainInlineCommandAliases } from './inlineCommands'
2
3
 
3
4
  /** 把服务器下发的结构化行拼成纯文本;第一阶段只处理 title / line / error **/
4
5
  export class AnalysisMsg {
@@ -31,7 +32,9 @@ export class AnalysisMsg {
31
32
  ? labeledRow.map(entry => entry.label! + (entry.description ? ' · ' + entry.description : '')
32
33
  + ` [${entry.viewCommand}]`).join('')
33
34
  : withCommandHint(item.content, commands.get(lineIndex))
34
- msg += `${content}\n`
35
+ const plainContent = item.codeBlock || item.mailPart === 'body' || labeledRow
36
+ ? content : renderPlainInlineCommandAliases(content)
37
+ msg += `${plainContent}\n`
35
38
  break
36
39
  case MessageType.error:
37
40
  msg += `❌出错了!错误原因:${item.content},请反馈至联系开发者\n`
@@ -0,0 +1,39 @@
1
+ export type InlineCommandSpan = { start: number; end: number; label: string; command: string; aliased?: true }
2
+
3
+ /** Boxed commands may use [label](command:input) to show a different action label. */
4
+ export function inlineCommandSpans(content: string): InlineCommandSpan[] {
5
+ const spans: InlineCommandSpan[] = []
6
+ let depth = 0, start = 0, labelStart = 0
7
+ for (let index = 0; index < content.length; index++) {
8
+ if (content[index] === '[') {
9
+ if (depth === 0) {
10
+ start = index > 0 && content[index - 1] === '\\' ? index - 1 : index
11
+ labelStart = index + 1
12
+ }
13
+ depth++
14
+ } else if (content[index] === ']' && depth > 0 && --depth === 0) {
15
+ const label = content.slice(labelStart, index).replace(/\\$/, '')
16
+ if (!label.trim() || /[\[\]\r\n]/.test(label) || /^\d+$/.test(label.trim())) continue
17
+ const alias = /^\(command:([^()[\]\r\n]+)\)/.exec(content.slice(index + 1))
18
+ // Ordinary templates keep their existing command extraction rules.
19
+ const command = alias ? alias[1].trim() : label.split(/[++]/, 1)[0].trim().replace(/^🔄\s*/u, '')
20
+ if (!command) continue
21
+ const end = index + 1 + (alias?.[0].length ?? 0)
22
+ spans.push({ start, end, label, command, ...(alias ? { aliased: true as const } : {}) })
23
+ if (alias) index = end - 1
24
+ }
25
+ }
26
+ return spans
27
+ }
28
+
29
+ /** Keep aliased actions usable when the platform falls back to ordinary text. */
30
+ export function renderPlainInlineCommandAliases(content: string): string {
31
+ let result = '', offset = 0
32
+ for (const span of inlineCommandSpans(content)) {
33
+ if (!span.aliased) continue
34
+ result += content.slice(offset, span.start)
35
+ + (span.label.trim() === span.command ? `[${span.command}]` : `${span.label}[${span.command}]`)
36
+ offset = span.end
37
+ }
38
+ return result + content.slice(offset)
39
+ }
@@ -25,7 +25,7 @@ export interface ServiceType {
25
25
  }
26
26
 
27
27
  export const serviceProto: ServiceProto<ServiceType> = {
28
- "version": 37,
28
+ "version": 38,
29
29
  "services": [
30
30
  {
31
31
  "id": 0,
@@ -709,6 +709,14 @@ export const serviceProto: ServiceProto<ServiceType> = {
709
709
  }
710
710
  },
711
711
  "optional": true
712
+ },
713
+ {
714
+ "id": 14,
715
+ "name": "explorationRank",
716
+ "type": {
717
+ "type": "String"
718
+ },
719
+ "optional": true
712
720
  }
713
721
  ]
714
722
  },
@@ -38,7 +38,7 @@ export interface CharacterCardData {
38
38
  bloodlines: Array<{ name: string; quality: string; artworkUrl?: string }>;
39
39
  /** 当前基因锁大阶的配置名称,不包含层级;未开启时为基因锁未开启。 */
40
40
  geneLockRankName?: string;
41
- /** 当前称号的图片链接;未获得称号或图片不可用时省略,客户端显示图片占位。 */
41
+ /** 当前称号的图片链接;未获得称号或图片不可用时省略,客户端不显示称号横幅。 */
42
42
  titleImageUrl?: string;
43
43
  /** 已获得的真实基因锁大阶(0~5);0 表示尚未解锁。 */
44
44
  geneLockTier?: number;
@@ -46,6 +46,8 @@ export interface CharacterCardData {
46
46
  equipment?: Array<{ id: string; name: string; quality: string; slot: string; enhancement?: number }>;
47
47
  /** 当前构筑的全部技能,包括装备、递归附带与限时增益来源。 */
48
48
  skills?: Array<{ id: string; name: string; rank: number; quality?: string; type?: CompanionSkillType; artworkUrl?: string }>;
49
+ /** 玩家当前的轮回探索品阶(F~SSS),与角色等级、基因锁阶级分开显示。 */
50
+ explorationRank?: string;
49
51
  }
50
52
  export enum CompanionCardKind {
51
53
  pet = 'pet',