koishi-plugin-dew-bot 1.0.261 → 1.0.263
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 +316 -105
- package/lib/plugin/characterCardAssets.d.ts +3 -1
- package/lib/plugin/characterCardStyles.d.ts +1 -1
- package/lib/plugin/serverHandel/imageMessageFollowup.d.ts +9 -0
- package/lib/plugin/serverHandel/qqMessageButtons.d.ts +27 -0
- package/lib/plugin/serverHandel/qqMessagePreview.d.ts +3 -0
- package/lib/plugin/serverHandel/qqPageMessage.d.ts +2 -1
- package/lib/shared/bot/analysisMsg.d.ts +5 -0
- package/lib/shared/bot/inlineCommands.d.ts +11 -0
- package/lib/shared/face/index.d.ts +16 -1
- package/package.json +1 -1
- package/src/plugin/bloodlineCardTemplate.ts +2 -4
- package/src/plugin/cardLayout.ts +15 -1
- package/src/plugin/characterCardAssets.ts +9 -1
- package/src/plugin/characterCardStyles.ts +6 -4
- package/src/plugin/characterCardTemplate.ts +9 -8
- package/src/plugin/serverHandel/image.ts +5 -5
- package/src/plugin/serverHandel/imageMessageFollowup.ts +23 -0
- package/src/plugin/serverHandel/qqCommandMessage.ts +10 -3
- package/src/plugin/serverHandel/qqMessageButtons.ts +38 -0
- package/src/plugin/serverHandel/qqMessagePreview.ts +19 -8
- package/src/plugin/serverHandel/qqPageMessage.ts +9 -21
- package/src/plugin/serverHandel/qqRewardMessage.ts +2 -0
- package/src/plugin/serverHandel/qqStoryMessage.ts +4 -1
- package/src/plugin/serverHandel/qqTransactionMessage.ts +4 -1
- package/src/plugin/serverHandel/text.ts +1 -1
- package/src/shared/bot/analysisMsg.ts +38 -1
- package/src/shared/bot/inlineCommands.ts +39 -0
- package/src/shared/bot/serviceProto.ts +80 -1
- package/src/shared/face/index.ts +18 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
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,9 @@
|
|
|
1
|
+
import type { IMessage } from '../../shared/face';
|
|
2
|
+
import { type QQKeyboard } from './qqMessageButtons';
|
|
3
|
+
export interface QQImageFollowup {
|
|
4
|
+
text: string;
|
|
5
|
+
markdown?: string;
|
|
6
|
+
keyboard?: QQKeyboard;
|
|
7
|
+
}
|
|
8
|
+
/** Images are sent first; actions stay outside the image and retain a plain fallback. */
|
|
9
|
+
export declare function renderQQImageFollowup(data: IMessage): QQImageFollowup | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IMessage } from '../../shared/face';
|
|
2
|
+
export interface QQKeyboard {
|
|
3
|
+
content: {
|
|
4
|
+
rows: Array<{
|
|
5
|
+
buttons: Array<{
|
|
6
|
+
id: string;
|
|
7
|
+
render_data: {
|
|
8
|
+
label: string;
|
|
9
|
+
visited_label: string;
|
|
10
|
+
style: 0 | 1;
|
|
11
|
+
};
|
|
12
|
+
action: {
|
|
13
|
+
type: 2;
|
|
14
|
+
permission: {
|
|
15
|
+
type: 2;
|
|
16
|
+
};
|
|
17
|
+
data: string;
|
|
18
|
+
enter: true;
|
|
19
|
+
reply: false;
|
|
20
|
+
unsupport_tips: string;
|
|
21
|
+
};
|
|
22
|
+
}>;
|
|
23
|
+
}>;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** Explicit actions use QQ's native keyboard; ordinary command links stay inline. */
|
|
27
|
+
export declare function renderQQKeyboard(data: Pick<IMessage, 'buttons'>): QQKeyboard | undefined;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { type IMessage } from '../../shared/face';
|
|
2
|
+
import { type QQKeyboard } from './qqMessageButtons';
|
|
2
3
|
export type QQMessageKind = 'text' | 'story' | 'command' | 'transaction' | 'page' | 'reward';
|
|
3
4
|
export interface QQMessagePreview {
|
|
4
5
|
kind: QQMessageKind;
|
|
5
6
|
/** Exact native QQ Markdown payload; absent when the bot sends plain text. */
|
|
6
7
|
markdown?: string;
|
|
8
|
+
/** Native buttons accompany the same Markdown in bot delivery and editor preview. */
|
|
9
|
+
keyboard?: QQKeyboard;
|
|
7
10
|
/** Plain fallback before the transport adds its QQ group greeting. */
|
|
8
11
|
text: string;
|
|
9
12
|
}
|
|
@@ -1,6 +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
5
|
export { escapeMarkdown, renderQQTitle } from './qqMarkdownHelpers';
|
|
5
6
|
export declare function inlineCommandUrl(command: string): string;
|
|
6
7
|
export declare function hasInlineCommands(content: string): boolean;
|
|
@@ -13,4 +14,4 @@ export declare function canSendQQPage(session: Session | undefined, data: MsgMes
|
|
|
13
14
|
/** Send a native QQ Markdown page with inline command links. */
|
|
14
15
|
export declare function sendQQPage(session: Session, data: MsgMessage): Promise<void>;
|
|
15
16
|
/** Reuse the adapter's native reply API for structured Markdown messages. */
|
|
16
|
-
export declare function sendQQMarkdown(session: Session, markdown: string): Promise<void>;
|
|
17
|
+
export declare function sendQQMarkdown(session: Session, markdown: string, keyboard?: QQKeyboard): Promise<void>;
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { IMessage } from '../face';
|
|
2
|
+
/** Platform-neutral button hints also work when Markdown or keyboards are unavailable. */
|
|
3
|
+
export declare function messageButtonFallbackLines(data: Pick<IMessage, 'buttons'>): string[];
|
|
4
|
+
export declare function appendMessageButtonFallbacks(content: string, data: Pick<IMessage, 'buttons'>): string;
|
|
5
|
+
/** Remove only complete action hints; keep source line indexes used by page metadata. */
|
|
6
|
+
export declare function withoutMessageButtonFallbacks<T extends IMessage>(data: T): T;
|
|
2
7
|
/** 把服务器下发的结构化行拼成纯文本;第一阶段只处理 title / line / error **/
|
|
3
8
|
export declare class AnalysisMsg {
|
|
4
9
|
parse(data: IMessage): 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",
|
|
@@ -265,6 +267,17 @@ export interface PageNavData {
|
|
|
265
267
|
command: string;
|
|
266
268
|
}>;
|
|
267
269
|
}
|
|
270
|
+
/** 平台无关的消息按钮;点击后发送普通指令,业务仍负责权限与状态校验。 */
|
|
271
|
+
export interface MessageButton {
|
|
272
|
+
label: string;
|
|
273
|
+
command: string;
|
|
274
|
+
/** 主操作蓝色线框,次操作灰色线框;缺省为次操作。 */
|
|
275
|
+
style?: 'primary' | 'secondary';
|
|
276
|
+
}
|
|
277
|
+
/** 同一行的按钮;QQ 原生键盘最多五行,每行最多五个。 */
|
|
278
|
+
export interface MessageButtonRow {
|
|
279
|
+
buttons: MessageButton[];
|
|
280
|
+
}
|
|
268
281
|
/** 下发给客户端的消息主体 **/
|
|
269
282
|
export interface IMessage {
|
|
270
283
|
handleType: MessageHandleType;
|
|
@@ -291,4 +304,6 @@ export interface IMessage {
|
|
|
291
304
|
line: IMessageItem[];
|
|
292
305
|
companionCard?: CompanionCardData;
|
|
293
306
|
titleCard?: TitleCardData;
|
|
307
|
+
/** 通用指令按钮;不支持原生按钮的平台保留文字指令。 */
|
|
308
|
+
buttons?: MessageButtonRow[];
|
|
294
309
|
}
|
package/package.json
CHANGED
|
@@ -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
|
|
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 *
|
|
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('')}
|
package/src/plugin/cardLayout.ts
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
export async function layoutCard(document: Document = globalThis.document) {
|
|
3
3
|
|
|
4
4
|
await document.fonts.ready
|
|
5
|
+
// Measure names in one line before the flow-layout cards return below.
|
|
6
|
+
for (const name of document.querySelectorAll<HTMLElement>('#character-name,#companion-name')) {
|
|
7
|
+
const style = document.defaultView!.getComputedStyle(name)
|
|
8
|
+
const maximum = parseFloat(style.fontSize)
|
|
9
|
+
const spacing = parseFloat(style.letterSpacing) || 0
|
|
10
|
+
name.style.whiteSpace = 'nowrap'
|
|
11
|
+
for (let size = maximum; size >= 18; size--) {
|
|
12
|
+
name.style.fontSize = size + 'px'
|
|
13
|
+
name.style.letterSpacing = spacing * size / maximum + 'px'
|
|
14
|
+
if (name.scrollWidth <= name.clientWidth) break
|
|
15
|
+
}
|
|
16
|
+
// Preserve the complete name when even the readable minimum cannot fit.
|
|
17
|
+
if (name.scrollWidth > name.clientWidth) name.style.whiteSpace = 'normal'
|
|
18
|
+
}
|
|
5
19
|
const titleBanner = document.querySelector<HTMLImageElement>('.title-banner-artwork')
|
|
6
20
|
if (titleBanner) {
|
|
7
21
|
try { await titleBanner.decode() } catch {
|
|
@@ -26,7 +40,7 @@ export async function layoutCard(document: Document = globalThis.document) {
|
|
|
26
40
|
portrait.replaceWith(placeholder)
|
|
27
41
|
}
|
|
28
42
|
}
|
|
29
|
-
//
|
|
43
|
+
// Requested title artwork has an independent fallback; untitled cards have no title element.
|
|
30
44
|
const titleArtwork = document.querySelector<HTMLImageElement>('.title-artwork')
|
|
31
45
|
if (titleArtwork) {
|
|
32
46
|
try { await titleArtwork.decode() } catch {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/**
|
|
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>.
|
|
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
|
-
.
|
|
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
|
|
28
|
-
.
|
|
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
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
<
|
|
66
|
-
<div class="resource companion-resource
|
|
67
|
-
<
|
|
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
|
}
|
|
@@ -5,7 +5,7 @@ import handel_base from './handel_base'
|
|
|
5
5
|
import TextHandler from './text'
|
|
6
6
|
import sessions, { sessionKey } from '../sessions'
|
|
7
7
|
import { canSendQQPage, sendQQMarkdown } from './qqPageMessage'
|
|
8
|
-
import {
|
|
8
|
+
import { renderQQImageFollowup } from './imageMessageFollowup'
|
|
9
9
|
export default class ImageHandler extends handel_base {
|
|
10
10
|
async start(data: MsgMessage) {
|
|
11
11
|
let content = ''
|
|
@@ -32,16 +32,16 @@ export default class ImageHandler extends handel_base {
|
|
|
32
32
|
}
|
|
33
33
|
const sendStartedAt = this.timing ? performance.now() : 0
|
|
34
34
|
await this.send(data, content, 'image')
|
|
35
|
-
const followup =
|
|
35
|
+
const followup = renderQQImageFollowup(data)
|
|
36
36
|
if (!followup) return
|
|
37
37
|
const session = sessions.get(sessionKey(data.botId, data.userId, data.channelId))
|
|
38
38
|
let sentMarkdown = false
|
|
39
|
-
if (canSendQQPage(session, data)) {
|
|
39
|
+
if (followup.markdown !== undefined && canSendQQPage(session, data)) {
|
|
40
40
|
try {
|
|
41
|
-
await sendQQMarkdown(session, followup.markdown)
|
|
41
|
+
await sendQQMarkdown(session, followup.markdown, followup.keyboard)
|
|
42
42
|
sentMarkdown = true
|
|
43
43
|
} catch (error) {
|
|
44
|
-
this.ctx.logger.warn('
|
|
44
|
+
this.ctx.logger.warn('图片操作 Markdown 发送失败,改用普通文字', error instanceof Error ? error.message : error)
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
if (!sentMarkdown) await this.send({ ...data, notification: true }, followup.text)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IMessage } from '../../shared/face'
|
|
2
|
+
import { appendMessageButtonFallbacks, messageButtonFallbackLines, withoutMessageButtonFallbacks } from '../../shared/bot/analysisMsg'
|
|
3
|
+
import { titleCardFollowup } from './titleCardMessage'
|
|
4
|
+
import { renderQQKeyboard, type QQKeyboard } from './qqMessageButtons'
|
|
5
|
+
|
|
6
|
+
export interface QQImageFollowup {
|
|
7
|
+
text: string
|
|
8
|
+
markdown?: string
|
|
9
|
+
keyboard?: QQKeyboard
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Images are sent first; actions stay outside the image and retain a plain fallback. */
|
|
13
|
+
export function renderQQImageFollowup(data: IMessage): QQImageFollowup | undefined {
|
|
14
|
+
const title = titleCardFollowup(data)
|
|
15
|
+
const actionLines = messageButtonFallbackLines(data)
|
|
16
|
+
if (!title && !actionLines.length) return undefined
|
|
17
|
+
const text = appendMessageButtonFallbacks(title?.text ?? '', data).trimEnd()
|
|
18
|
+
let keyboard: QQKeyboard | undefined
|
|
19
|
+
try { keyboard = renderQQKeyboard(data) }
|
|
20
|
+
catch { return { text } }
|
|
21
|
+
const markdown = titleCardFollowup(withoutMessageButtonFallbacks(data))?.markdown ?? ' '
|
|
22
|
+
return { text, markdown, ...(keyboard ? { keyboard } : {}) }
|
|
23
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { withoutMessageButtonFallbacks } from '../../shared/bot/analysisMsg'
|
|
2
|
+
import { renderQQKeyboard } from './qqMessageButtons'
|
|
1
3
|
import type { Session } from 'koishi'
|
|
2
4
|
import { MessageType, type IMessage } from '../../shared/face'
|
|
3
5
|
import type { MsgMessage } from '../../shared/bot/MsgMessage'
|
|
@@ -6,6 +8,7 @@ import { isRewardMessage, renderRewardLine, renderRewardTitle } from './qqReward
|
|
|
6
8
|
|
|
7
9
|
/** Render command help and ordinary prose with clickable bracketed commands. */
|
|
8
10
|
export function renderQQCommandMarkdown(data: IMessage, showGreeting: boolean): string {
|
|
11
|
+
data = withoutMessageButtonFallbacks(data)
|
|
9
12
|
if (data.notification) {
|
|
10
13
|
const onlyLine = data.line.length === 1 ? data.line[0] : undefined
|
|
11
14
|
if (onlyLine?.type === MessageType.line && /^🔔系统提示:[^\r\n]+$/.test(onlyLine.content)) {
|
|
@@ -52,8 +55,12 @@ export function renderQQCommandMarkdown(data: IMessage, showGreeting: boolean):
|
|
|
52
55
|
break
|
|
53
56
|
}
|
|
54
57
|
flushRewards()
|
|
55
|
-
|
|
56
|
-
|
|
58
|
+
// A single message item may contain a complete multiline battle summary.
|
|
59
|
+
// Keep its line/paragraph boundaries when log links select native Markdown.
|
|
60
|
+
for (const line of item.content.replace(/\r\n?/g, '\n').split('\n')) {
|
|
61
|
+
if (!line.trim()) flushBody()
|
|
62
|
+
else bodyLines.push(renderInlineCommands(line))
|
|
63
|
+
}
|
|
57
64
|
break
|
|
58
65
|
case MessageType.error:
|
|
59
66
|
flushBody()
|
|
@@ -68,5 +75,5 @@ export function renderQQCommandMarkdown(data: IMessage, showGreeting: boolean):
|
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
export async function sendQQCommand(session: Session, data: MsgMessage): Promise<void> {
|
|
71
|
-
await sendQQMarkdown(session, renderQQCommandMarkdown(data, session.event?.channel?.type === 0))
|
|
78
|
+
await sendQQMarkdown(session, renderQQCommandMarkdown(data, session.event?.channel?.type === 0), renderQQKeyboard(data))
|
|
72
79
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { IMessage } from '../../shared/face'
|
|
2
|
+
|
|
3
|
+
export interface QQKeyboard {
|
|
4
|
+
content: { rows: Array<{ buttons: Array<{
|
|
5
|
+
id: string
|
|
6
|
+
render_data: { label: string; visited_label: string; style: 0 | 1 }
|
|
7
|
+
action: {
|
|
8
|
+
type: 2
|
|
9
|
+
permission: { type: 2 }
|
|
10
|
+
data: string
|
|
11
|
+
enter: true
|
|
12
|
+
reply: false
|
|
13
|
+
unsupport_tips: string
|
|
14
|
+
}
|
|
15
|
+
}> }> }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Explicit actions use QQ's native keyboard; ordinary command links stay inline. */
|
|
19
|
+
export function renderQQKeyboard(data: Pick<IMessage, 'buttons'>): QQKeyboard | undefined {
|
|
20
|
+
const rows = data.buttons
|
|
21
|
+
if (!rows?.length) return undefined
|
|
22
|
+
if (rows.length > 5 || rows.some(row => !row.buttons.length || row.buttons.length > 5)) {
|
|
23
|
+
throw new Error('QQ 按钮最多 5 行,每行必须有 1–5 个按钮')
|
|
24
|
+
}
|
|
25
|
+
return { content: { rows: rows.map((row, rowIndex) => ({
|
|
26
|
+
buttons: row.buttons.map((button, buttonIndex) => {
|
|
27
|
+
if (!button.label.trim() || !button.command.trim() || /[\r\n\0]/.test(button.label + button.command)
|
|
28
|
+
|| button.command.length > 4000 || (button.style !== undefined && button.style !== 'primary' && button.style !== 'secondary')) {
|
|
29
|
+
throw new Error('QQ 按钮文案和指令必须是非空单行文本,样式必须为 primary 或 secondary')
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
id: 'action-' + rowIndex + '-' + buttonIndex,
|
|
33
|
+
render_data: { label: button.label, visited_label: button.label, style: button.style === 'primary' ? 1 : 0 },
|
|
34
|
+
action: { type: 2, permission: { type: 2 }, data: button.command, enter: true, reply: false, unsupport_tips: '请发送:' + button.command },
|
|
35
|
+
}
|
|
36
|
+
}),
|
|
37
|
+
})) } }
|
|
38
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type IMessage, MessageType } from '../../shared/face'
|
|
2
|
-
import { AnalysisMsg } from '../../shared/bot/analysisMsg'
|
|
2
|
+
import { AnalysisMsg, withoutMessageButtonFallbacks } from '../../shared/bot/analysisMsg'
|
|
3
|
+
import { renderQQKeyboard, type QQKeyboard } from './qqMessageButtons'
|
|
3
4
|
import { hasInlineCommands, renderQQPageMarkdown } from './qqPageMessage'
|
|
4
5
|
import { renderQQCommandMarkdown } from './qqCommandMessage'
|
|
5
6
|
import { renderQQTransactionMarkdown } from './qqTransactionMessage'
|
|
@@ -13,6 +14,8 @@ export interface QQMessagePreview {
|
|
|
13
14
|
kind: QQMessageKind
|
|
14
15
|
/** Exact native QQ Markdown payload; absent when the bot sends plain text. */
|
|
15
16
|
markdown?: string
|
|
17
|
+
/** Native buttons accompany the same Markdown in bot delivery and editor preview. */
|
|
18
|
+
keyboard?: QQKeyboard
|
|
16
19
|
/** Plain fallback before the transport adds its QQ group greeting. */
|
|
17
20
|
text: string
|
|
18
21
|
}
|
|
@@ -21,14 +24,22 @@ export interface QQMessagePreview {
|
|
|
21
24
|
export function renderQQMessagePreview(data: IMessage, showGreeting = false): QQMessagePreview {
|
|
22
25
|
const plain = new AnalysisMsg().parse(data)
|
|
23
26
|
const text = data.notification ? plain.trimEnd() : plain
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
let keyboard: QQKeyboard | undefined
|
|
28
|
+
try { keyboard = renderQQKeyboard(data) }
|
|
29
|
+
catch { return { kind: 'text', text } }
|
|
30
|
+
data = withoutMessageButtonFallbacks(data)
|
|
31
|
+
const native = (kind: QQMessageKind, markdown: string): QQMessagePreview => ({
|
|
32
|
+
kind, markdown, text, ...(keyboard ? { keyboard } : {}),
|
|
33
|
+
})
|
|
34
|
+
if (data.notification) return native('command', renderQQCommandMarkdown(data, false))
|
|
35
|
+
if (hasStoryCodeBlock(data)) return native('story', renderQQStoryMarkdown(data, showGreeting))
|
|
36
|
+
if (data.commandHelp) return native('command', renderQQCommandMarkdown(data, showGreeting))
|
|
37
|
+
if (data.transaction) return native('transaction', renderQQTransactionMarkdown(data, showGreeting))
|
|
38
|
+
if (data.pageNav) return native('page', renderQQPageMarkdown(data, showGreeting))
|
|
39
|
+
if (isRewardMessage(data)) return native('reward', renderQQRewardMarkdown(data, showGreeting))
|
|
30
40
|
if (battleLogMessageLinks(data).length || data.line.some(item => (item.type === MessageType.line || item.type === MessageType.error) && hasInlineCommands(item.content))) {
|
|
31
|
-
return
|
|
41
|
+
return native('command', renderQQCommandMarkdown(data, showGreeting))
|
|
32
42
|
}
|
|
43
|
+
if (keyboard) return native('command', renderQQCommandMarkdown(data, showGreeting))
|
|
33
44
|
return { kind: 'text', text }
|
|
34
45
|
}
|