@jeik/dingtalk-connector 0.8.21-fix1
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/CHANGELOG.md +686 -0
- package/LICENSE +21 -0
- package/README.en.md +181 -0
- package/README.md +221 -0
- package/bin/dingtalk-connector.js +858 -0
- package/bin/wizard-config.mjs +110 -0
- package/dist/accounts-BAzdqkAV.mjs +268 -0
- package/dist/accounts-BQptOmgB.mjs +2 -0
- package/dist/chunk-upload-BBQgGtcZ.mjs +193 -0
- package/dist/chunk-upload-DaLXXZH3.mjs +2 -0
- package/dist/common-C8pYKU_y.mjs +2 -0
- package/dist/common-Dt9n6fQN.mjs +101 -0
- package/dist/connection-DHHFFNQJ.mjs +423 -0
- package/dist/entry-bundled.d.mts +16 -0
- package/dist/entry-bundled.mjs +31 -0
- package/dist/game-xiyou-CqHt-6Q1.mjs +4271 -0
- package/dist/gateway-methods-C4tcgI7P.mjs +771 -0
- package/dist/gateway-methods-Ci31A3vg.mjs +2 -0
- package/dist/http-client-CpnJHB89.mjs +2 -0
- package/dist/http-client-DFWZgO1n.mjs +33 -0
- package/dist/index.d.mts +193 -0
- package/dist/index.mjs +45 -0
- package/dist/logger-BmJkQkm1.mjs +2 -0
- package/dist/logger-mZ9OSbmD.mjs +58 -0
- package/dist/media-C_SVin7s.mjs +2 -0
- package/dist/media-cz72EVS3.mjs +509 -0
- package/dist/message-handler-DESzFFDc.mjs +1971 -0
- package/dist/messaging-B6l1sRvX.mjs +1044 -0
- package/dist/runtime-DUgpo5zC.mjs +1422 -0
- package/dist/session-DJ4jYqPv.mjs +114 -0
- package/dist/utils-Bjh4r_qS.mjs +4 -0
- package/dist/utils-CIfI_3Jh.mjs +63 -0
- package/dist/utils-legacy-CALCPP1t.mjs +230 -0
- package/dist/utils-legacy-CFYDBM4r.mjs +3 -0
- package/docs/DEAP_AGENT_GUIDE.en.md +115 -0
- package/docs/DEAP_AGENT_GUIDE.md +115 -0
- package/docs/DINGTALK_MANUAL_SETUP.md +50 -0
- package/docs/MULTI_AGENT_SETUP.md +306 -0
- package/docs/RELEASE_NOTES_V0.7.10.md +40 -0
- package/docs/RELEASE_NOTES_V0.7.2.md +143 -0
- package/docs/RELEASE_NOTES_V0.7.3.md +149 -0
- package/docs/RELEASE_NOTES_V0.7.4.md +206 -0
- package/docs/RELEASE_NOTES_V0.7.5.md +267 -0
- package/docs/RELEASE_NOTES_V0.7.6.md +219 -0
- package/docs/RELEASE_NOTES_V0.7.7.md +122 -0
- package/docs/RELEASE_NOTES_V0.7.8.md +101 -0
- package/docs/RELEASE_NOTES_V0.7.9.md +65 -0
- package/docs/RELEASE_NOTES_V0.8.0.md +53 -0
- package/docs/RELEASE_NOTES_V0.8.1.md +47 -0
- package/docs/RELEASE_NOTES_V0.8.10.md +49 -0
- package/docs/RELEASE_NOTES_V0.8.11.md +51 -0
- package/docs/RELEASE_NOTES_V0.8.12.md +63 -0
- package/docs/RELEASE_NOTES_V0.8.13-beta.0.md +69 -0
- package/docs/RELEASE_NOTES_V0.8.13.md +62 -0
- package/docs/RELEASE_NOTES_V0.8.14.md +86 -0
- package/docs/RELEASE_NOTES_V0.8.16.md +40 -0
- package/docs/RELEASE_NOTES_V0.8.17.md +87 -0
- package/docs/RELEASE_NOTES_V0.8.18.md +64 -0
- package/docs/RELEASE_NOTES_V0.8.19.md +62 -0
- package/docs/RELEASE_NOTES_V0.8.2.md +55 -0
- package/docs/RELEASE_NOTES_V0.8.20.md +49 -0
- package/docs/RELEASE_NOTES_V0.8.3.md +63 -0
- package/docs/RELEASE_NOTES_V0.8.4.md +45 -0
- package/docs/RELEASE_NOTES_V0.8.7.md +49 -0
- package/docs/RELEASE_NOTES_V0.8.8.md +63 -0
- package/docs/RELEASE_NOTES_V0.8.9.md +81 -0
- package/docs/RELEASE_NOTES_v0.7.0.md +142 -0
- package/docs/RELEASE_NOTES_v0.7.1.md +74 -0
- package/docs/TROUBLESHOOTING.md +122 -0
- package/index.ts +77 -0
- package/openclaw.plugin.json +551 -0
- package/package.json +147 -0
- package/skills/dingtalk-channel-rules/SKILL.md +91 -0
- package/skills/dingtalk-troubleshoot/SKILL.md +93 -0
- package/skills/dws-cli/SKILL.md +129 -0
- package/skills/dws-cli/references/error-codes.md +95 -0
- package/skills/dws-cli/references/field-rules.md +105 -0
- package/skills/dws-cli/references/global-reference.md +104 -0
- package/skills/dws-cli/references/intent-guide.md +114 -0
- package/skills/dws-cli/references/products/aitable.md +452 -0
- package/skills/dws-cli/references/products/attendance.md +93 -0
- package/skills/dws-cli/references/products/calendar.md +217 -0
- package/skills/dws-cli/references/products/chat.md +292 -0
- package/skills/dws-cli/references/products/contact.md +108 -0
- package/skills/dws-cli/references/products/ding.md +57 -0
- package/skills/dws-cli/references/products/report.md +162 -0
- package/skills/dws-cli/references/products/simple.md +128 -0
- package/skills/dws-cli/references/products/todo.md +138 -0
- package/skills/dws-cli/references/products/workbench.md +39 -0
- package/skills/dws-cli/references/recovery-guide.md +94 -0
- package/src/channel.ts +588 -0
- package/src/config/accounts.ts +242 -0
- package/src/config/schema.ts +180 -0
- package/src/core/connection.ts +741 -0
- package/src/core/message-handler.ts +1788 -0
- package/src/core/provider.ts +111 -0
- package/src/core/state.ts +54 -0
- package/src/device-auth-config.ts +14 -0
- package/src/device-auth.ts +197 -0
- package/src/directory.ts +95 -0
- package/src/docs.ts +293 -0
- package/src/game-xiyou/achievement-engine.ts +252 -0
- package/src/game-xiyou/bounty-system.ts +315 -0
- package/src/game-xiyou/commands.ts +223 -0
- package/src/game-xiyou/drop-engine.ts +241 -0
- package/src/game-xiyou/encounter-system.ts +135 -0
- package/src/game-xiyou/escape-engine.ts +164 -0
- package/src/game-xiyou/exp-calculator.ts +139 -0
- package/src/game-xiyou/index.ts +479 -0
- package/src/game-xiyou/level-system.ts +91 -0
- package/src/game-xiyou/monster-pool.ts +180 -0
- package/src/game-xiyou/pity-counter.ts +114 -0
- package/src/game-xiyou/random-event-engine.ts +648 -0
- package/src/game-xiyou/renderer.ts +679 -0
- package/src/game-xiyou/storage.ts +218 -0
- package/src/game-xiyou/treasure-system.ts +105 -0
- package/src/game-xiyou/types.ts +582 -0
- package/src/game-xiyou/uid-resolver.ts +49 -0
- package/src/gateway-methods.ts +740 -0
- package/src/onboarding.ts +553 -0
- package/src/policy.ts +32 -0
- package/src/probe.ts +210 -0
- package/src/reply-dispatcher.ts +874 -0
- package/src/runtime.ts +32 -0
- package/src/sdk/helpers.ts +322 -0
- package/src/sdk/types.ts +519 -0
- package/src/secret-input.ts +19 -0
- package/src/services/media/audio.ts +54 -0
- package/src/services/media/chunk-upload.ts +296 -0
- package/src/services/media/common.ts +155 -0
- package/src/services/media/file.ts +75 -0
- package/src/services/media/image.ts +81 -0
- package/src/services/media/index.ts +10 -0
- package/src/services/media/video.ts +162 -0
- package/src/services/media.ts +1143 -0
- package/src/services/messaging/card.ts +604 -0
- package/src/services/messaging/index.ts +18 -0
- package/src/services/messaging/mentions.ts +267 -0
- package/src/services/messaging/send.ts +141 -0
- package/src/services/messaging.ts +1191 -0
- package/src/services/reply-markers.ts +55 -0
- package/src/targets.ts +45 -0
- package/src/types/index.ts +59 -0
- package/src/types/pdf-parse.d.ts +3 -0
- package/src/utils/agent.ts +63 -0
- package/src/utils/async.ts +51 -0
- package/src/utils/constants.ts +27 -0
- package/src/utils/http-client.ts +38 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/logger.ts +78 -0
- package/src/utils/session.ts +147 -0
- package/src/utils/token.ts +93 -0
- package/src/utils/utils-legacy.ts +454 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown 渲染器
|
|
3
|
+
*
|
|
4
|
+
* 输出纯 Markdown 字符串,适配钉钉 AI Card 渲染。
|
|
5
|
+
* 不关心发送方式,只负责内容生成。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
DropResult, ExpResult, LevelUpResult, Encounter,
|
|
10
|
+
Achievement, UserProfile, UserCollection, Monster,
|
|
11
|
+
} from './types.ts';
|
|
12
|
+
import { QUALITY_LABELS, LEVEL_DEFINITIONS } from './types.ts';
|
|
13
|
+
import { getMonsterById, getWeeklyUpMonster, getTotalMonsterCount, getAllMonsters } from './monster-pool.ts';
|
|
14
|
+
import { getImmortalById, getTreasureName, getTreasureDescription } from './encounter-system.ts';
|
|
15
|
+
import { getLevelProgress, getExpToNextLevel, getNextLevel } from './level-system.ts';
|
|
16
|
+
import { getUserTreasures, getConsumableTreasures } from './treasure-system.ts';
|
|
17
|
+
import { getAllAchievements } from './achievement-engine.ts';
|
|
18
|
+
|
|
19
|
+
// ============ 掉落结果渲染 ============
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 渲染普通掉落结果(追加到 agent 回复末尾)
|
|
23
|
+
*
|
|
24
|
+
* v3: 紧凑化渲染 — 用妖怪 emoji 图标 + 单行/双行展示,大幅减少纵向空间
|
|
25
|
+
*/
|
|
26
|
+
export function renderDropResult(drop: DropResult, expResult: ExpResult, collection: UserCollection): string {
|
|
27
|
+
if (!drop.monster.id) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const qualityLabel = drop.isShiny ? '✨' : QUALITY_LABELS[drop.monster.quality];
|
|
32
|
+
const monsterEmoji = drop.monster.emoji ?? '🗡️';
|
|
33
|
+
const totalMonsters = getTotalMonsterCount();
|
|
34
|
+
const collectedCount = collection.entries.length;
|
|
35
|
+
const tags: string[] = [];
|
|
36
|
+
if (drop.isPityTriggered) tags.push('🔮保底');
|
|
37
|
+
if (drop.isUpMonster) tags.push('📢UP');
|
|
38
|
+
if (drop.isNew) tags.push('📖新发现');
|
|
39
|
+
const tagStr = tags.length > 0 ? ` ${tags.join(' ')}` : '';
|
|
40
|
+
|
|
41
|
+
if (drop.escaped) {
|
|
42
|
+
return `\n💨 ${monsterEmoji} ${qualityLabel} **${drop.monster.name}** 逃跑! *"${drop.monster.captureQuote}"* · +2${tagStr}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (drop.isShiny) {
|
|
46
|
+
return `\n🌈 ${monsterEmoji} ✨ **${drop.monster.name}** ✨ 闪光降临! *"${drop.monster.captureQuote}"* · +${expResult.totalExp} · 📖${collectedCount}/${totalMonsters}${tagStr}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (drop.monster.quality === 'epic' || drop.monster.quality === 'legendary') {
|
|
50
|
+
return `\n✦ ${monsterEmoji} ${qualityLabel} **${drop.monster.name}** · *${drop.monster.origin}* · *"${drop.monster.captureQuote}"* · +${expResult.totalExp} · 📖${collectedCount}/${totalMonsters}${tagStr}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return `\n🗡️ ${monsterEmoji} ${qualityLabel} **${drop.monster.name}** · *"${drop.monster.captureQuote}"* · +${expResult.totalExp} · 📖${collectedCount}/${totalMonsters}${tagStr}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ============ 升级渲染 ============
|
|
57
|
+
|
|
58
|
+
export function renderLevelUp(levelUp: LevelUpResult): string {
|
|
59
|
+
const unlockPart = levelUp.unlockDescription ? ` · 🔓${levelUp.unlockDescription}` : '';
|
|
60
|
+
return `\n⬆️ **升级!** ${levelUp.previousTitle} → **${levelUp.newTitle}** (Lv.${levelUp.newLevel})${unlockPart}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ============ 机缘渲染 ============
|
|
64
|
+
|
|
65
|
+
export function renderEncounter(encounter: Encounter): string {
|
|
66
|
+
const immortal = getImmortalById(encounter.immortalId);
|
|
67
|
+
if (!immortal) return '';
|
|
68
|
+
|
|
69
|
+
const typeTag = encounter.type === 'guidance' ? '🤍点化'
|
|
70
|
+
: encounter.type === 'treasure' ? '💛赐宝' : '💜收徒';
|
|
71
|
+
|
|
72
|
+
let extra = '';
|
|
73
|
+
if (encounter.type === 'treasure' && encounter.treasureId) {
|
|
74
|
+
extra = ` · 获得${getTreasureName(encounter.treasureId)}`;
|
|
75
|
+
}
|
|
76
|
+
if (encounter.type === 'apprentice') {
|
|
77
|
+
extra = ' · 永久加成';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return `\n☁️ ${typeTag} **${immortal.name}**:*"${immortal.guidanceQuote}"*${extra}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ============ 成就渲染 ============
|
|
84
|
+
|
|
85
|
+
export function renderNewAchievements(achievements: Achievement[]): string {
|
|
86
|
+
if (achievements.length === 0) return '';
|
|
87
|
+
|
|
88
|
+
return achievements.map(achievement => {
|
|
89
|
+
const titlePart = achievement.titleReward ? ` 🎖️「${achievement.titleReward}」` : '';
|
|
90
|
+
return `\n🏆 ${achievement.emoji} **${achievement.name}** — *${achievement.description}* · +${achievement.expReward}${titlePart}`;
|
|
91
|
+
}).join('');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============ 面板渲染(命令响应) ============
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 渲染修行面板 (/修行)
|
|
98
|
+
*/
|
|
99
|
+
export function renderProfilePanel(profile: UserProfile, collection: UserCollection): string {
|
|
100
|
+
const totalMonsters = getTotalMonsterCount();
|
|
101
|
+
const collectedCount = collection.entries.length;
|
|
102
|
+
const shinyCount = collection.entries.filter(e => e.isShiny).length;
|
|
103
|
+
const progress = getLevelProgress(profile.totalExp);
|
|
104
|
+
const expToNext = getExpToNextLevel(profile.totalExp);
|
|
105
|
+
const nextLevel = getNextLevel(profile.level);
|
|
106
|
+
const upMonster = getWeeklyUpMonster();
|
|
107
|
+
const allAchievementsList = getAllAchievements();
|
|
108
|
+
|
|
109
|
+
// 进度条(使用 emoji 圆形符号,兼容钉钉 Markdown 渲染)
|
|
110
|
+
const progressBar = renderEmojiBar(progress);
|
|
111
|
+
|
|
112
|
+
const lines = [
|
|
113
|
+
`### 🐒 西游妖魔榜 · 修行面板`,
|
|
114
|
+
'',
|
|
115
|
+
`**修行者 ID**:${profile.uidHash.slice(0, 8)}`,
|
|
116
|
+
`**称号**:${profile.title} (Lv.${profile.level})`,
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
if (nextLevel) {
|
|
120
|
+
lines.push(
|
|
121
|
+
`**修行值**:${profile.totalExp.toLocaleString()} / ${nextLevel.requiredExp.toLocaleString()} (${progress}%)`,
|
|
122
|
+
'',
|
|
123
|
+
`${progressBar}`,
|
|
124
|
+
'',
|
|
125
|
+
`距离下一级「${nextLevel.title}」还需 ${expToNext?.toLocaleString()} 修行值`,
|
|
126
|
+
);
|
|
127
|
+
} else {
|
|
128
|
+
lines.push(`**修行值**:${profile.totalExp.toLocaleString()} (已满级)`, '', `${renderEmojiBar(100)}`);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
lines.push(
|
|
132
|
+
'',
|
|
133
|
+
`#### 📊 统计`,
|
|
134
|
+
`- **总操作**:${profile.totalOperations} 次`,
|
|
135
|
+
`- **连击中**:${profile.currentCombo} 次${profile.currentCombo >= 3 ? ` (×${getComboDisplay(profile.currentCombo)})` : ''}`,
|
|
136
|
+
`- **连续签到**:${profile.consecutiveSignInDays} 天`,
|
|
137
|
+
`- **最高连击**:${profile.maxCombo} 次`,
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
// 图鉴进度
|
|
141
|
+
const qualityCounts = getQualityProgress(collection);
|
|
142
|
+
lines.push(
|
|
143
|
+
'',
|
|
144
|
+
`#### 📖 图鉴 ${collectedCount}/${totalMonsters} (${Math.floor(collectedCount / totalMonsters * 100)}%)`,
|
|
145
|
+
'',
|
|
146
|
+
`| 品质 | 进度 |`,
|
|
147
|
+
`|------|------|`,
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
for (const [label, collected, total] of qualityCounts) {
|
|
151
|
+
const status = collected >= total ? ' ✅' : '';
|
|
152
|
+
lines.push(`| ${label} | ${collected}/${total}${status} |`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (shinyCount > 0) {
|
|
156
|
+
lines.push(`| ✨ 闪光 | ${shinyCount} |`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 保底状态
|
|
160
|
+
const pity = profile.pityCounters;
|
|
161
|
+
lines.push(
|
|
162
|
+
'',
|
|
163
|
+
`#### 🔮 保底状态`,
|
|
164
|
+
`- **小保底**:${pity.sinceLastRare}/30 · **大保底**:${pity.sinceLastEpic}/80 · **天命**:${pity.sinceLastLegendary}/150`,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// UP 妖怪
|
|
168
|
+
if (upMonster) {
|
|
169
|
+
lines.push(
|
|
170
|
+
'',
|
|
171
|
+
`📢 **本周 UP**:${QUALITY_LABELS[upMonster.quality]} ${upMonster.name} (权重 ×5)`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 成就
|
|
176
|
+
lines.push(
|
|
177
|
+
'',
|
|
178
|
+
`🏆 **成就**:${profile.unlockedAchievements.length}/${allAchievementsList.length}`,
|
|
179
|
+
`🎒 **法宝**:${profile.treasures.length} 件`,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
return lines.join('\n');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 渲染图鉴面板 (/图鉴)
|
|
187
|
+
*/
|
|
188
|
+
export function renderCollectionPanel(collection: UserCollection): string {
|
|
189
|
+
const allMonstersList = getAllMonsters();
|
|
190
|
+
const totalMonsters = getTotalMonsterCount();
|
|
191
|
+
const collectedCount = collection.entries.length;
|
|
192
|
+
|
|
193
|
+
const lines = [
|
|
194
|
+
`### 📖 妖怪图鉴 · ${collectedCount}/${totalMonsters}`,
|
|
195
|
+
'',
|
|
196
|
+
];
|
|
197
|
+
|
|
198
|
+
const qualityGroups: Array<{ quality: string; label: string; monsters: Monster[] }> = [
|
|
199
|
+
{ quality: 'normal', label: QUALITY_LABELS.normal, monsters: allMonstersList.filter(m => m.quality === 'normal') },
|
|
200
|
+
{ quality: 'fine', label: QUALITY_LABELS.fine, monsters: allMonstersList.filter(m => m.quality === 'fine') },
|
|
201
|
+
{ quality: 'rare', label: QUALITY_LABELS.rare, monsters: allMonstersList.filter(m => m.quality === 'rare') },
|
|
202
|
+
{ quality: 'epic', label: QUALITY_LABELS.epic, monsters: allMonstersList.filter(m => m.quality === 'epic') },
|
|
203
|
+
{ quality: 'legendary', label: QUALITY_LABELS.legendary, monsters: allMonstersList.filter(m => m.quality === 'legendary') },
|
|
204
|
+
];
|
|
205
|
+
|
|
206
|
+
for (const group of qualityGroups) {
|
|
207
|
+
const collected = group.monsters.filter(m =>
|
|
208
|
+
collection.entries.some(e => e.monsterId === m.id && !e.isShiny)
|
|
209
|
+
);
|
|
210
|
+
const uncollectedCount = group.monsters.length - collected.length;
|
|
211
|
+
|
|
212
|
+
lines.push(`#### ${group.label} ${collected.length}/${group.monsters.length}${collected.length >= group.monsters.length ? ' ✅' : ''}`);
|
|
213
|
+
|
|
214
|
+
if (collected.length > 0) {
|
|
215
|
+
lines.push(collected.map(m => m.name).join(' · '));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (uncollectedCount > 0) {
|
|
219
|
+
lines.push(`${'❓'.repeat(Math.min(uncollectedCount, 5))} *还有 ${uncollectedCount} 只未发现*`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
lines.push('');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// 闪光
|
|
226
|
+
const shinyEntries = collection.entries.filter(e => e.isShiny);
|
|
227
|
+
lines.push(`#### ✨ 闪光 ${shinyEntries.length}`);
|
|
228
|
+
if (shinyEntries.length > 0) {
|
|
229
|
+
const shinyNames = shinyEntries.map(e => {
|
|
230
|
+
const monster = getMonsterById(e.monsterId);
|
|
231
|
+
return monster ? `${monster.name} ✨` : e.monsterId;
|
|
232
|
+
});
|
|
233
|
+
lines.push(shinyNames.join(' · '));
|
|
234
|
+
} else {
|
|
235
|
+
lines.push('*等级 ≥ 9 后解锁闪光掉落*');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return lines.join('\n');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* 渲染成就面板 (/成就)
|
|
243
|
+
*/
|
|
244
|
+
export function renderAchievementPanel(profile: UserProfile): string {
|
|
245
|
+
const allAchievementsList = getAllAchievements();
|
|
246
|
+
const lines = [
|
|
247
|
+
`### 🏆 成就列表 · ${profile.unlockedAchievements.length}/${allAchievementsList.length}`,
|
|
248
|
+
'',
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
const categories = [
|
|
252
|
+
{ key: 'cultivation', label: '修行成就' },
|
|
253
|
+
{ key: 'collection', label: '收集成就' },
|
|
254
|
+
{ key: 'product', label: '产品成就' },
|
|
255
|
+
{ key: 'hidden', label: '隐藏成就' },
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
for (const category of categories) {
|
|
259
|
+
const categoryAchievements = allAchievementsList.filter(a => a.category === category.key);
|
|
260
|
+
lines.push(`#### ${category.label}`);
|
|
261
|
+
|
|
262
|
+
for (const achievement of categoryAchievements) {
|
|
263
|
+
const unlocked = profile.unlockedAchievements.includes(achievement.id);
|
|
264
|
+
const status = unlocked ? '✅' : '⬜';
|
|
265
|
+
const desc = category.key === 'hidden' && !unlocked ? '???' : achievement.description;
|
|
266
|
+
lines.push(`- ${status} ${achievement.emoji} **${achievement.name}** — ${desc} (+${achievement.expReward})`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
lines.push('');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return lines.join('\n');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 渲染法宝面板 (/法宝)
|
|
277
|
+
*/
|
|
278
|
+
export function renderTreasurePanel(profile: UserProfile): string {
|
|
279
|
+
const treasures = getUserTreasures(profile);
|
|
280
|
+
const consumable = getConsumableTreasures(profile);
|
|
281
|
+
|
|
282
|
+
const lines = [
|
|
283
|
+
`### 🎒 法宝背包 · ${treasures.length} 件`,
|
|
284
|
+
'',
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
if (treasures.length === 0) {
|
|
288
|
+
lines.push('*背包空空如也,等待神仙赐宝...*');
|
|
289
|
+
return lines.join('\n');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
for (const treasure of treasures) {
|
|
293
|
+
const consumed = profile.consumedTreasures.includes(treasure.id);
|
|
294
|
+
const status = consumed ? '(已使用)' : treasure.consumable ? '(可使用)' : '(永久生效)';
|
|
295
|
+
lines.push(`- **${treasure.name}** ${status}`, ` ${treasure.description}`, ` *来源:${treasure.source}*`, '');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (consumable.length > 0) {
|
|
299
|
+
lines.push('', `💡 发送 \`/使用 法宝名\` 来使用一次性法宝`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return lines.join('\n');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 渲染保底面板 (/保底)
|
|
307
|
+
*/
|
|
308
|
+
export function renderPityPanel(profile: UserProfile): string {
|
|
309
|
+
const pity = profile.pityCounters;
|
|
310
|
+
|
|
311
|
+
// v2: 软保底状态提示
|
|
312
|
+
const softPityHints: string[] = [];
|
|
313
|
+
if (pity.sinceLastRare >= 20) softPityHints.push(`稀有软保底已激活 +${(pity.sinceLastRare - 20) * 3}%`);
|
|
314
|
+
if (pity.sinceLastEpic >= 60) softPityHints.push(`史诗软保底已激活 +${(pity.sinceLastEpic - 60) * 2}%`);
|
|
315
|
+
if (pity.sinceLastLegendary >= 120) softPityHints.push(`传说软保底已激活 +${(pity.sinceLastLegendary - 120) * 1}%`);
|
|
316
|
+
|
|
317
|
+
const lines = [
|
|
318
|
+
`### 🔮 保底计数器`,
|
|
319
|
+
'',
|
|
320
|
+
`| 保底类型 | 当前计数 | 触发阈值 | 进度 |`,
|
|
321
|
+
`|---------|---------|---------|------|`,
|
|
322
|
+
`| 小保底(稀有) | ${pity.sinceLastRare} | 30 | ${Math.floor(pity.sinceLastRare / 30 * 100)}% |`,
|
|
323
|
+
`| 大保底(史诗) | ${pity.sinceLastEpic} | 80 | ${Math.floor(pity.sinceLastEpic / 80 * 100)}% |`,
|
|
324
|
+
`| 天命保底(传说) | ${pity.sinceLastLegendary} | 150 | ${Math.floor(pity.sinceLastLegendary / 150 * 100)}% |`,
|
|
325
|
+
`| 闪光保底 | ${pity.totalDropsWithoutShiny} | 800 | ${Math.floor(pity.totalDropsWithoutShiny / 800 * 100)}% |`,
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
if (softPityHints.length > 0) {
|
|
329
|
+
lines.push('', `🌟 ${softPityHints.join(' · ')}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
lines.push('', `*保底计数器在对应品质或更高品质掉落后重置*`);
|
|
333
|
+
return lines.join('\n');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* 渲染机缘面板 (/机缘)
|
|
338
|
+
*/
|
|
339
|
+
export function renderEncounterPanel(profile: UserProfile): string {
|
|
340
|
+
const lines = [
|
|
341
|
+
`### ☁️ 神仙机缘录`,
|
|
342
|
+
'',
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
if (profile.level < 3) {
|
|
346
|
+
lines.push('*等级 ≥ 3(修行者)后解锁机缘系统*');
|
|
347
|
+
return lines.join('\n');
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (profile.encounters.length === 0) {
|
|
351
|
+
lines.push('*尚未遇到任何神仙,继续修行吧...*');
|
|
352
|
+
return lines.join('\n');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
for (const encounter of profile.encounters) {
|
|
356
|
+
const immortal = getImmortalById(encounter.immortalId);
|
|
357
|
+
if (!immortal) continue;
|
|
358
|
+
|
|
359
|
+
const typeLabel = encounter.type === 'guidance' ? '🤍 点化' :
|
|
360
|
+
encounter.type === 'treasure' ? '💛 赐宝' : '💜 收徒';
|
|
361
|
+
const date = new Date(encounter.occurredAt).toLocaleDateString('zh-CN');
|
|
362
|
+
|
|
363
|
+
lines.push(`- ${typeLabel} **${immortal.name}** — ${date}`);
|
|
364
|
+
if (encounter.type === 'treasure' && encounter.treasureId) {
|
|
365
|
+
lines.push(` 赐宝:${getTreasureName(encounter.treasureId)}`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return lines.join('\n');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* 渲染妖魔榜 (/妖魔榜)
|
|
374
|
+
*/
|
|
375
|
+
export function renderLeaderboard(profile: UserProfile, collection: UserCollection): string {
|
|
376
|
+
const upMonster = getWeeklyUpMonster();
|
|
377
|
+
const totalMonsters = getTotalMonsterCount();
|
|
378
|
+
|
|
379
|
+
const lines = [
|
|
380
|
+
`### 🐒 西游妖魔榜`,
|
|
381
|
+
'',
|
|
382
|
+
];
|
|
383
|
+
|
|
384
|
+
if (upMonster) {
|
|
385
|
+
lines.push(
|
|
386
|
+
`#### 📢 本周 UP`,
|
|
387
|
+
`${QUALITY_LABELS[upMonster.quality]} **${upMonster.name}** · ${upMonster.origin}`,
|
|
388
|
+
`> "${upMonster.captureQuote}"`,
|
|
389
|
+
`*在对应品质池中掉落权重 ×5*`,
|
|
390
|
+
'',
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
lines.push(
|
|
395
|
+
`#### 📊 掉落统计`,
|
|
396
|
+
`- **总掉落**:${profile.totalOperations} 次`,
|
|
397
|
+
`- **图鉴完成度**:${collection.entries.length}/${totalMonsters}`,
|
|
398
|
+
`- **闪光收服**:${collection.entries.filter(e => e.isShiny).length} 只`,
|
|
399
|
+
'',
|
|
400
|
+
`#### 🔮 保底状态`,
|
|
401
|
+
`- 小保底:${profile.pityCounters.sinceLastRare}/30`,
|
|
402
|
+
`- 大保底:${profile.pityCounters.sinceLastEpic}/80`,
|
|
403
|
+
`- 天命:${profile.pityCounters.sinceLastLegendary}/150`,
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
return lines.join('\n');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* 渲染法宝使用结果
|
|
411
|
+
*/
|
|
412
|
+
export function renderTreasureUse(treasureName: string, expGained: number, currentExp: number, nextLevelExp: number | null): string {
|
|
413
|
+
const emojiMap: Record<string, string> = {
|
|
414
|
+
'蟠桃': '🍑',
|
|
415
|
+
'人参果': '🍐',
|
|
416
|
+
};
|
|
417
|
+
const emoji = emojiMap[treasureName] ?? '✨';
|
|
418
|
+
|
|
419
|
+
const lines = [
|
|
420
|
+
'---',
|
|
421
|
+
`${emoji} **使用了${treasureName}!**`,
|
|
422
|
+
`修行值 +${expGained}${nextLevelExp ? ` · 当前 ${currentExp}/${nextLevelExp}` : ''}`,
|
|
423
|
+
`> "仙物入腹,周身舒泰。"`,
|
|
424
|
+
];
|
|
425
|
+
|
|
426
|
+
return lines.join('\n');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* 渲染群聊炫耀 (/炫耀)
|
|
431
|
+
*/
|
|
432
|
+
export function renderShowOff(profile: UserProfile, collection: UserCollection): string {
|
|
433
|
+
const shinyCount = collection.entries.filter(e => e.isShiny).length;
|
|
434
|
+
const rarest = findRarestMonster(collection);
|
|
435
|
+
|
|
436
|
+
const lines = [
|
|
437
|
+
`### 🐒 ${profile.title} (Lv.${profile.level}) 的西游妖魔榜`,
|
|
438
|
+
'',
|
|
439
|
+
`图鉴:${collection.entries.length}/${getTotalMonsterCount()} · 闪光:${shinyCount}`,
|
|
440
|
+
];
|
|
441
|
+
|
|
442
|
+
if (rarest) {
|
|
443
|
+
lines.push(`最稀有:${QUALITY_LABELS[rarest.quality]} ${rarest.name}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
lines.push('', `> "此人修为不浅,诸位小心。"`);
|
|
447
|
+
|
|
448
|
+
return lines.join('\n');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// ============ 辅助函数 ============
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* 渲染 emoji 进度条(8 格,用圆形符号表示)
|
|
455
|
+
*
|
|
456
|
+
* 示例:●●●●●○○○ 62%
|
|
457
|
+
*/
|
|
458
|
+
function renderEmojiBar(percent: number): string {
|
|
459
|
+
const total = 8;
|
|
460
|
+
const filled = Math.round(percent / 100 * total);
|
|
461
|
+
return '●'.repeat(filled) + '○'.repeat(total - filled);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function getComboDisplay(combo: number): string {
|
|
465
|
+
if (combo >= 10) return '3.0';
|
|
466
|
+
if (combo >= 5) return '2.0';
|
|
467
|
+
if (combo >= 3) return '1.5';
|
|
468
|
+
return '1.0';
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function getQualityProgress(collection: UserCollection): Array<[string, number, number]> {
|
|
472
|
+
const allMonstersList = getAllMonsters();
|
|
473
|
+
const qualities: Array<{ label: string; quality: string }> = [
|
|
474
|
+
{ label: QUALITY_LABELS.normal, quality: 'normal' },
|
|
475
|
+
{ label: QUALITY_LABELS.fine, quality: 'fine' },
|
|
476
|
+
{ label: QUALITY_LABELS.rare, quality: 'rare' },
|
|
477
|
+
{ label: QUALITY_LABELS.epic, quality: 'epic' },
|
|
478
|
+
{ label: QUALITY_LABELS.legendary, quality: 'legendary' },
|
|
479
|
+
];
|
|
480
|
+
|
|
481
|
+
return qualities.map(({ label, quality }) => {
|
|
482
|
+
const total = allMonstersList.filter(m => m.quality === quality).length;
|
|
483
|
+
const collected = allMonstersList.filter(m =>
|
|
484
|
+
m.quality === quality && collection.entries.some(e => e.monsterId === m.id && !e.isShiny)
|
|
485
|
+
).length;
|
|
486
|
+
return [label, collected, total];
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function findRarestMonster(collection: UserCollection): Monster | null {
|
|
491
|
+
const qualityPriority = ['legendary', 'epic', 'rare', 'fine', 'normal'];
|
|
492
|
+
|
|
493
|
+
for (const quality of qualityPriority) {
|
|
494
|
+
const entry = collection.entries.find(e => {
|
|
495
|
+
const monster = getMonsterById(e.monsterId);
|
|
496
|
+
return monster?.quality === quality;
|
|
497
|
+
});
|
|
498
|
+
if (entry) {
|
|
499
|
+
return getMonsterById(entry.monsterId) ?? null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return null;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// ============ v2: 悬赏令渲染 ============
|
|
507
|
+
|
|
508
|
+
import type { Bounty, DailyBountyState, RandomEvent, ChallengeEvent } from './types.ts';
|
|
509
|
+
|
|
510
|
+
const BOUNTY_TIER_LABELS: Record<string, string> = {
|
|
511
|
+
bronze: '🥉 铜令',
|
|
512
|
+
silver: '🥈 银令',
|
|
513
|
+
gold: '🥇 金令',
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 渲染悬赏令面板 (/悬赏)
|
|
518
|
+
*/
|
|
519
|
+
export function renderBountyPanel(profile: UserProfile): string {
|
|
520
|
+
const bountyState = profile.dailyBounty;
|
|
521
|
+
|
|
522
|
+
if (!bountyState || bountyState.bounties.length === 0) {
|
|
523
|
+
return [
|
|
524
|
+
`### 📜 今日悬赏令`,
|
|
525
|
+
'',
|
|
526
|
+
'*今日悬赏令尚未生成,执行一次 dws 命令即可刷新。*',
|
|
527
|
+
].join('\n');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const lines = [
|
|
531
|
+
`### 📜 今日悬赏令`,
|
|
532
|
+
'',
|
|
533
|
+
];
|
|
534
|
+
|
|
535
|
+
for (const bounty of bountyState.bounties) {
|
|
536
|
+
const tierLabel = BOUNTY_TIER_LABELS[bounty.tier] ?? bounty.tier;
|
|
537
|
+
const status = bounty.completed ? '✅' : '⬜';
|
|
538
|
+
const progressPercent = Math.min(100, Math.floor(bounty.current / bounty.target * 100));
|
|
539
|
+
const progressBar = renderEmojiBar(progressPercent);
|
|
540
|
+
|
|
541
|
+
lines.push(
|
|
542
|
+
`${status} **${tierLabel}**:${bounty.description}`,
|
|
543
|
+
` 奖励:+${bounty.reward.exp} 修行值`,
|
|
544
|
+
` 进度:${bounty.current}/${bounty.target} ${progressBar} ${progressPercent}%`,
|
|
545
|
+
'',
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// 刷新倒计时
|
|
550
|
+
const now = new Date();
|
|
551
|
+
const tomorrow = new Date(now);
|
|
552
|
+
tomorrow.setHours(24, 0, 0, 0);
|
|
553
|
+
const remainingMs = tomorrow.getTime() - now.getTime();
|
|
554
|
+
const remainingHours = Math.floor(remainingMs / (60 * 60 * 1000));
|
|
555
|
+
const remainingMinutes = Math.floor((remainingMs % (60 * 60 * 1000)) / (60 * 1000));
|
|
556
|
+
|
|
557
|
+
lines.push(`⏰ 刷新倒计时:${remainingHours} 小时 ${remainingMinutes} 分`);
|
|
558
|
+
|
|
559
|
+
// 历史统计
|
|
560
|
+
const history = profile.bountyHistory;
|
|
561
|
+
lines.push(
|
|
562
|
+
'',
|
|
563
|
+
`#### 📊 悬赏历史`,
|
|
564
|
+
`累计完成:${history.totalCompleted} 张 (🥉${history.bronzeCompleted} 🥈${history.silverCompleted} 🥇${history.goldCompleted})`,
|
|
565
|
+
`连续全清:${history.consecutiveFullClear} 天`,
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
return lines.join('\n');
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* 渲染悬赏令完成通知(紧凑单行)
|
|
573
|
+
*/
|
|
574
|
+
export function renderBountyComplete(bounty: Bounty): string {
|
|
575
|
+
const tierLabel = BOUNTY_TIER_LABELS[bounty.tier] ?? bounty.tier;
|
|
576
|
+
return `\n📜 **${tierLabel}完成!** 「${bounty.description}」 · +${bounty.reward.exp}`;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// ============ v2: 随机事件渲染 ============
|
|
580
|
+
|
|
581
|
+
const EVENT_CATEGORY_EMOJI: Record<string, string> = {
|
|
582
|
+
blessing: '🌟',
|
|
583
|
+
challenge: '⚔️',
|
|
584
|
+
disaster: '😈',
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* 渲染随机事件触发通知(紧凑版)
|
|
589
|
+
*/
|
|
590
|
+
export function renderEventTrigger(event: RandomEvent | ChallengeEvent): string {
|
|
591
|
+
const emoji = EVENT_CATEGORY_EMOJI[event.category] ?? '🎲';
|
|
592
|
+
const durationStr = event.duration.type !== 'instant' ? ` (${event.duration.remaining}/${event.duration.total})` : '';
|
|
593
|
+
|
|
594
|
+
if (event.category === 'challenge') {
|
|
595
|
+
const challenge = event as ChallengeEvent;
|
|
596
|
+
return `\n${emoji} **${event.name}** — ${event.description} · 🏆+${challenge.successReward.exp} 💀-${challenge.failurePenalty.expLoss} · ⏰${challenge.operationLimit}次`;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const resolvePart = (event.category === 'disaster' && event.resolution) ? ` · 💡${event.resolution.description}` : '';
|
|
600
|
+
return `\n${emoji} **${event.name}** — ${event.description}${durationStr}${resolvePart}`;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* 渲染挑战事件结果(紧凑单行)
|
|
605
|
+
*/
|
|
606
|
+
export function renderChallengeResult(event: ChallengeEvent, success: boolean): string {
|
|
607
|
+
if (success) {
|
|
608
|
+
const pityPart = event.successReward.pityBonus ? ` · 保底+${event.successReward.pityBonus}` : '';
|
|
609
|
+
return `\n🏆 **${event.name} · 挑战成功!** +${event.successReward.exp}${pityPart}`;
|
|
610
|
+
}
|
|
611
|
+
const comboPart = event.failurePenalty.comboReset ? ' · 连击归零' : '';
|
|
612
|
+
return `\n💀 **${event.name} · 挑战失败** -${event.failurePenalty.expLoss}${comboPart}`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* 渲染灾厄事件化解通知
|
|
617
|
+
*/
|
|
618
|
+
export function renderDisasterResolved(event: RandomEvent): string {
|
|
619
|
+
return [
|
|
620
|
+
'',
|
|
621
|
+
'---',
|
|
622
|
+
`🛡️ **${event.name} · 已化解!**`,
|
|
623
|
+
`*灾厄消散,天地清明。*`,
|
|
624
|
+
].join('\n');
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* 渲染事件面板 (/事件)
|
|
629
|
+
*/
|
|
630
|
+
export function renderEventPanel(profile: UserProfile): string {
|
|
631
|
+
const activeState = profile.activeEvents;
|
|
632
|
+
const lines = [
|
|
633
|
+
`### 🎲 随机事件`,
|
|
634
|
+
'',
|
|
635
|
+
];
|
|
636
|
+
|
|
637
|
+
// 当前活跃事件
|
|
638
|
+
if (activeState.currentEvents.length === 0 && !activeState.activeChallenge) {
|
|
639
|
+
lines.push('*当前没有活跃的随机事件。*');
|
|
640
|
+
} else {
|
|
641
|
+
if (activeState.currentEvents.length > 0) {
|
|
642
|
+
lines.push(`#### 当前生效`);
|
|
643
|
+
for (const event of activeState.currentEvents) {
|
|
644
|
+
const emoji = EVENT_CATEGORY_EMOJI[event.category] ?? '🎲';
|
|
645
|
+
const remaining = event.duration.type !== 'instant'
|
|
646
|
+
? ` (剩余 ${event.duration.remaining}/${event.duration.total})`
|
|
647
|
+
: '';
|
|
648
|
+
lines.push(`- ${emoji} **${event.name}**:${event.description}${remaining}`);
|
|
649
|
+
if (event.resolution) {
|
|
650
|
+
lines.push(` 💡 化解:${event.resolution.description}`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
lines.push('');
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (activeState.activeChallenge) {
|
|
657
|
+
const challenge = activeState.activeChallenge;
|
|
658
|
+
lines.push(
|
|
659
|
+
`#### ⚔️ 进行中的挑战`,
|
|
660
|
+
`**${challenge.name}**:${challenge.description}`,
|
|
661
|
+
`进度:${challenge.challengeCondition.current}/${challenge.challengeCondition.target}`,
|
|
662
|
+
`操作次数:${challenge.progress.operationsUsed}/${challenge.progress.operationLimit}`,
|
|
663
|
+
'',
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// 事件统计
|
|
669
|
+
const stats = profile.eventStats;
|
|
670
|
+
lines.push(
|
|
671
|
+
`#### 📊 事件统计`,
|
|
672
|
+
`- **累计触发**:${stats.totalTriggered} 次`,
|
|
673
|
+
`- **挑战完成**:${stats.challengesCompleted} 次`,
|
|
674
|
+
`- **挑战失败**:${stats.challengesFailed} 次`,
|
|
675
|
+
`- **灾厄化解**:${stats.disastersResolved} 次`,
|
|
676
|
+
);
|
|
677
|
+
|
|
678
|
+
return lines.join('\n');
|
|
679
|
+
}
|