koishi-plugin-dew-bot 1.0.265 → 1.0.266

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -67,7 +67,7 @@ var serviceProto;
67
67
  var init_serviceProto = __esm({
68
68
  "src/shared/bot/serviceProto.ts"() {
69
69
  serviceProto = {
70
- "version": 42,
70
+ "version": 44,
71
71
  "services": [
72
72
  {
73
73
  "id": 0,
@@ -931,6 +931,22 @@ var init_serviceProto = __esm({
931
931
  "type": {
932
932
  "type": "String"
933
933
  }
934
+ },
935
+ {
936
+ "id": 15,
937
+ "name": "stars",
938
+ "type": {
939
+ "type": "Number"
940
+ },
941
+ "optional": true
942
+ },
943
+ {
944
+ "id": 16,
945
+ "name": "maxStars",
946
+ "type": {
947
+ "type": "Number"
948
+ },
949
+ "optional": true
934
950
  }
935
951
  ]
936
952
  },
@@ -1192,6 +1208,33 @@ var init_serviceProto = __esm({
1192
1208
  "type": "String"
1193
1209
  },
1194
1210
  "optional": true
1211
+ },
1212
+ {
1213
+ "id": 13,
1214
+ "name": "randomAffixes",
1215
+ "type": {
1216
+ "type": "Array",
1217
+ "elementType": {
1218
+ "type": "Interface",
1219
+ "properties": [
1220
+ {
1221
+ "id": 0,
1222
+ "name": "name",
1223
+ "type": {
1224
+ "type": "String"
1225
+ }
1226
+ },
1227
+ {
1228
+ "id": 1,
1229
+ "name": "value",
1230
+ "type": {
1231
+ "type": "String"
1232
+ }
1233
+ }
1234
+ ]
1235
+ }
1236
+ },
1237
+ "optional": true
1195
1238
  }
1196
1239
  ]
1197
1240
  },
@@ -2768,6 +2811,12 @@ h1,h2,h3,p{margin:0}
2768
2811
  .skill-type{font-size:24px;letter-spacing:3px;color:#cbcdbd}
2769
2812
  .quality-badge{display:inline-flex;align-items:center;gap:9px;padding:6px 14px;color:#fff5de;font-size:18px;font-weight:600;border:1px solid color-mix(in srgb,var(--quality-accent) 70%,transparent);border-radius:6px;background:color-mix(in srgb,var(--quality-accent) 17%,transparent)}
2770
2813
  .quality-badge b{color:var(--quality-end);font:700 24px Georgia,serif}
2814
+ .skill-stars{display:flex;align-items:center;flex-wrap:wrap;gap:3px;max-width:100%;margin-top:-8px}
2815
+ .skill-star{display:block;flex:0 0 40px;width:40px;height:40px;overflow:visible;filter:drop-shadow(0 1px 1px #10121780)}
2816
+ .star-shape{stroke:#24252b;stroke-width:2.4;stroke-linejoin:round}
2817
+ .star-gradient-light{stop-color:color-mix(in srgb,var(--quality-end) 55%,#fff)}.star-gradient-mid{stop-color:var(--quality-accent)}.star-gradient-shade{stop-color:var(--quality-start)}
2818
+ .star-facet-light{fill:#fff;opacity:.28}.star-facet-shade{fill:#151328;opacity:.18}.star-facet-glint{fill:none;stroke:#fff;stroke-width:.7;stroke-linejoin:round;opacity:.55}
2819
+ .skill-star.unlit .star-shape{fill:#303436;stroke:#171d22;stroke-width:2.6}
2771
2820
  .tags{display:flex;gap:7px;flex-wrap:wrap}
2772
2821
  .tag{font-size:20px;line-height:1.3;padding:6px 10px;border-radius:4px;overflow-wrap:anywhere;color:#bfcaed;background:#647bb434;border:1px solid #7d9cc62b}
2773
2822
  .tag.damage{color:#f5ae94;background:#b961433d;border-color:#b4614340}.tag.shield{color:#a3d7b7;background:#5186683d;border-color:#73a48a40}.tag.heal{color:#b5deb0;background:#668e4a3b;border-color:#89a47140}.tag.passive{color:#cebbef;background:#8367a63d;border-color:#9682b540}.tag.growth{color:#ecd291;background:#b1944540;border-color:#ab944e40}
@@ -3627,6 +3676,20 @@ function tagClass(tag) {
3627
3676
  return "";
3628
3677
  }
3629
3678
  __name(tagClass, "tagClass");
3679
+ function skillStars(card) {
3680
+ if (card.stars === void 0 && card.maxStars === void 0) return "";
3681
+ const maxStars = Math.min(20, Math.max(0, Math.floor(Number.isFinite(card.maxStars) ? card.maxStars : Number.isFinite(card.stars) ? card.stars : 0)));
3682
+ if (!maxStars) return "";
3683
+ const stars = Math.min(maxStars, Math.max(0, Math.floor(Number.isFinite(card.stars) ? card.stars : 0)));
3684
+ const outline = "20,3 25.1,13.4 36.6,15.1 28.3,23.2 30.3,34.7 20,29.3 9.7,34.7 11.7,23.2 3.4,15.1 14.9,13.4";
3685
+ const icons = Array.from({ length: maxStars }, (_, index) => {
3686
+ const lit = index < stars;
3687
+ const gradient = "skill-star-fill-" + index;
3688
+ return '<svg class="skill-star ' + (lit ? "lit" : "unlit") + '" viewBox="0 0 40 40" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">' + (lit ? '<defs><linearGradient id="' + gradient + '" x1="0" y1="0" x2="0.7" y2="1"><stop class="star-gradient-light"/><stop offset=".48" class="star-gradient-mid"/><stop offset="1" class="star-gradient-shade"/></linearGradient></defs>' : "") + '<polygon class="star-shape" points="' + outline + '"' + (lit ? ' fill="url(#' + gradient + ')"' : "") + "/>" + (lit ? '<path class="star-facet-light" d="M20 5.7 20 20.6 5.9 16.1 15.9 14.6Z"/><path class="star-facet-shade" d="M20 20.6 28.6 32.3 26.9 22.7 34.1 16.1Z"/><path class="star-facet-glint" d="M20 6.2 20 20.6 12 31.9 13.3 23 6.5 16.6 16 15.2Z"/>' : "") + "</svg>";
3689
+ }).join("");
3690
+ return '<div class="skill-stars" role="img" aria-label="' + stars + " / " + maxStars + ' 星">' + icons + "</div>";
3691
+ }
3692
+ __name(skillStars, "skillStars");
3630
3693
  function skillCardHtml(card, _frame = "", artwork) {
3631
3694
  const quality = normalizeCardQuality(card.quality);
3632
3695
  const description = card.description?.trim() || "暂无技能效果说明";
@@ -3644,13 +3707,13 @@ function skillCardHtml(card, _frame = "", artwork) {
3644
3707
  <div id="capture" class="skill-sheet"><article class="detail-card skill-card quality-${quality.toLowerCase()}" style="${qualityStyle(quality)}">
3645
3708
  <i class="card-corner corner-tl" aria-hidden="true"></i><i class="card-corner corner-tr" aria-hidden="true"></i><i class="card-corner corner-bl" aria-hidden="true"></i><i class="card-corner corner-br" aria-hidden="true"></i>
3646
3709
  <div class="card-inner"><header class="card-titlebar"><span class="title-glyph" aria-hidden="true">◇</span><h1>${escape2(card.name)}</h1><span class="title-star" aria-hidden="true">✦</span></header>
3647
- <div class="card-hero"><div class="hero-copy"><div class="hero-meta"><span class="skill-type">${card.passive ? "被动技能" : "主动技能"}</span><span class="quality-badge"><b>${quality}</b> 级</span></div><div class="tags">${tags.map((tag) => `<span class="tag ${tagClass(tag)}">${escape2(tag)}</span>`).join("")}</div></div><div class="hero-icon-wrap">${qualityMagicCircle(quality)}<div class="art-fallback" aria-hidden="true">${card.passive ? "静" : "势"}</div>${artwork ? `<img class="skill-artwork" alt="${escape2(card.name)}图标" src="${escape2(artwork)}">` : ""}${qualityIconFrame(quality)}</div></div>
3710
+ <div class="card-hero"><div class="hero-copy"><div class="hero-meta"><span class="skill-type">${card.passive ? "被动技能" : "主动技能"}</span><span class="quality-badge"><b>${quality}</b> 级</span>${skillStars(card)}</div><div class="tags">${tags.map((tag) => `<span class="tag ${tagClass(tag)}">${escape2(tag)}</span>`).join("")}</div></div><div class="hero-icon-wrap">${qualityMagicCircle(quality)}<div class="art-fallback" aria-hidden="true">${card.passive ? "静" : "势"}</div>${artwork ? `<img class="skill-artwork" alt="${escape2(card.name)}图标" src="${escape2(artwork)}">` : ""}${qualityIconFrame(quality)}</div></div>
3648
3711
  <div class="card-body"><div class="effect-stats">
3649
3712
  ${row("消耗", escape2(card.cost || "无消耗"), true)}
3650
3713
  ${row("冷却", cooldownValue, true)}
3651
3714
  ${power ? row("战力", escape2(power.replace(/^(?:🔥\s*)?战力\s*[::]\s*/, "")), true) : ""}
3652
3715
  ${(card.stats || []).slice(0, 3).map((stat) => row(stat.label, statValue(stat.value))).join("")}
3653
- </div><section class="effect"><h2 class="section-heading">技能效果</h2><p class="description">${effectText(effect)}</p></section>
3716
+ </div><section class="effect"><h2 class="section-heading">${escape2(card.mechanism || "技能效果")}</h2><p class="description">${effectText(effect)}</p></section>
3654
3717
  ${growth ? `<section class="growth-section"><h2 class="section-heading">永久成长 · 仅玩家</h2><p class="description">${effectText(growth)}</p></section>` : ""}
3655
3718
  ${otherNotes ? `<section class="note-section"><h2 class="section-heading">附加信息</h2><p class="description">${escape2(otherNotes)}</p></section>` : ""}
3656
3719
  </div></div></article></div></body></html>`;
@@ -3668,7 +3731,8 @@ function equipmentCardHtml(card, artwork, unknownFallback = false) {
3668
3731
  cardStatRow("打造者", card.authorName?.trim() || "未知"),
3669
3732
  ...card.referencePrice ? [cardStatRow("参考价格", card.referencePrice)] : []
3670
3733
  ].join("");
3671
- const affixes = card.affixes?.length ? '<div class="detail-list">' + card.affixes.map((affix) => cardStatRow(affix.name, affix.value)).join("") + "</div>" : '<p class="description">暂无附加属性</p>';
3734
+ const affixes = card.affixes?.length ? '<div class="detail-list">' + card.affixes.map((affix) => cardStatRow(affix.name, affix.value)).join("") + "</div>" : '<p class="description">暂无基础属性</p>';
3735
+ const randomAffixes = card.randomAffixes?.length ? cardSection("随机词条", '<div class="detail-list">' + card.randomAffixes.map((affix) => cardStatRow(affix.name, affix.value)).join("") + "</div>") : "";
3672
3736
  const skills2 = card.skills?.length ? cardSection("附带技能", '<div class="detail-list">' + card.skills.map((skill) => cardStatRow(skill.name, "")).join("") + "</div>") : "";
3673
3737
  const description = card.description?.trim() ? cardSection("装备描述", '<p class="description">' + cardEscape(card.description.trim()) + "</p>") : "";
3674
3738
  const notes = card.notes?.filter((note) => note?.trim()).length ? cardSection("附加信息", '<p class="description">' + card.notes.filter((note) => note?.trim()).map((note) => cardEscape(note)).join("\n") + "</p>") : "";
@@ -3681,7 +3745,7 @@ function equipmentCardHtml(card, artwork, unknownFallback = false) {
3681
3745
  fallbackGlyph: "?",
3682
3746
  sheetClass: "equipment-sheet",
3683
3747
  tags: unknownFallback || !artwork ? ["外观暂未显现"] : [],
3684
- body: '<div class="effect-stats">' + details + "</div>" + cardSection("附加属性", affixes) + skills2 + description + notes
3748
+ body: '<div class="effect-stats">' + details + "</div>" + cardSection("基础属性", affixes) + randomAffixes + skills2 + description + notes
3685
3749
  });
3686
3750
  }
3687
3751
  __name(equipmentCardHtml, "equipmentCardHtml");
@@ -4793,28 +4857,6 @@ var AnalysisMsg = class {
4793
4857
  }
4794
4858
  };
4795
4859
 
4796
- // src/plugin/serverHandel/qqMessageButtons.ts
4797
- function renderQQKeyboard(data) {
4798
- const rows = data.buttons;
4799
- if (!rows?.length) return void 0;
4800
- if (rows.length > 5 || rows.some((row) => !row.buttons.length || row.buttons.length > 5)) {
4801
- throw new Error("QQ 按钮最多 5 行,每行必须有 1–5 个按钮");
4802
- }
4803
- return { content: { rows: rows.map((row, rowIndex) => ({
4804
- buttons: row.buttons.map((button, buttonIndex) => {
4805
- if (!button.label.trim() || !button.command.trim() || /[\r\n\0]/.test(button.label + button.command) || button.command.length > 4e3 || button.style !== void 0 && button.style !== "primary" && button.style !== "secondary") {
4806
- throw new Error("QQ 按钮文案和指令必须是非空单行文本,样式必须为 primary 或 secondary");
4807
- }
4808
- return {
4809
- id: "action-" + rowIndex + "-" + buttonIndex,
4810
- render_data: { label: button.label, visited_label: button.label, style: button.style === "primary" ? 1 : 0 },
4811
- action: { type: 2, permission: { type: 2 }, data: button.command, enter: true, reply: false, unsupport_tips: "请发送:" + button.command }
4812
- };
4813
- })
4814
- })) } };
4815
- }
4816
- __name(renderQQKeyboard, "renderQQKeyboard");
4817
-
4818
4860
  // src/plugin/serverHandel/qqMarkdownHelpers.ts
4819
4861
  function escapeMarkdown(value) {
4820
4862
  return value.replace(/[\\`*_{}\[\]()#+\-.!|>~<&]/g, "\\$&");
@@ -4890,16 +4932,20 @@ function renderRewardTitle(content) {
4890
4932
  return `**${escapeMarkdown(compactDisplayText(content))}**`;
4891
4933
  }
4892
4934
  __name(renderRewardTitle, "renderRewardTitle");
4893
- function renderRewardLine(content) {
4935
+ function renderRewardLine(content, command) {
4894
4936
  return content.replace(/\r\n?/g, "\n").split("\n").filter((line) => line.trim()).map((line) => {
4895
4937
  const text = line.trim();
4896
4938
  const quantity = /([++\-−×xX]\s*\d+(?:\.\d+)?)$/.exec(text);
4897
- const formatted = quantity ? escapeMarkdown(text.slice(0, quantity.index)) + `**${escapeMarkdown(quantity[1])}**` : escapeMarkdown(text);
4939
+ const label = quantity ? text.slice(0, quantity.index) : text;
4940
+ const name2 = label.trimEnd();
4941
+ const formattedName = command && name2 ? `[${escapeMarkdown(name2)}](${inlineCommandUrl(command)})${escapeMarkdown(label.slice(name2.length))}` : escapeMarkdown(label);
4942
+ const formatted = formattedName + (quantity ? `**${escapeMarkdown(quantity[1])}**` : "");
4898
4943
  return "> " + formatted;
4899
4944
  }).join(" \n");
4900
4945
  }
4901
4946
  __name(renderRewardLine, "renderRewardLine");
4902
4947
  function renderQQMailMarkdown(data) {
4948
+ const commands = new Map(data.pageNav?.entries?.map((entry) => [entry.lineIndex, entry.viewCommand]) ?? []);
4903
4949
  const sections = [];
4904
4950
  const text = [];
4905
4951
  const rewards = [];
@@ -4909,7 +4955,7 @@ function renderQQMailMarkdown(data) {
4909
4955
  const flushRewards = /* @__PURE__ */ __name(() => {
4910
4956
  if (rewards.length) sections.push(rewards.splice(0).join(" \n"));
4911
4957
  }, "flushRewards");
4912
- for (const item of data.line) {
4958
+ for (const [lineIndex, item] of data.line.entries()) {
4913
4959
  if (item.type === 0 /* title */ || item.type === 1 /* line */ && item.mailPart === "title") {
4914
4960
  flushText();
4915
4961
  flushRewards();
@@ -4920,7 +4966,7 @@ function renderQQMailMarkdown(data) {
4920
4966
  sections.push(item.content.replace(/\r\n?/g, "\n").split("\n").map((line) => "> " + escapeMarkdown(line)).join(" \n"));
4921
4967
  } else if (item.type === 1 /* line */ && item.reward) {
4922
4968
  flushText();
4923
- const reward = renderRewardLine(item.content);
4969
+ const reward = renderRewardLine(item.content, commands.get(lineIndex));
4924
4970
  if (reward) rewards.push(reward);
4925
4971
  } else {
4926
4972
  flushRewards();
@@ -5275,6 +5321,28 @@ async function sendQQMarkdown(session, markdown, keyboard) {
5275
5321
  }
5276
5322
  __name(sendQQMarkdown, "sendQQMarkdown");
5277
5323
 
5324
+ // src/plugin/serverHandel/qqMessageButtons.ts
5325
+ function renderQQKeyboard(data) {
5326
+ const rows = data.buttons;
5327
+ if (!rows?.length) return void 0;
5328
+ if (rows.length > 5 || rows.some((row) => !row.buttons.length || row.buttons.length > 5)) {
5329
+ throw new Error("QQ 按钮最多 5 行,每行必须有 1–5 个按钮");
5330
+ }
5331
+ return { content: { rows: rows.map((row, rowIndex) => ({
5332
+ buttons: row.buttons.map((button, buttonIndex) => {
5333
+ if (!button.label.trim() || !button.command.trim() || /[\r\n\0]/.test(button.label + button.command) || button.command.length > 4e3 || button.style !== void 0 && button.style !== "primary" && button.style !== "secondary") {
5334
+ throw new Error("QQ 按钮文案和指令必须是非空单行文本,样式必须为 primary 或 secondary");
5335
+ }
5336
+ return {
5337
+ id: "action-" + rowIndex + "-" + buttonIndex,
5338
+ render_data: { label: button.label, visited_label: button.label, style: button.style === "primary" ? 1 : 0 },
5339
+ action: { type: 2, permission: { type: 2 }, data: button.command, enter: true, reply: false, unsupport_tips: "请发送:" + button.command }
5340
+ };
5341
+ })
5342
+ })) } };
5343
+ }
5344
+ __name(renderQQKeyboard, "renderQQKeyboard");
5345
+
5278
5346
  // src/plugin/serverHandel/qqCommandMessage.ts
5279
5347
  function renderQQCommandMarkdown(data, showGreeting) {
5280
5348
  data = withoutMessageButtonFallbacks(data);
@@ -5512,6 +5580,7 @@ function renderQQMessagePreview(data, showGreeting = false) {
5512
5580
  if (hasStoryCodeBlock(data)) return native("story", renderQQStoryMarkdown(data, showGreeting));
5513
5581
  if (data.commandHelp) return native("command", renderQQCommandMarkdown(data, showGreeting));
5514
5582
  if (data.transaction) return native("transaction", renderQQTransactionMarkdown(data, showGreeting));
5583
+ if (data.mail) return native("reward", renderQQRewardMarkdown(data, showGreeting));
5515
5584
  if (data.pageNav) return native("page", renderQQPageMarkdown(data, showGreeting));
5516
5585
  if (isRewardMessage(data)) return native("reward", renderQQRewardMarkdown(data, showGreeting));
5517
5586
  if (battleLogMessageLinks(data).length || data.line.some((item) => (item.type === 1 /* line */ || item.type === 2 /* error */) && hasInlineCommands(item.content))) {
@@ -1,2 +1,2 @@
1
1
  /** Shared detail-card styling; all ornaments render with JavaScript disabled. */
2
- export declare const qualityCardStyles = "\n*{box-sizing:border-box}\nhtml,body{margin:0;width:760px;background:#e9e9e2;font-family:'Microsoft YaHei','Noto Sans CJK SC',sans-serif;color:#303735}\n#capture{width:760px;padding:18px;background:radial-gradient(ellipse at 70% 10%,#f9f7efb8,transparent 55%),#e9e9e2}\nh1,h2,h3,p{margin:0}\n.detail-card{position:relative;width:100%;isolation:isolate;border:3px solid color-mix(in srgb,var(--quality-accent) 50%,#454a45);border-radius:18px;box-shadow:0 8px 18px #3f47321c;background:#f5f4ed;overflow:visible;--corner-size:24px;--corner-metal:#dce1d6;--corner-cut:polygon(0 0,100% 13%,75% 50%,100% 100%,50% 75%,13% 100%)}\n.card-inner{overflow:hidden;border-radius:14px}\n.card-corner{position:absolute;width:var(--corner-size);height:var(--corner-size);z-index:5;background:linear-gradient(135deg,#faf9eb,var(--corner-metal));border:1px solid #7b8075;clip-path:var(--corner-cut);filter:drop-shadow(0 1px 2px #0009)}\n.card-corner:after{content:'';position:absolute;inset:6px;background:color-mix(in srgb,var(--quality-accent) 80%,#4e584b);clip-path:var(--corner-cut)}\n.corner-tl{top:-7px;left:-7px}.corner-tr{top:-7px;right:-7px;transform:rotate(90deg)}.corner-br{bottom:-7px;right:-7px;transform:rotate(180deg)}.corner-bl{bottom:-7px;left:-7px;transform:rotate(270deg)}\n.quality-f{--corner-size:13.5px;--corner-metal:#a6afa4;--corner-cut:polygon(0 0,100% 0,100% 30%,30% 30%,30% 100%,0 100%)}\n.quality-f .card-corner:after{display:none}\n.quality-e{--corner-size:18px;--corner-metal:#a6c1a7;--corner-cut:polygon(0 0,100% 0,85% 28%,37% 37%,28% 85%,0 100%)}\n.quality-d{--corner-size:21px;--corner-metal:#87c3b1;--corner-cut:polygon(0 0,70% 10%,100% 45%,45% 100%,10% 70%)}\n.quality-c{--corner-metal:#acd5ed}\n.quality-b{--corner-size:25.5px;--corner-metal:#c2a5dc;--corner-cut:polygon(0 0,58% 14%,100% 4%,86% 47%,100% 100%,47% 86%,4% 100%,14% 58%)}\n.quality-a{--corner-size:27px;--corner-metal:#e2ba7b}\n.quality-s{--corner-size:30px;--corner-metal:#e2ca7c;--corner-cut:polygon(0 0,70% 9%,100% 0,86% 30%,67% 45%,100% 100%,45% 67%,30% 86%,0 100%,9% 70%)}\n.quality-ss{--corner-size:30px;--corner-metal:#dea595;--corner-cut:polygon(0 0,78% 14%,100% 43%,70% 70%,43% 100%,14% 78%)}\n.quality-sss{--corner-size:34.5px;--corner-metal:#d6b575;--corner-cut:polygon(0 0,55% 14%,100% 3%,82% 30%,68% 50%,100% 100%,50% 68%,30% 82%,3% 100%,14% 55%)}\n.quality-sss .card-corner:after{background:#9d79ac}\n.card-titlebar{position:relative;isolation:isolate;display:flex;align-items:center;gap:18px;min-height:98px;padding:27px 35px;background:linear-gradient(112deg,var(--quality-start),var(--quality-end));border-bottom:4px solid var(--quality-accent);color:#fffbee}\n.card-titlebar:before{content:'';position:absolute;inset:0;z-index:-1;background-image:radial-gradient(circle,#fff7 1.5px,transparent 2.2px),linear-gradient(30deg,transparent 65%,#ffffff1a 65.2%,transparent 65.6%);background-size:118px 95px,100% 100%;opacity:.55}\n.card-titlebar h1{min-width:0;flex:1;font-size:35px;line-height:1.4;letter-spacing:1px;font-weight:600;text-shadow:0 1px 2px #2c292348;overflow-wrap:anywhere}\n.title-glyph{font-size:31px;line-height:1;color:#fff6cecc}.title-star{font:38px Georgia;opacity:.75}\n.card-hero{position:relative;isolation:isolate;display:flex;align-items:center;justify-content:space-between;gap:24px;min-height:228px;padding:30px 36px;color:#eeeedd;background:radial-gradient(ellipse at 0 0,color-mix(in srgb,var(--quality-accent) 33%,transparent),transparent 65%),#272f30}\n.card-hero:before{content:'';position:absolute;inset:0;z-index:-1;opacity:.16;background-image:radial-gradient(circle,#d3d4bd 1px,transparent 1.2px);background-size:7.5px 7.5px;mask-image:linear-gradient(to bottom,#000,transparent 74%)}\n.hero-copy{flex:1;min-width:0;align-self:stretch;display:flex;flex-direction:column;justify-content:space-between}\n.hero-meta{display:flex;flex-direction:column;align-items:flex-start;gap:15px;margin-bottom:25px}\n.skill-type{font-size:24px;letter-spacing:3px;color:#cbcdbd}\n.quality-badge{display:inline-flex;align-items:center;gap:9px;padding:6px 14px;color:#fff5de;font-size:18px;font-weight:600;border:1px solid color-mix(in srgb,var(--quality-accent) 70%,transparent);border-radius:6px;background:color-mix(in srgb,var(--quality-accent) 17%,transparent)}\n.quality-badge b{color:var(--quality-end);font:700 24px Georgia,serif}\n.tags{display:flex;gap:7px;flex-wrap:wrap}\n.tag{font-size:20px;line-height:1.3;padding:6px 10px;border-radius:4px;overflow-wrap:anywhere;color:#bfcaed;background:#647bb434;border:1px solid #7d9cc62b}\n.tag.damage{color:#f5ae94;background:#b961433d;border-color:#b4614340}.tag.shield{color:#a3d7b7;background:#5186683d;border-color:#73a48a40}.tag.heal{color:#b5deb0;background:#668e4a3b;border-color:#89a47140}.tag.passive{color:#cebbef;background:#8367a63d;border-color:#9682b540}.tag.growth{color:#ecd291;background:#b1944540;border-color:#ab944e40}\n.hero-icon-wrap{--icon-size:120px;--magic-size:calc(var(--magic-base) * 1.5px);position:relative;flex-shrink:0;width:var(--magic-size);height:var(--magic-size)}\n.magic-circle{position:absolute;width:100%;height:100%;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--quality-end);pointer-events:none;overflow:visible}\n.magic-rays{stroke:currentColor;stroke-width:.9;opacity:.28}.magic-orbits{stroke:currentColor;stroke-width:1.2;opacity:.42}.magic-detail{stroke:currentColor;stroke-width:.9;opacity:.45}.magic-marks{fill:currentColor;opacity:.68}\n.magic-sss .magic-rays{stroke:#ddbc80;opacity:.49}.magic-sss .magic-detail{stroke:#bb9ccc;opacity:.64}.magic-sss .magic-marks{fill:#eed093;opacity:.88}\n.quality-s .magic-circle,.quality-ss .magic-circle,.quality-sss .magic-circle{filter:drop-shadow(0 0 4px color-mix(in srgb,var(--quality-accent) 30%,transparent))}\n.skill-artwork,.art-fallback{position:absolute;z-index:1;top:50%;left:50%;transform:translate(-50%,-50%);width:var(--icon-size);height:var(--icon-size);border:1.5px solid #bfc6b7;border-radius:50%;box-shadow:0 4px 13px #0007;object-fit:cover;object-position:center}\n.art-fallback{display:grid;place-items:center;color:var(--quality-end);font:44px 'KaiTi','SimSun',serif;background:radial-gradient(circle,#53615b,#1f2928)}\n.skill-icon-frame{position:absolute;top:50%;left:50%;width:calc(var(--icon-size) + 45px);height:calc(var(--icon-size) + 45px);transform:translate(-50%,-50%);z-index:2;color:var(--quality-accent);pointer-events:none;filter:drop-shadow(0 1px 1px #10181480)}\n.quality-sss .hero-icon-wrap{background:radial-gradient(ellipse,#ad85d525,transparent 68%)}\n.card-body{padding:32px 36px 34px;background:linear-gradient(145deg,#faf9f3,#eeeee7)}\n.effect-stats{display:grid;gap:9px;margin-bottom:27px}\n.stat-row{display:flex;align-items:center;justify-content:space-between;gap:18px;min-height:54px;padding:13px 18px;background:#e5e7de;border-radius:6px;font-size:24px}\n.stat-row>span{color:#626d57;flex:0 0 auto;max-width:45%;overflow-wrap:anywhere}\n.stat-row b{min-width:0;text-align:right;overflow-wrap:anywhere;font-size:30px;color:var(--quality-ink);font-weight:600}\n.stat-row.detail-stat b{font-size:26px;font-weight:500;color:#4f594b;background:none;-webkit-text-fill-color:currentColor}\n.stat-row .param-unit{font-size:24px;font-weight:400;color:#75816d;-webkit-text-fill-color:currentColor}\n.stat-value{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:flex-end;gap:9px}\n.stat-value .stat-unit{font-size:24px;font-weight:400;color:#737c68;-webkit-text-fill-color:currentColor}\n.section-heading{display:flex;align-items:center;gap:10px;margin:0 0 16px;font-size:24px;font-weight:500;letter-spacing:2px;color:#6f7d60}\n.section-heading:before{content:'\u25C6';font-size:10px;color:var(--quality-accent)}\n.description{font-size:28px;line-height:1.85;color:#515b4c;overflow-wrap:anywhere;white-space:pre-wrap}\n.description .number{color:var(--quality-ink);font-weight:600}.description .keyword{color:#907843;font-weight:500}\n.growth-section,.note-section{border-top:1px solid #d6dacd;margin-top:27px;padding-top:24px}\n.growth-section .description{color:#5c684f}\n.note-section .description{font-size:26px;color:#626d57}\n.quality-sss .card-titlebar{background:linear-gradient(110deg,#7c679e,#a77c95 50%,#cba770)}\n.quality-sss .quality-badge{border-color:#d3c192}\n.quality-sss .stat-number{background:linear-gradient(90deg,#936891,#ab884d);background-clip:text;-webkit-text-fill-color:transparent}\n";
2
+ export declare const qualityCardStyles = "\n*{box-sizing:border-box}\nhtml,body{margin:0;width:760px;background:#e9e9e2;font-family:'Microsoft YaHei','Noto Sans CJK SC',sans-serif;color:#303735}\n#capture{width:760px;padding:18px;background:radial-gradient(ellipse at 70% 10%,#f9f7efb8,transparent 55%),#e9e9e2}\nh1,h2,h3,p{margin:0}\n.detail-card{position:relative;width:100%;isolation:isolate;border:3px solid color-mix(in srgb,var(--quality-accent) 50%,#454a45);border-radius:18px;box-shadow:0 8px 18px #3f47321c;background:#f5f4ed;overflow:visible;--corner-size:24px;--corner-metal:#dce1d6;--corner-cut:polygon(0 0,100% 13%,75% 50%,100% 100%,50% 75%,13% 100%)}\n.card-inner{overflow:hidden;border-radius:14px}\n.card-corner{position:absolute;width:var(--corner-size);height:var(--corner-size);z-index:5;background:linear-gradient(135deg,#faf9eb,var(--corner-metal));border:1px solid #7b8075;clip-path:var(--corner-cut);filter:drop-shadow(0 1px 2px #0009)}\n.card-corner:after{content:'';position:absolute;inset:6px;background:color-mix(in srgb,var(--quality-accent) 80%,#4e584b);clip-path:var(--corner-cut)}\n.corner-tl{top:-7px;left:-7px}.corner-tr{top:-7px;right:-7px;transform:rotate(90deg)}.corner-br{bottom:-7px;right:-7px;transform:rotate(180deg)}.corner-bl{bottom:-7px;left:-7px;transform:rotate(270deg)}\n.quality-f{--corner-size:13.5px;--corner-metal:#a6afa4;--corner-cut:polygon(0 0,100% 0,100% 30%,30% 30%,30% 100%,0 100%)}\n.quality-f .card-corner:after{display:none}\n.quality-e{--corner-size:18px;--corner-metal:#a6c1a7;--corner-cut:polygon(0 0,100% 0,85% 28%,37% 37%,28% 85%,0 100%)}\n.quality-d{--corner-size:21px;--corner-metal:#87c3b1;--corner-cut:polygon(0 0,70% 10%,100% 45%,45% 100%,10% 70%)}\n.quality-c{--corner-metal:#acd5ed}\n.quality-b{--corner-size:25.5px;--corner-metal:#c2a5dc;--corner-cut:polygon(0 0,58% 14%,100% 4%,86% 47%,100% 100%,47% 86%,4% 100%,14% 58%)}\n.quality-a{--corner-size:27px;--corner-metal:#e2ba7b}\n.quality-s{--corner-size:30px;--corner-metal:#e2ca7c;--corner-cut:polygon(0 0,70% 9%,100% 0,86% 30%,67% 45%,100% 100%,45% 67%,30% 86%,0 100%,9% 70%)}\n.quality-ss{--corner-size:30px;--corner-metal:#dea595;--corner-cut:polygon(0 0,78% 14%,100% 43%,70% 70%,43% 100%,14% 78%)}\n.quality-sss{--corner-size:34.5px;--corner-metal:#d6b575;--corner-cut:polygon(0 0,55% 14%,100% 3%,82% 30%,68% 50%,100% 100%,50% 68%,30% 82%,3% 100%,14% 55%)}\n.quality-sss .card-corner:after{background:#9d79ac}\n.card-titlebar{position:relative;isolation:isolate;display:flex;align-items:center;gap:18px;min-height:98px;padding:27px 35px;background:linear-gradient(112deg,var(--quality-start),var(--quality-end));border-bottom:4px solid var(--quality-accent);color:#fffbee}\n.card-titlebar:before{content:'';position:absolute;inset:0;z-index:-1;background-image:radial-gradient(circle,#fff7 1.5px,transparent 2.2px),linear-gradient(30deg,transparent 65%,#ffffff1a 65.2%,transparent 65.6%);background-size:118px 95px,100% 100%;opacity:.55}\n.card-titlebar h1{min-width:0;flex:1;font-size:35px;line-height:1.4;letter-spacing:1px;font-weight:600;text-shadow:0 1px 2px #2c292348;overflow-wrap:anywhere}\n.title-glyph{font-size:31px;line-height:1;color:#fff6cecc}.title-star{font:38px Georgia;opacity:.75}\n.card-hero{position:relative;isolation:isolate;display:flex;align-items:center;justify-content:space-between;gap:24px;min-height:228px;padding:30px 36px;color:#eeeedd;background:radial-gradient(ellipse at 0 0,color-mix(in srgb,var(--quality-accent) 33%,transparent),transparent 65%),#272f30}\n.card-hero:before{content:'';position:absolute;inset:0;z-index:-1;opacity:.16;background-image:radial-gradient(circle,#d3d4bd 1px,transparent 1.2px);background-size:7.5px 7.5px;mask-image:linear-gradient(to bottom,#000,transparent 74%)}\n.hero-copy{flex:1;min-width:0;align-self:stretch;display:flex;flex-direction:column;justify-content:space-between}\n.hero-meta{display:flex;flex-direction:column;align-items:flex-start;gap:15px;margin-bottom:25px}\n.skill-type{font-size:24px;letter-spacing:3px;color:#cbcdbd}\n.quality-badge{display:inline-flex;align-items:center;gap:9px;padding:6px 14px;color:#fff5de;font-size:18px;font-weight:600;border:1px solid color-mix(in srgb,var(--quality-accent) 70%,transparent);border-radius:6px;background:color-mix(in srgb,var(--quality-accent) 17%,transparent)}\n.quality-badge b{color:var(--quality-end);font:700 24px Georgia,serif}\n.skill-stars{display:flex;align-items:center;flex-wrap:wrap;gap:3px;max-width:100%;margin-top:-8px}\n.skill-star{display:block;flex:0 0 40px;width:40px;height:40px;overflow:visible;filter:drop-shadow(0 1px 1px #10121780)}\n.star-shape{stroke:#24252b;stroke-width:2.4;stroke-linejoin:round}\n.star-gradient-light{stop-color:color-mix(in srgb,var(--quality-end) 55%,#fff)}.star-gradient-mid{stop-color:var(--quality-accent)}.star-gradient-shade{stop-color:var(--quality-start)}\n.star-facet-light{fill:#fff;opacity:.28}.star-facet-shade{fill:#151328;opacity:.18}.star-facet-glint{fill:none;stroke:#fff;stroke-width:.7;stroke-linejoin:round;opacity:.55}\n.skill-star.unlit .star-shape{fill:#303436;stroke:#171d22;stroke-width:2.6}\n.tags{display:flex;gap:7px;flex-wrap:wrap}\n.tag{font-size:20px;line-height:1.3;padding:6px 10px;border-radius:4px;overflow-wrap:anywhere;color:#bfcaed;background:#647bb434;border:1px solid #7d9cc62b}\n.tag.damage{color:#f5ae94;background:#b961433d;border-color:#b4614340}.tag.shield{color:#a3d7b7;background:#5186683d;border-color:#73a48a40}.tag.heal{color:#b5deb0;background:#668e4a3b;border-color:#89a47140}.tag.passive{color:#cebbef;background:#8367a63d;border-color:#9682b540}.tag.growth{color:#ecd291;background:#b1944540;border-color:#ab944e40}\n.hero-icon-wrap{--icon-size:120px;--magic-size:calc(var(--magic-base) * 1.5px);position:relative;flex-shrink:0;width:var(--magic-size);height:var(--magic-size)}\n.magic-circle{position:absolute;width:100%;height:100%;top:50%;left:50%;transform:translate(-50%,-50%);color:var(--quality-end);pointer-events:none;overflow:visible}\n.magic-rays{stroke:currentColor;stroke-width:.9;opacity:.28}.magic-orbits{stroke:currentColor;stroke-width:1.2;opacity:.42}.magic-detail{stroke:currentColor;stroke-width:.9;opacity:.45}.magic-marks{fill:currentColor;opacity:.68}\n.magic-sss .magic-rays{stroke:#ddbc80;opacity:.49}.magic-sss .magic-detail{stroke:#bb9ccc;opacity:.64}.magic-sss .magic-marks{fill:#eed093;opacity:.88}\n.quality-s .magic-circle,.quality-ss .magic-circle,.quality-sss .magic-circle{filter:drop-shadow(0 0 4px color-mix(in srgb,var(--quality-accent) 30%,transparent))}\n.skill-artwork,.art-fallback{position:absolute;z-index:1;top:50%;left:50%;transform:translate(-50%,-50%);width:var(--icon-size);height:var(--icon-size);border:1.5px solid #bfc6b7;border-radius:50%;box-shadow:0 4px 13px #0007;object-fit:cover;object-position:center}\n.art-fallback{display:grid;place-items:center;color:var(--quality-end);font:44px 'KaiTi','SimSun',serif;background:radial-gradient(circle,#53615b,#1f2928)}\n.skill-icon-frame{position:absolute;top:50%;left:50%;width:calc(var(--icon-size) + 45px);height:calc(var(--icon-size) + 45px);transform:translate(-50%,-50%);z-index:2;color:var(--quality-accent);pointer-events:none;filter:drop-shadow(0 1px 1px #10181480)}\n.quality-sss .hero-icon-wrap{background:radial-gradient(ellipse,#ad85d525,transparent 68%)}\n.card-body{padding:32px 36px 34px;background:linear-gradient(145deg,#faf9f3,#eeeee7)}\n.effect-stats{display:grid;gap:9px;margin-bottom:27px}\n.stat-row{display:flex;align-items:center;justify-content:space-between;gap:18px;min-height:54px;padding:13px 18px;background:#e5e7de;border-radius:6px;font-size:24px}\n.stat-row>span{color:#626d57;flex:0 0 auto;max-width:45%;overflow-wrap:anywhere}\n.stat-row b{min-width:0;text-align:right;overflow-wrap:anywhere;font-size:30px;color:var(--quality-ink);font-weight:600}\n.stat-row.detail-stat b{font-size:26px;font-weight:500;color:#4f594b;background:none;-webkit-text-fill-color:currentColor}\n.stat-row .param-unit{font-size:24px;font-weight:400;color:#75816d;-webkit-text-fill-color:currentColor}\n.stat-value{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:flex-end;gap:9px}\n.stat-value .stat-unit{font-size:24px;font-weight:400;color:#737c68;-webkit-text-fill-color:currentColor}\n.section-heading{display:flex;align-items:center;gap:10px;margin:0 0 16px;font-size:24px;font-weight:500;letter-spacing:2px;color:#6f7d60}\n.section-heading:before{content:'\u25C6';font-size:10px;color:var(--quality-accent)}\n.description{font-size:28px;line-height:1.85;color:#515b4c;overflow-wrap:anywhere;white-space:pre-wrap}\n.description .number{color:var(--quality-ink);font-weight:600}.description .keyword{color:#907843;font-weight:500}\n.growth-section,.note-section{border-top:1px solid #d6dacd;margin-top:27px;padding-top:24px}\n.growth-section .description{color:#5c684f}\n.note-section .description{font-size:26px;color:#626d57}\n.quality-sss .card-titlebar{background:linear-gradient(110deg,#7c679e,#a77c95 50%,#cba770)}\n.quality-sss .quality-badge{border-color:#d3c192}\n.quality-sss .stat-number{background:linear-gradient(90deg,#936891,#ab884d);background-clip:text;-webkit-text-fill-color:transparent}\n";
@@ -1,6 +1,3 @@
1
- import type { Session } from 'koishi';
2
1
  import { type IMessage } from '../../shared/face';
3
- import type { MsgMessage } from '../../shared/bot/MsgMessage';
4
2
  /** Render command help and ordinary prose with clickable bracketed commands. */
5
3
  export declare function renderQQCommandMarkdown(data: IMessage, showGreeting: boolean): string;
6
- export declare function sendQQCommand(session: Session, data: MsgMessage): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  import type { Session } from 'koishi';
2
2
  import { type IMessage, type PageNavData } from '../../shared/face';
3
3
  import type { MsgMessage } from '../../shared/bot/MsgMessage';
4
- import { type QQKeyboard } from './qqMessageButtons';
4
+ import type { QQKeyboard } from './qqMessageButtons';
5
5
  export { escapeMarkdown, renderQQTitle } from './qqMarkdownHelpers';
6
6
  export declare function inlineCommandUrl(command: string): string;
7
7
  export declare function hasInlineCommands(content: string): boolean;
@@ -11,7 +11,5 @@ export declare function pageCommandLines(nav: PageNavData): string[];
11
11
  export declare function renderQQPageMarkdown(data: IMessage, showGreeting: boolean): string;
12
12
  /** Returns false if this is not the matching QQ session or the adapter has no native API. */
13
13
  export declare function canSendQQPage(session: Session | undefined, data: MsgMessage): session is Session;
14
- /** Send a native QQ Markdown page with inline command links. */
15
- export declare function sendQQPage(session: Session, data: MsgMessage): Promise<void>;
16
14
  /** Reuse the adapter's native reply API for structured Markdown messages. */
17
15
  export declare function sendQQMarkdown(session: Session, markdown: string, keyboard?: QQKeyboard): Promise<void>;
@@ -3,7 +3,7 @@ export declare function hasRewardLines(data: Pick<IMessage, 'line'>): boolean;
3
3
  /** The message marker also covers empty rewards and attachments already claimed. */
4
4
  export declare function isRewardMessage(data: Pick<IMessage, 'line' | 'reward'>): boolean;
5
5
  export declare function renderRewardTitle(content: string): string;
6
- /** Reward names remain literal text; only the final signed quantity is emphasized. */
7
- export declare function renderRewardLine(content: string): string;
6
+ /** Reward names stay literal; a supplied action links the name, keeping its signed quantity emphasized. */
7
+ export declare function renderRewardLine(content: string, command?: string): string;
8
8
  /** Reward cards stay compact; mail keeps its content before the attachment quote. */
9
9
  export declare function renderQQRewardMarkdown(data: IMessage, showGreeting: boolean): string;
@@ -1,7 +1,4 @@
1
- import type { Session } from 'koishi';
2
1
  import { type IMessage } from '../../shared/face';
3
- import type { MsgMessage } from '../../shared/bot/MsgMessage';
4
2
  export declare function hasStoryCodeBlock(data: Pick<IMessage, 'line'>): boolean;
5
3
  /** Marked stories keep their complete text; settled rewards use the shared reward card. */
6
4
  export declare function renderQQStoryMarkdown(data: IMessage, showGreeting: boolean): string;
7
- export declare function sendQQStory(session: Session, data: MsgMessage): Promise<void>;
@@ -1,6 +1,3 @@
1
- import type { Session } from 'koishi';
2
1
  import { type IMessage } from '../../shared/face';
3
- import type { MsgMessage } from '../../shared/bot/MsgMessage';
4
2
  /** Render only explicitly marked transactions; line content never enables this channel. */
5
3
  export declare function renderQQTransactionMarkdown(data: IMessage, showGreeting: boolean): string;
6
- export declare function sendQQTransaction(session: Session, data: MsgMessage): Promise<void>;
@@ -4,6 +4,5 @@ export interface SigninPortraitArtwork {
4
4
  name: string;
5
5
  framing: 'full-body' | 'waist-up';
6
6
  }
7
- export declare const SIGNIN_CARD_WIDTH = 760;
8
7
  /** Pure markup shared by the real bot screenshot and the editor's chat preview. */
9
8
  export declare function signinCardHtml(card: SigninCardData, artwork?: Array<string | undefined>, portrait?: SigninPortraitArtwork): string;
@@ -175,6 +175,11 @@ export interface EquipmentCardData {
175
175
  }>;
176
176
  notes?: string[];
177
177
  authorName?: string;
178
+ /** 独立随机副词条,旧装备缺省时仅展示基础属性。 */
179
+ randomAffixes?: Array<{
180
+ name: string;
181
+ value: string;
182
+ }>;
178
183
  }
179
184
  export interface LeaderboardImageData {
180
185
  backgroundUrl?: string;
@@ -217,6 +222,10 @@ export interface SkillCardData {
217
222
  tags: string[];
218
223
  sources: string[];
219
224
  note: string;
225
+ /** 当前技能品质内已点亮的星数;缺省为旧版卡片。 */
226
+ stars?: number;
227
+ /** 当前品质允许的最高星数;0 表示该品质没有额外星级。 */
228
+ maxStars?: number;
220
229
  }
221
230
  /** 客户端按此字段选择发送通道;第一阶段只有文字 **/
222
231
  export declare enum MessageHandleType {
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.265",
4
+ "version": "1.0.266",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -15,7 +15,10 @@ export function equipmentCardHtml(card: EquipmentCardData, artwork?: string, unk
15
15
  ].join('')
16
16
  const affixes = card.affixes?.length
17
17
  ? '<div class="detail-list">' + card.affixes.map(affix => cardStatRow(affix.name, affix.value)).join('') + '</div>'
18
- : '<p class="description">暂无附加属性</p>'
18
+ : '<p class="description">暂无基础属性</p>'
19
+ const randomAffixes = card.randomAffixes?.length
20
+ ? cardSection('随机词条', '<div class="detail-list">' + card.randomAffixes.map(affix => cardStatRow(affix.name, affix.value)).join('') + '</div>')
21
+ : ''
19
22
  const skills = card.skills?.length
20
23
  ? cardSection('附带技能', '<div class="detail-list">' + card.skills.map(skill => cardStatRow(skill.name, '')).join('') + '</div>')
21
24
  : ''
@@ -34,6 +37,6 @@ export function equipmentCardHtml(card: EquipmentCardData, artwork?: string, unk
34
37
  fallbackGlyph: '?',
35
38
  sheetClass: 'equipment-sheet',
36
39
  tags: unknownFallback || !artwork ? ['外观暂未显现'] : [],
37
- body: '<div class="effect-stats">' + details + '</div>' + cardSection('附加属性', affixes) + skills + description + notes,
40
+ body: '<div class="effect-stats">' + details + '</div>' + cardSection('基础属性', affixes) + randomAffixes + skills + description + notes,
38
41
  })
39
42
  }
@@ -31,6 +31,12 @@ h1,h2,h3,p{margin:0}
31
31
  .skill-type{font-size:24px;letter-spacing:3px;color:#cbcdbd}
32
32
  .quality-badge{display:inline-flex;align-items:center;gap:9px;padding:6px 14px;color:#fff5de;font-size:18px;font-weight:600;border:1px solid color-mix(in srgb,var(--quality-accent) 70%,transparent);border-radius:6px;background:color-mix(in srgb,var(--quality-accent) 17%,transparent)}
33
33
  .quality-badge b{color:var(--quality-end);font:700 24px Georgia,serif}
34
+ .skill-stars{display:flex;align-items:center;flex-wrap:wrap;gap:3px;max-width:100%;margin-top:-8px}
35
+ .skill-star{display:block;flex:0 0 40px;width:40px;height:40px;overflow:visible;filter:drop-shadow(0 1px 1px #10121780)}
36
+ .star-shape{stroke:#24252b;stroke-width:2.4;stroke-linejoin:round}
37
+ .star-gradient-light{stop-color:color-mix(in srgb,var(--quality-end) 55%,#fff)}.star-gradient-mid{stop-color:var(--quality-accent)}.star-gradient-shade{stop-color:var(--quality-start)}
38
+ .star-facet-light{fill:#fff;opacity:.28}.star-facet-shade{fill:#151328;opacity:.18}.star-facet-glint{fill:none;stroke:#fff;stroke-width:.7;stroke-linejoin:round;opacity:.55}
39
+ .skill-star.unlit .star-shape{fill:#303436;stroke:#171d22;stroke-width:2.6}
34
40
  .tags{display:flex;gap:7px;flex-wrap:wrap}
35
41
  .tag{font-size:20px;line-height:1.3;padding:6px 10px;border-radius:4px;overflow-wrap:anywhere;color:#bfcaed;background:#647bb434;border:1px solid #7d9cc62b}
36
42
  .tag.damage{color:#f5ae94;background:#b961433d;border-color:#b4614340}.tag.shield{color:#a3d7b7;background:#5186683d;border-color:#73a48a40}.tag.heal{color:#b5deb0;background:#668e4a3b;border-color:#89a47140}.tag.passive{color:#cebbef;background:#8367a63d;border-color:#9682b540}.tag.growth{color:#ecd291;background:#b1944540;border-color:#ab944e40}
@@ -1,9 +1,6 @@
1
1
  import { withoutMessageButtonFallbacks } from '../../shared/bot/analysisMsg'
2
- import { renderQQKeyboard } from './qqMessageButtons'
3
- import type { Session } from 'koishi'
4
2
  import { MessageType, type IMessage } from '../../shared/face'
5
- import type { MsgMessage } from '../../shared/bot/MsgMessage'
6
- import { escapeMarkdown, renderInlineCommands, renderQQPageMarkdown, renderQQTitle, sendQQMarkdown } from './qqPageMessage'
3
+ import { escapeMarkdown, renderInlineCommands, renderQQPageMarkdown, renderQQTitle } from './qqPageMessage'
7
4
  import { isRewardMessage, renderRewardLine, renderRewardTitle } from './qqRewardMessage'
8
5
 
9
6
  /** Render command help and ordinary prose with clickable bracketed commands. */
@@ -73,7 +70,3 @@ export function renderQQCommandMarkdown(data: IMessage, showGreeting: boolean):
73
70
  flushRewards()
74
71
  return sections.join('\n\n') || ' '
75
72
  }
76
-
77
- export async function sendQQCommand(session: Session, data: MsgMessage): Promise<void> {
78
- await sendQQMarkdown(session, renderQQCommandMarkdown(data, session.event?.channel?.type === 0), renderQQKeyboard(data))
79
- }
@@ -36,6 +36,7 @@ export function renderQQMessagePreview(data: IMessage, showGreeting = false): QQ
36
36
  if (hasStoryCodeBlock(data)) return native('story', renderQQStoryMarkdown(data, showGreeting))
37
37
  if (data.commandHelp) return native('command', renderQQCommandMarkdown(data, showGreeting))
38
38
  if (data.transaction) return native('transaction', renderQQTransactionMarkdown(data, showGreeting))
39
+ if (data.mail) return native('reward', renderQQRewardMarkdown(data, showGreeting))
39
40
  if (data.pageNav) return native('page', renderQQPageMarkdown(data, showGreeting))
40
41
  if (isRewardMessage(data)) return native('reward', renderQQRewardMarkdown(data, showGreeting))
41
42
  if (battleLogMessageLinks(data).length || data.line.some(item => (item.type === MessageType.line || item.type === MessageType.error) && hasInlineCommands(item.content))) {
@@ -2,7 +2,7 @@ import type { Session } from 'koishi'
2
2
  import { MessageType, type IMessage, type PageNavData } from '../../shared/face'
3
3
  import type { MsgMessage } from '../../shared/bot/MsgMessage'
4
4
  import { withoutMessageButtonFallbacks } from '../../shared/bot/analysisMsg'
5
- import { renderQQKeyboard, type QQKeyboard } from './qqMessageButtons'
5
+ import type { QQKeyboard } from './qqMessageButtons'
6
6
  import { compactDisplayText, escapeMarkdown, renderQQTitle } from './qqMarkdownHelpers'
7
7
  import { isRewardMessage, renderRewardLine, renderRewardTitle } from './qqRewardMessage'
8
8
  import { artworkIdentity } from '../skillArtwork'
@@ -57,7 +57,6 @@ export function inlineCommandUrl(command: string): string {
57
57
  return `mqqapi://aio/inlinecmd?command=${encoded}&enter=true&reply=false`
58
58
  }
59
59
 
60
-
61
60
  /** Bracketed commands may appear anywhere in prose, including escaped brackets. */
62
61
  function inlineCommandSpans(content: string): InlineCommandSpan[] {
63
62
  const spans = bracketedCommandSpans(content)
@@ -359,12 +358,6 @@ export function canSendQQPage(session: Session | undefined, data: MsgMessage): s
359
358
  return !!internal?.sendMessage && !!internal.sendPrivateMessage
360
359
  }
361
360
 
362
- /** Send a native QQ Markdown page with inline command links. */
363
- export async function sendQQPage(session: Session, data: MsgMessage): Promise<void> {
364
- const markdown = renderQQPageMarkdown(data, session.event?.channel?.type === 0)
365
- await sendQQMarkdown(session, markdown, renderQQKeyboard(data))
366
- }
367
-
368
361
  /** Reuse the adapter's native reply API for structured Markdown messages. */
369
362
  export async function sendQQMarkdown(session: Session, markdown: string, keyboard?: QQKeyboard): Promise<void> {
370
363
  const internal = (session.bot as typeof session.bot & { internal: QQInternal }).internal
@@ -2,6 +2,7 @@ import { withoutMessageButtonFallbacks } from '../../shared/bot/analysisMsg'
2
2
  import { MessageType, type IMessage } from '../../shared/face'
3
3
  import { compactDisplayText, escapeMarkdown } from './qqMarkdownHelpers'
4
4
  import { appendMissingBattleLogLinks, renderBattleLogLinkText } from './qqBattleLogLinks'
5
+ import { inlineCommandUrl } from './qqPageMessage'
5
6
 
6
7
  export function hasRewardLines(data: Pick<IMessage, 'line'>): boolean {
7
8
  return data.line.some(item => item.type === MessageType.line && item.reward === true)
@@ -16,20 +17,24 @@ export function renderRewardTitle(content: string): string {
16
17
  return `**${escapeMarkdown(compactDisplayText(content))}**`
17
18
  }
18
19
 
19
- /** Reward names remain literal text; only the final signed quantity is emphasized. */
20
- export function renderRewardLine(content: string): string {
20
+ /** Reward names stay literal; a supplied action links the name, keeping its signed quantity emphasized. */
21
+ export function renderRewardLine(content: string, command?: string): string {
21
22
  return content.replace(/\r\n?/g, '\n').split('\n').filter(line => line.trim()).map(line => {
22
23
  const text = line.trim()
23
24
  const quantity = /([++\-−×xX]\s*\d+(?:\.\d+)?)$/.exec(text)
24
- const formatted = quantity
25
- ? escapeMarkdown(text.slice(0, quantity.index)) + `**${escapeMarkdown(quantity[1])}**`
26
- : escapeMarkdown(text)
25
+ const label = quantity ? text.slice(0, quantity.index) : text
26
+ const name = label.trimEnd()
27
+ const formattedName = command && name
28
+ ? `[${escapeMarkdown(name)}](${inlineCommandUrl(command)})${escapeMarkdown(label.slice(name.length))}`
29
+ : escapeMarkdown(label)
30
+ const formatted = formattedName + (quantity ? `**${escapeMarkdown(quantity[1])}**` : '')
27
31
  return '> ' + formatted
28
32
  }).join(' \n')
29
33
  }
30
34
 
31
35
  /** Mail sections retain their order so each attachment stays with its own message. */
32
36
  function renderQQMailMarkdown(data: IMessage): string {
37
+ const commands = new Map(data.pageNav?.entries?.map(entry => [entry.lineIndex, entry.viewCommand]) ?? [])
33
38
  const sections: string[] = []
34
39
  const text: string[] = []
35
40
  const rewards: string[] = []
@@ -39,7 +44,7 @@ function renderQQMailMarkdown(data: IMessage): string {
39
44
  const flushRewards = () => {
40
45
  if (rewards.length) sections.push(rewards.splice(0).join(' \n'))
41
46
  }
42
- for (const item of data.line) {
47
+ for (const [lineIndex, item] of data.line.entries()) {
43
48
  if (item.type === MessageType.title || (item.type === MessageType.line && item.mailPart === 'title')) {
44
49
  flushText()
45
50
  flushRewards()
@@ -51,7 +56,7 @@ function renderQQMailMarkdown(data: IMessage): string {
51
56
  .map(line => '> ' + escapeMarkdown(line)).join(' \n'))
52
57
  } else if (item.type === MessageType.line && item.reward) {
53
58
  flushText()
54
- const reward = renderRewardLine(item.content)
59
+ const reward = renderRewardLine(item.content, commands.get(lineIndex))
55
60
  if (reward) rewards.push(reward)
56
61
  } else {
57
62
  flushRewards()
@@ -1,9 +1,6 @@
1
1
  import { withoutMessageButtonFallbacks } from '../../shared/bot/analysisMsg'
2
- import { renderQQKeyboard } from './qqMessageButtons'
3
- import type { Session } from 'koishi'
4
2
  import { MessageType, type IMessage } from '../../shared/face'
5
- import type { MsgMessage } from '../../shared/bot/MsgMessage'
6
- import { escapeMarkdown, renderInlineCommands, renderQQTitle, sendQQMarkdown } from './qqPageMessage'
3
+ import { escapeMarkdown, renderInlineCommands, renderQQTitle } from './qqPageMessage'
7
4
  import { renderBattleLogLinkText } from './qqBattleLogLinks'
8
5
  import { isRewardMessage, renderQQRewardMarkdown, renderRewardLine, renderRewardTitle } from './qqRewardMessage'
9
6
 
@@ -76,7 +73,3 @@ export function renderQQStoryMarkdown(data: IMessage, showGreeting: boolean): st
76
73
  flushRewards()
77
74
  return sections.join('\n\n') || ' '
78
75
  }
79
-
80
- export async function sendQQStory(session: Session, data: MsgMessage): Promise<void> {
81
- await sendQQMarkdown(session, renderQQStoryMarkdown(data, session.event?.channel?.type === 0), renderQQKeyboard(data))
82
- }
@@ -1,9 +1,6 @@
1
1
  import { withoutMessageButtonFallbacks } from '../../shared/bot/analysisMsg'
2
- import { renderQQKeyboard } from './qqMessageButtons'
3
- import type { Session } from 'koishi'
4
2
  import { MessageType, type IMessage } from '../../shared/face'
5
- import type { MsgMessage } from '../../shared/bot/MsgMessage'
6
- import { escapeMarkdown, inlineCommandUrl, renderInlineCommands, renderQQTitle, sendQQMarkdown } from './qqPageMessage'
3
+ import { escapeMarkdown, inlineCommandUrl, renderInlineCommands, renderQQTitle } from './qqPageMessage'
7
4
  import { isRewardMessage, renderRewardLine, renderRewardTitle } from './qqRewardMessage'
8
5
 
9
6
  /** Render only explicitly marked transactions; line content never enables this channel. */
@@ -91,7 +88,3 @@ export function renderQQTransactionMarkdown(data: IMessage, showGreeting: boolea
91
88
  flush()
92
89
  return sections.join('\n\n') || ' '
93
90
  }
94
-
95
- export async function sendQQTransaction(session: Session, data: MsgMessage): Promise<void> {
96
- await sendQQMarkdown(session, renderQQTransactionMarkdown(data, session.event?.channel?.type === 0), renderQQKeyboard(data))
97
- }
@@ -8,7 +8,6 @@ export interface SigninPortraitArtwork {
8
8
  framing: 'full-body' | 'waist-up'
9
9
  }
10
10
 
11
- export const SIGNIN_CARD_WIDTH = 760
12
11
  const check = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m20 6-11 11-5-5"/></svg>'
13
12
  const progressCheck = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 11 3 3L22 4"/><path d="M21 12v1a9 9 0 1 1-5.34-8.23"/></svg>'
14
13
  const clock = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><path d="M12 6v6h4"/></svg>'
@@ -24,6 +24,25 @@ function tagClass(tag: string): string {
24
24
  return ''
25
25
  }
26
26
 
27
+ /** Each star is a small faceted gem; progression numbers stay in the accessible label. */
28
+ function skillStars(card: SkillCardData): string {
29
+ if (card.stars === undefined && card.maxStars === undefined) return ''
30
+ const maxStars = Math.min(20, Math.max(0, Math.floor(Number.isFinite(card.maxStars) ? card.maxStars! : Number.isFinite(card.stars) ? card.stars! : 0)))
31
+ if (!maxStars) return ''
32
+ const stars = Math.min(maxStars, Math.max(0, Math.floor(Number.isFinite(card.stars) ? card.stars! : 0)))
33
+ const outline = '20,3 25.1,13.4 36.6,15.1 28.3,23.2 30.3,34.7 20,29.3 9.7,34.7 11.7,23.2 3.4,15.1 14.9,13.4'
34
+ const icons = Array.from({ length: maxStars }, (_, index) => {
35
+ const lit = index < stars
36
+ const gradient = 'skill-star-fill-' + index
37
+ return '<svg class="skill-star ' + (lit ? 'lit' : 'unlit') + '" viewBox="0 0 40 40" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg">'
38
+ + (lit ? '<defs><linearGradient id="' + gradient + '" x1="0" y1="0" x2="0.7" y2="1"><stop class="star-gradient-light"/><stop offset=".48" class="star-gradient-mid"/><stop offset="1" class="star-gradient-shade"/></linearGradient></defs>' : '')
39
+ + '<polygon class="star-shape" points="' + outline + '"' + (lit ? ' fill="url(#' + gradient + ')"' : '') + '/>'
40
+ + (lit ? '<path class="star-facet-light" d="M20 5.7 20 20.6 5.9 16.1 15.9 14.6Z"/><path class="star-facet-shade" d="M20 20.6 28.6 32.3 26.9 22.7 34.1 16.1Z"/><path class="star-facet-glint" d="M20 6.2 20 20.6 12 31.9 13.3 23 6.5 16.6 16 15.2Z"/>' : '')
41
+ + '</svg>'
42
+ }).join('')
43
+ return '<div class="skill-stars" role="img" aria-label="' + stars + ' / ' + maxStars + ' 星">' + icons + '</div>'
44
+ }
45
+
27
46
  /** Compact skill detail with a small existing icon and quality-specific ornaments. */
28
47
  export function skillCardHtml(card: SkillCardData, _frame = '', artwork?: string): string {
29
48
  const quality = normalizeCardQuality(card.quality)
@@ -42,13 +61,13 @@ export function skillCardHtml(card: SkillCardData, _frame = '', artwork?: string
42
61
  <div id="capture" class="skill-sheet"><article class="detail-card skill-card quality-${quality.toLowerCase()}" style="${qualityStyle(quality)}">
43
62
  <i class="card-corner corner-tl" aria-hidden="true"></i><i class="card-corner corner-tr" aria-hidden="true"></i><i class="card-corner corner-bl" aria-hidden="true"></i><i class="card-corner corner-br" aria-hidden="true"></i>
44
63
  <div class="card-inner"><header class="card-titlebar"><span class="title-glyph" aria-hidden="true">◇</span><h1>${escape(card.name)}</h1><span class="title-star" aria-hidden="true">✦</span></header>
45
- <div class="card-hero"><div class="hero-copy"><div class="hero-meta"><span class="skill-type">${card.passive ? '被动技能' : '主动技能'}</span><span class="quality-badge"><b>${quality}</b> 级</span></div><div class="tags">${tags.map(tag => `<span class="tag ${tagClass(tag)}">${escape(tag)}</span>`).join('')}</div></div><div class="hero-icon-wrap">${qualityMagicCircle(quality)}<div class="art-fallback" aria-hidden="true">${card.passive ? '静' : '势'}</div>${artwork ? `<img class="skill-artwork" alt="${escape(card.name)}图标" src="${escape(artwork)}">` : ''}${qualityIconFrame(quality)}</div></div>
64
+ <div class="card-hero"><div class="hero-copy"><div class="hero-meta"><span class="skill-type">${card.passive ? '被动技能' : '主动技能'}</span><span class="quality-badge"><b>${quality}</b> 级</span>${skillStars(card)}</div><div class="tags">${tags.map(tag => `<span class="tag ${tagClass(tag)}">${escape(tag)}</span>`).join('')}</div></div><div class="hero-icon-wrap">${qualityMagicCircle(quality)}<div class="art-fallback" aria-hidden="true">${card.passive ? '静' : '势'}</div>${artwork ? `<img class="skill-artwork" alt="${escape(card.name)}图标" src="${escape(artwork)}">` : ''}${qualityIconFrame(quality)}</div></div>
46
65
  <div class="card-body"><div class="effect-stats">
47
66
  ${row('消耗', escape(card.cost || '无消耗'), true)}
48
67
  ${row('冷却', cooldownValue, true)}
49
68
  ${power ? row('战力', escape(power.replace(/^(?:🔥\s*)?战力\s*[::]\s*/, '')), true) : ''}
50
69
  ${(card.stats || []).slice(0, 3).map(stat => row(stat.label, statValue(stat.value))).join('')}
51
- </div><section class="effect"><h2 class="section-heading">技能效果</h2><p class="description">${effectText(effect)}</p></section>
70
+ </div><section class="effect"><h2 class="section-heading">${escape(card.mechanism || '技能效果')}</h2><p class="description">${effectText(effect)}</p></section>
52
71
  ${growth ? `<section class="growth-section"><h2 class="section-heading">永久成长 · 仅玩家</h2><p class="description">${effectText(growth)}</p></section>` : ''}
53
72
  ${otherNotes ? `<section class="note-section"><h2 class="section-heading">附加信息</h2><p class="description">${escape(otherNotes)}</p></section>` : ''}
54
73
  </div></div></article></div></body></html>`
@@ -25,7 +25,7 @@ export interface ServiceType {
25
25
  }
26
26
 
27
27
  export const serviceProto: ServiceProto<ServiceType> = {
28
- "version": 42,
28
+ "version": 44,
29
29
  "services": [
30
30
  {
31
31
  "id": 0,
@@ -889,6 +889,22 @@ export const serviceProto: ServiceProto<ServiceType> = {
889
889
  "type": {
890
890
  "type": "String"
891
891
  }
892
+ },
893
+ {
894
+ "id": 15,
895
+ "name": "stars",
896
+ "type": {
897
+ "type": "Number"
898
+ },
899
+ "optional": true
900
+ },
901
+ {
902
+ "id": 16,
903
+ "name": "maxStars",
904
+ "type": {
905
+ "type": "Number"
906
+ },
907
+ "optional": true
892
908
  }
893
909
  ]
894
910
  },
@@ -1150,6 +1166,33 @@ export const serviceProto: ServiceProto<ServiceType> = {
1150
1166
  "type": "String"
1151
1167
  },
1152
1168
  "optional": true
1169
+ },
1170
+ {
1171
+ "id": 13,
1172
+ "name": "randomAffixes",
1173
+ "type": {
1174
+ "type": "Array",
1175
+ "elementType": {
1176
+ "type": "Interface",
1177
+ "properties": [
1178
+ {
1179
+ "id": 0,
1180
+ "name": "name",
1181
+ "type": {
1182
+ "type": "String"
1183
+ }
1184
+ },
1185
+ {
1186
+ "id": 1,
1187
+ "name": "value",
1188
+ "type": {
1189
+ "type": "String"
1190
+ }
1191
+ }
1192
+ ]
1193
+ }
1194
+ },
1195
+ "optional": true
1153
1196
  }
1154
1197
  ]
1155
1198
  },
@@ -112,6 +112,8 @@ export interface EquipmentCardData {
112
112
  skills: Array<{ name: string; rank: string }>;
113
113
  notes?: string[];
114
114
  authorName?: string;
115
+ /** 独立随机副词条,旧装备缺省时仅展示基础属性。 */
116
+ randomAffixes?: Array<{ name: string; value: string }>;
115
117
  }
116
118
  export interface LeaderboardImageData {
117
119
  backgroundUrl?: string;
@@ -139,6 +141,10 @@ export interface SkillCardData {
139
141
  tags: string[];
140
142
  sources: string[];
141
143
  note: string;
144
+ /** 当前技能品质内已点亮的星数;缺省为旧版卡片。 */
145
+ stars?: number;
146
+ /** 当前品质允许的最高星数;0 表示该品质没有额外星级。 */
147
+ maxStars?: number;
142
148
  }
143
149
  /** 客户端按此字段选择发送通道;第一阶段只有文字 **/
144
150
  export enum MessageHandleType {