polycopy 0.0.1

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.
@@ -0,0 +1,932 @@
1
+ "use strict";
2
+ const readline = require("readline");
3
+ const fsExtra = require("fs-extra");
4
+ const path = require("path");
5
+ const grammy = require("grammy");
6
+ const winston = require("winston");
7
+ const DailyRotateFile = require("winston-daily-rotate-file");
8
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
12
+ if (e) {
13
+ for (const k in e) {
14
+ if (k !== "default") {
15
+ const d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: () => e[k]
19
+ });
20
+ }
21
+ }
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+ const readline__namespace = /* @__PURE__ */ _interopNamespace(readline);
27
+ const fsExtra__default = /* @__PURE__ */ _interopDefault(fsExtra);
28
+ const path__default = /* @__PURE__ */ _interopDefault(path);
29
+ const winston__default = /* @__PURE__ */ _interopDefault(winston);
30
+ const DailyRotateFile__default = /* @__PURE__ */ _interopDefault(DailyRotateFile);
31
+ class LocalStorage {
32
+ dbPath;
33
+ data;
34
+ constructor(dbFileName) {
35
+ this.dbPath = path__default.default.join(process.cwd(), `localstorage/${dbFileName}.json`);
36
+ if (fsExtra__default.default.existsSync(this.dbPath)) {
37
+ this.data = fsExtra__default.default.readJSONSync(this.dbPath);
38
+ } else {
39
+ this.data = {};
40
+ }
41
+ }
42
+ getItem(key, defaultValue) {
43
+ return key in this.data ? this.data[key] : defaultValue;
44
+ }
45
+ setItem(key, value) {
46
+ this.data[key] = value;
47
+ fsExtra__default.default.outputJSONSync(this.dbPath, this.data);
48
+ return value;
49
+ }
50
+ }
51
+ const configLocal = new LocalStorage("config");
52
+ function validateConfig(config) {
53
+ const missingFields = [];
54
+ if (!config.telegram?.botToken) {
55
+ missingFields.push("telegram.botToken");
56
+ }
57
+ return {
58
+ valid: missingFields.length === 0,
59
+ missingFields
60
+ };
61
+ }
62
+ function hasPolyMarketConfig(config) {
63
+ return !!(config.polymarket?.privateKey && config.polymarket.privateKey.trim());
64
+ }
65
+ function hasTradingConfig(config) {
66
+ const trading = config.trading;
67
+ return !!(trading?.amountMode && trading?.amountValue !== void 0 && trading?.amountValue !== null && trading?.buyPrice !== void 0 && trading?.buyPrice !== null && trading?.sellPrice !== void 0 && trading?.sellPrice !== null);
68
+ }
69
+ function canAutoTrade(config) {
70
+ return hasPolyMarketConfig(config) && hasTradingConfig(config);
71
+ }
72
+ const LOG_DIR = path__default.default.resolve(process.cwd(), "logs");
73
+ const formatTimestamp = () => {
74
+ const now = /* @__PURE__ */ new Date();
75
+ const pad = (n) => n.toString().padStart(2, "0");
76
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
77
+ };
78
+ const customFormat = winston__default.default.format.printf(({ message }) => {
79
+ return `[${formatTimestamp()}] ${message}`;
80
+ });
81
+ const fileTransport = new DailyRotateFile__default.default({
82
+ dirname: LOG_DIR,
83
+ filename: "%DATE%",
84
+ extension: ".log",
85
+ datePattern: "YYYY-MM-DD",
86
+ // 按天创建文件
87
+ maxSize: "2m",
88
+ // 超过 2MB 自动轮转(会在文件名后加序号)
89
+ maxFiles: 10,
90
+ // 最多 10 个文件
91
+ format: customFormat,
92
+ // 不创建 current.log 符号链接
93
+ createSymlink: false
94
+ });
95
+ const winstonLogger = winston__default.default.createLogger({
96
+ level: "info",
97
+ transports: [fileTransport]
98
+ });
99
+ class Logger {
100
+ initialized = false;
101
+ /**
102
+ * 初始化日志模块(输出日志路径信息)
103
+ */
104
+ init() {
105
+ if (this.initialized) return;
106
+ this.initialized = true;
107
+ console.log(`
108
+ 📁 日志目录: ${LOG_DIR}`);
109
+ }
110
+ /**
111
+ * 获取带时间前缀的消息(用于控制台输出)
112
+ */
113
+ withTimestamp(message) {
114
+ return `[${formatTimestamp()}] ${message}`;
115
+ }
116
+ /**
117
+ * 写入日志(同时输出到控制台和文件)
118
+ */
119
+ write(message, addNewLine = true) {
120
+ const consoleMsg = addNewLine ? `
121
+ ${this.withTimestamp(message)}` : this.withTimestamp(message);
122
+ console.log(consoleMsg);
123
+ winstonLogger.info(message);
124
+ }
125
+ /**
126
+ * 输出信息日志(前后自动添加空行)
127
+ */
128
+ info(message, ...args) {
129
+ const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
130
+ this.write(formatted);
131
+ }
132
+ /**
133
+ * 输出成功日志(前后自动添加空行)
134
+ */
135
+ success(message, ...args) {
136
+ const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
137
+ this.write(`✅ ${formatted}`);
138
+ }
139
+ /**
140
+ * 输出警告日志(前后自动添加空行)
141
+ */
142
+ warning(message, ...args) {
143
+ const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
144
+ this.write(`⚠️ ${formatted}`);
145
+ }
146
+ /**
147
+ * 输出错误日志(前后自动添加空行)
148
+ */
149
+ error(message, ...args) {
150
+ const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
151
+ console.error(`
152
+ ${this.withTimestamp(`❌ ${formatted}`)}`);
153
+ winstonLogger.error(`❌ ${formatted}`);
154
+ }
155
+ /**
156
+ * 输出章节标题(前后自动添加空行)
157
+ */
158
+ section(title) {
159
+ this.write(`=== ${title} ===`);
160
+ }
161
+ /**
162
+ * 输出列表项(不添加空行,用于连续输出)
163
+ */
164
+ item(message, indent = 0) {
165
+ const indentStr = " ".repeat(indent);
166
+ const formatted = `${indentStr}${message}`;
167
+ console.log(this.withTimestamp(formatted));
168
+ winstonLogger.info(formatted);
169
+ }
170
+ /**
171
+ * 输出普通文本(不添加空行,用于连续输出)
172
+ */
173
+ text(message, ...args) {
174
+ const formatted = args.length > 0 ? `${message} ${args.join(" ")}` : message;
175
+ console.log(this.withTimestamp(formatted));
176
+ winstonLogger.info(formatted);
177
+ }
178
+ /**
179
+ * 输出空行
180
+ */
181
+ blank() {
182
+ console.log("");
183
+ }
184
+ /**
185
+ * 输出带前缀的信息(不添加空行)
186
+ * 用于在同一组日志中输出多行信息
187
+ */
188
+ line(prefix, message, ...args) {
189
+ const formatted = args.length > 0 ? ` ${prefix} ${message} ${args.join(" ")}` : ` ${prefix} ${message}`;
190
+ console.log(this.withTimestamp(formatted));
191
+ winstonLogger.info(formatted);
192
+ }
193
+ }
194
+ const logger = new Logger();
195
+ function parseSignalMetadata(messageText) {
196
+ const match = messageText.match(/🤖\s*({.*})/s);
197
+ if (!match) {
198
+ return null;
199
+ }
200
+ try {
201
+ const jsonStr = match[1];
202
+ const metadata = JSON.parse(jsonStr);
203
+ if (!metadata.eventId || !metadata.assetId || !metadata.outcome || !metadata.endTime) {
204
+ logger.error("解析失败: 缺少必需字段", {
205
+ hasEventId: !!metadata.eventId,
206
+ hasAssetId: !!metadata.assetId,
207
+ hasOutcome: !!metadata.outcome,
208
+ hasEndTime: !!metadata.endTime
209
+ });
210
+ return null;
211
+ }
212
+ if (metadata.outcome !== "Up" && metadata.outcome !== "Down") {
213
+ logger.error("解析失败: outcome 值无效", {
214
+ outcome: metadata.outcome,
215
+ expected: "Up 或 Down"
216
+ });
217
+ return null;
218
+ }
219
+ if (typeof metadata.endTime !== "number" || metadata.endTime <= 0) {
220
+ logger.error("解析失败: endTime 值无效", {
221
+ endTime: metadata.endTime,
222
+ expected: "毫秒级时间戳"
223
+ });
224
+ return null;
225
+ }
226
+ return metadata;
227
+ } catch (error) {
228
+ logger.error("解析信号元数据失败:", error);
229
+ if (error instanceof Error) {
230
+ logger.line("", `错误详情: ${error.message}`);
231
+ }
232
+ return null;
233
+ }
234
+ }
235
+ class TelegramListener {
236
+ bot;
237
+ onSignalCallback;
238
+ onChatIdsChangedCallback;
239
+ targetChatIds;
240
+ // 运行时监听的频道 ID 集合(自动管理)
241
+ constructor(botToken, targetChatIds = []) {
242
+ this.bot = new grammy.Bot(botToken);
243
+ this.targetChatIds = new Set(targetChatIds);
244
+ this.setupHandlers();
245
+ }
246
+ /**
247
+ * 设置消息处理器
248
+ */
249
+ setupHandlers() {
250
+ this.bot.on("channel_post", async (ctx) => {
251
+ await this.handleChannelPost(ctx);
252
+ });
253
+ this.bot.on("edited_channel_post", async (ctx) => {
254
+ await this.handleChannelPost(ctx);
255
+ });
256
+ this.bot.on("my_chat_member", async (ctx) => {
257
+ await this.handleMyChatMemberUpdate(ctx);
258
+ });
259
+ this.bot.catch((err) => {
260
+ logger.error("Telegram Bot 错误:", err);
261
+ if (err.error) {
262
+ logger.line("", `错误详情: ${err.error}`);
263
+ }
264
+ if (err.ctx) {
265
+ logger.line("", `上下文信息:`, {
266
+ chatId: err.ctx.chat?.id,
267
+ messageId: err.ctx.message?.message_id
268
+ });
269
+ }
270
+ });
271
+ }
272
+ /**
273
+ * 处理 Bot 自己的成员状态变更
274
+ */
275
+ async handleMyChatMemberUpdate(ctx) {
276
+ const update = ctx.myChatMember;
277
+ if (!update) {
278
+ return;
279
+ }
280
+ const chatId = update.chat.id;
281
+ const chatTitle = update.chat.title || "未知频道";
282
+ const oldStatus = update.old_chat_member.status;
283
+ const newStatus = update.new_chat_member.status;
284
+ const chatType = update.chat.type;
285
+ if (chatType !== "channel") {
286
+ return;
287
+ }
288
+ const isAdmin = newStatus === "administrator" || newStatus === "creator";
289
+ const wasAdmin = oldStatus === "administrator" || oldStatus === "creator";
290
+ const isRemoved = newStatus === "left" || newStatus === "kicked";
291
+ const wasRemoved = oldStatus === "left" || oldStatus === "kicked";
292
+ if (oldStatus !== newStatus) {
293
+ let action = "";
294
+ let canListen = false;
295
+ let reason = "";
296
+ if (isRemoved) {
297
+ action = "❌ 频道主已移除 Bot";
298
+ canListen = false;
299
+ reason = "Bot 已被移除";
300
+ } else if (wasRemoved && isAdmin) {
301
+ action = "✅ 频道主已添加 Bot 为管理员";
302
+ canListen = true;
303
+ } else if (isAdmin && !wasAdmin) {
304
+ action = "✅ 频道主已授予 Bot 管理员权限";
305
+ canListen = true;
306
+ } else if (wasAdmin && !isAdmin) {
307
+ action = "⚠️ 频道主已移除 Bot 的管理员权限";
308
+ canListen = false;
309
+ reason = "Bot 不再是管理员";
310
+ } else if (wasRemoved && !isAdmin) {
311
+ action = "⚠️ 频道主已添加 Bot,但未授予管理员权限";
312
+ canListen = false;
313
+ reason = "需要管理员权限才能监听";
314
+ } else if (oldStatus === "restricted" && isAdmin) {
315
+ action = "✅ 频道主已授予 Bot 管理员权限";
316
+ canListen = true;
317
+ } else if (wasAdmin && newStatus === "restricted") {
318
+ action = "⚠️ 频道主已限制 Bot 的权限";
319
+ canListen = false;
320
+ reason = "Bot 权限受限,无法监听";
321
+ } else if (oldStatus === "restricted" && !isAdmin) {
322
+ action = `状态变更: ${oldStatus} → ${newStatus}`;
323
+ canListen = false;
324
+ reason = "需要管理员权限才能监听";
325
+ } else {
326
+ action = `状态变更: ${oldStatus} → ${newStatus}`;
327
+ canListen = isAdmin;
328
+ if (!canListen) {
329
+ reason = "需要管理员权限才能监听";
330
+ }
331
+ }
332
+ logger.info(`📢 频道状态更新: ${chatTitle}`);
333
+ logger.line("", action);
334
+ logger.line("", `当前状态: ${canListen ? "✅ 可监听预测消息" : "❌ 无法监听预测消息"}`);
335
+ if (reason) {
336
+ logger.line("", `原因: ${reason}`);
337
+ }
338
+ }
339
+ if (isAdmin && !this.targetChatIds.has(chatId)) {
340
+ this.targetChatIds.add(chatId);
341
+ await this.saveChatIds();
342
+ logger.line("📡", "已自动开始监听此频道");
343
+ }
344
+ if ((isRemoved || !isAdmin) && this.targetChatIds.has(chatId)) {
345
+ this.targetChatIds.delete(chatId);
346
+ await this.saveChatIds();
347
+ if (isRemoved) {
348
+ logger.line("⏹️", "已停止监听此频道(Bot 已被移除)");
349
+ } else {
350
+ logger.line("⏹️", "已停止监听此频道(Bot 不再是管理员)");
351
+ }
352
+ }
353
+ }
354
+ /**
355
+ * 保存频道 ID 列表到配置(用于持久化)
356
+ */
357
+ async saveChatIds() {
358
+ if (!this.onChatIdsChangedCallback) {
359
+ return;
360
+ }
361
+ try {
362
+ const chatIds = Array.from(this.targetChatIds);
363
+ await this.onChatIdsChangedCallback(chatIds);
364
+ } catch (error) {
365
+ logger.error("保存频道 ID 列表时出错:", error);
366
+ }
367
+ }
368
+ /**
369
+ * 处理频道消息(channel_post 和 edited_channel_post)
370
+ */
371
+ async handleChannelPost(ctx) {
372
+ const message = ctx.channelPost || ctx.editedChannelPost;
373
+ if (!message) {
374
+ return;
375
+ }
376
+ await this.processMessage(message, ctx);
377
+ }
378
+ /**
379
+ * 处理频道消息(统一的消息处理逻辑)
380
+ */
381
+ async processMessage(message, ctx) {
382
+ if (!message) {
383
+ return;
384
+ }
385
+ const chatId = message.chat.id;
386
+ const chatTitle = message.chat.title || "未知频道";
387
+ const isChannel = chatId < 0 && chatId.toString().startsWith("-100");
388
+ const messageText = message.text || message.caption || "";
389
+ if (!messageText) {
390
+ return;
391
+ }
392
+ if (isChannel && !this.targetChatIds.has(chatId)) {
393
+ this.targetChatIds.add(chatId);
394
+ logger.info(`➕ 自动添加频道到监听列表: ${chatTitle} (ID: ${chatId})`);
395
+ await this.saveChatIds();
396
+ logger.line("💾", "已保存到配置文件");
397
+ }
398
+ if (!isChannel) {
399
+ return;
400
+ }
401
+ if (!this.targetChatIds.has(chatId)) {
402
+ return;
403
+ }
404
+ const metadata = parseSignalMetadata(messageText);
405
+ if (!metadata) {
406
+ if (messageText.includes("🤖")) {
407
+ const emojiIndex = messageText.indexOf("🤖");
408
+ const jsonStart = emojiIndex + 1;
409
+ const jsonEnd = Math.min(jsonStart + 200, messageText.length);
410
+ logger.warning(`解析预测消息失败:`);
411
+ logger.line("", `频道 ID: ${chatId}`);
412
+ logger.line("", `消息片段: ${messageText.substring(emojiIndex, jsonEnd)}${messageText.length > jsonEnd ? "..." : ""}`);
413
+ logger.line("", "提示: 请检查消息格式是否正确");
414
+ }
415
+ return;
416
+ }
417
+ const parsedSignal = {
418
+ metadata,
419
+ rawMessage: messageText,
420
+ messageId: message.message_id,
421
+ chatId,
422
+ timestamp: message.date * 1e3
423
+ };
424
+ if (this.onSignalCallback) {
425
+ try {
426
+ await this.onSignalCallback(parsedSignal);
427
+ } catch (error) {
428
+ logger.error("处理信号时出错:", error);
429
+ }
430
+ }
431
+ }
432
+ /**
433
+ * 设置信号回调函数
434
+ */
435
+ onSignal(callback) {
436
+ this.onSignalCallback = callback;
437
+ }
438
+ /**
439
+ * 设置频道 ID 列表变更回调函数(当频道自动添加/移除时调用,用于持久化)
440
+ */
441
+ onChatIdsChanged(callback) {
442
+ this.onChatIdsChangedCallback = callback;
443
+ }
444
+ /**
445
+ * 设置监听的频道 ID 列表(用于启动时加载已保存的配置)
446
+ */
447
+ setTargetChatIds(chatIds) {
448
+ this.targetChatIds = new Set(chatIds);
449
+ }
450
+ /**
451
+ * 启动监听
452
+ * @param onReady 可选回调,在频道检查完成后、开始监听之前调用
453
+ */
454
+ async start(onReady) {
455
+ try {
456
+ logger.info("正在启动 Bot...");
457
+ const botInfo = await this.bot.api.getMe();
458
+ const botId = botInfo.id;
459
+ logger.success(`Bot 信息: @${botInfo.username} (ID: ${botId})`);
460
+ await this.checkAndDisplayChannels(botId);
461
+ if (onReady) {
462
+ await onReady();
463
+ }
464
+ logger.info("开始监听预测消息...");
465
+ await this.bot.start({
466
+ drop_pending_updates: true,
467
+ allowed_updates: [
468
+ "channel_post",
469
+ "edited_channel_post",
470
+ "my_chat_member"
471
+ ]
472
+ });
473
+ } catch (error) {
474
+ logger.error("启动 Bot 失败:", error);
475
+ throw error;
476
+ }
477
+ }
478
+ /**
479
+ * 检查并显示 Bot 所在的频道
480
+ * 验证配置文件中保存的频道,检查 Bot 是否仍在频道中以及是否是管理员
481
+ */
482
+ async checkAndDisplayChannels(botId) {
483
+ const chatIds = Array.from(this.targetChatIds);
484
+ if (chatIds.length === 0) {
485
+ logger.info(`📋 当前没有保存的频道配置`);
486
+ logger.line("", "提示: 当 Bot 被添加为频道管理员时,会自动添加到监听列表");
487
+ return;
488
+ }
489
+ logger.info(`📋 检查 Bot 所在的频道:`);
490
+ const validChannels = [];
491
+ const invalidChannels = [];
492
+ for (const chatId of chatIds) {
493
+ try {
494
+ const chat = await this.bot.api.getChat(chatId);
495
+ const chatTitle = chat.title || "未知频道";
496
+ try {
497
+ const member = await this.bot.api.getChatMember(chatId, botId);
498
+ const isAdmin = member.status === "administrator" || member.status === "creator";
499
+ validChannels.push({
500
+ id: chatId,
501
+ title: chatTitle,
502
+ isAdmin
503
+ });
504
+ } catch (error) {
505
+ invalidChannels.push({
506
+ id: chatId,
507
+ reason: "无法获取成员信息(可能已被移除)"
508
+ });
509
+ }
510
+ } catch (error) {
511
+ invalidChannels.push({
512
+ id: chatId,
513
+ reason: "无法获取频道信息(可能已被移除)"
514
+ });
515
+ }
516
+ }
517
+ if (validChannels.length > 0) {
518
+ const adminChannels = validChannels.filter((c) => c.isAdmin);
519
+ const nonAdminChannels = validChannels.filter((c) => !c.isAdmin);
520
+ if (adminChannels.length > 0) {
521
+ logger.info(`📡 可监听频道 (${adminChannels.length} 个):`);
522
+ adminChannels.forEach((channel, index) => {
523
+ logger.item(`${index + 1}. ${channel.title} (ID: ${channel.id}) 👑 管理员`, 1);
524
+ });
525
+ }
526
+ if (nonAdminChannels.length > 0) {
527
+ logger.warning(`无法监听频道 (${nonAdminChannels.length} 个):`);
528
+ nonAdminChannels.forEach((channel, index) => {
529
+ logger.item(`${index + 1}. ${channel.title}`, 1);
530
+ logger.item(`ID: ${channel.id}`, 2);
531
+ logger.item(`状态: 🚫 非管理员(无法接收预测消息)`, 2);
532
+ logger.item(`原因: 需要管理员权限才能监听`, 2);
533
+ });
534
+ nonAdminChannels.forEach((channel) => {
535
+ this.targetChatIds.delete(channel.id);
536
+ });
537
+ if (nonAdminChannels.length > 0) {
538
+ await this.saveChatIds();
539
+ logger.line("💾", "已清理无法监听的频道并保存配置");
540
+ }
541
+ }
542
+ }
543
+ if (invalidChannels.length > 0) {
544
+ logger.error(`无效频道 (${invalidChannels.length} 个):`);
545
+ invalidChannels.forEach((channel, index) => {
546
+ logger.item(`${index + 1}. ID: ${channel.id}`, 1);
547
+ logger.item(`原因: ${channel.reason}`, 2);
548
+ });
549
+ invalidChannels.forEach((channel) => {
550
+ this.targetChatIds.delete(channel.id);
551
+ });
552
+ if (invalidChannels.length > 0) {
553
+ await this.saveChatIds();
554
+ logger.line("💾", "已清理无效频道并保存配置");
555
+ }
556
+ }
557
+ }
558
+ /**
559
+ * 停止监听
560
+ */
561
+ async stop() {
562
+ await this.bot.stop();
563
+ }
564
+ /**
565
+ * 获取 bot 信息(用于验证 token 是否有效)
566
+ */
567
+ async getBotInfo() {
568
+ try {
569
+ return await this.bot.api.getMe();
570
+ } catch (error) {
571
+ throw new Error(`获取 Bot 信息失败: ${error}`);
572
+ }
573
+ }
574
+ }
575
+ let globalRl = null;
576
+ function getReadlineInterface() {
577
+ if (!globalRl) {
578
+ globalRl = readline__namespace.createInterface({
579
+ input: process.stdin,
580
+ output: process.stdout
581
+ });
582
+ }
583
+ return globalRl;
584
+ }
585
+ function question(query) {
586
+ const rl = getReadlineInterface();
587
+ return new Promise((resolve) => {
588
+ rl.question(query, resolve);
589
+ });
590
+ }
591
+ async function validateBotToken(botToken) {
592
+ try {
593
+ const listener = new TelegramListener(botToken);
594
+ await listener.getBotInfo();
595
+ await listener.stop();
596
+ return true;
597
+ } catch (error) {
598
+ logger.error(`Bot Token 验证失败: ${error}`);
599
+ return false;
600
+ }
601
+ }
602
+ async function runConfigWizard(existingConfig) {
603
+ logger.section("PolyMarket 跟单机器人配置向导");
604
+ const currentTelegram = existingConfig?.telegram || configLocal.getItem("telegram");
605
+ const currentPolymarket = existingConfig?.polymarket || configLocal.getItem("polymarket");
606
+ const currentTrading = existingConfig?.trading || configLocal.getItem("trading");
607
+ let botToken = "";
608
+ while (!botToken) {
609
+ const currentToken = currentTelegram?.botToken || "";
610
+ const currentDisplay = currentToken ? `${currentToken.substring(0, 10)}...` : "未配置";
611
+ const hint = currentToken ? "回车保持,或输入新值" : "必填";
612
+ const input = await question(` - Bot Token [${currentDisplay}] (${hint}): `);
613
+ botToken = input.trim();
614
+ if (!botToken && currentToken) {
615
+ botToken = currentToken;
616
+ break;
617
+ }
618
+ if (!botToken) {
619
+ console.log("❌ Bot Token 不能为空");
620
+ continue;
621
+ }
622
+ console.log("正在验证...");
623
+ const isValid = await validateBotToken(botToken);
624
+ if (!isValid) {
625
+ console.log("❌ Bot Token 无效,请重新输入");
626
+ botToken = "";
627
+ continue;
628
+ }
629
+ console.log("✅ 验证成功");
630
+ }
631
+ const currentAdminChatId = currentTelegram?.adminChatId;
632
+ const adminHint = currentAdminChatId ? "回车保持,skip 清除" : "回车跳过";
633
+ const adminChatIdInput = await question(
634
+ ` - 管理员 Chat ID [${currentAdminChatId || "未配置"}] (${adminHint}): `
635
+ );
636
+ let adminChatId = currentAdminChatId;
637
+ if (adminChatIdInput.trim().toLowerCase() === "skip") {
638
+ adminChatId = void 0;
639
+ } else if (adminChatIdInput.trim()) {
640
+ const parsed = parseInt(adminChatIdInput.trim());
641
+ if (!isNaN(parsed)) {
642
+ adminChatId = parsed;
643
+ } else {
644
+ console.log("Chat ID 格式无效,已跳过");
645
+ }
646
+ }
647
+ logger.section("PolyMarket 配置(可选)");
648
+ logger.text("提示: 以下配置项都可以跳过,留空后将无法自动跟单");
649
+ const currentPrivateKey = currentPolymarket?.privateKey || "";
650
+ const pkDisplay = currentPrivateKey ? "已配置" : "未配置";
651
+ const pkHint = currentPrivateKey ? "回车保持,skip 清除" : "回车跳过";
652
+ const privateKeyInput = await question(` - 钱包私钥 [${pkDisplay}] (${pkHint}): `);
653
+ const privateKeyTrimmed = privateKeyInput.trim().toLowerCase() === "skip" ? "" : privateKeyInput.trim() || currentPrivateKey;
654
+ let funderAddress = currentPolymarket?.funderAddress;
655
+ let builderCreds = currentPolymarket?.builderCreds;
656
+ if (privateKeyTrimmed) {
657
+ const funderDisplay = funderAddress || "未配置";
658
+ const funderHint = funderAddress ? "回车保持,skip 清除" : "回车跳过";
659
+ const funderInput = await question(` - Polymarket 账户地址 [${funderDisplay}] (${funderHint}): `);
660
+ if (funderInput.trim().toLowerCase() === "skip") {
661
+ funderAddress = void 0;
662
+ } else if (funderInput.trim()) {
663
+ funderAddress = funderInput.trim();
664
+ }
665
+ const hasBuilderCreds = !!builderCreds;
666
+ const builderDisplay = hasBuilderCreds ? "已配置" : "未配置";
667
+ const builderHint = hasBuilderCreds ? "y=重新配置, n=清除, 回车=保持" : "y=配置, 回车=跳过, 前往 polymarket.com/settings?tab=builder 获取";
668
+ const needBuilderCreds = await question(
669
+ ` - Builder API [${builderDisplay}] (${builderHint}): `
670
+ );
671
+ const needBuilderCredsLower = needBuilderCreds.trim().toLowerCase();
672
+ const shouldConfigBuilder = needBuilderCredsLower === "y" || !needBuilderCredsLower && hasBuilderCreds;
673
+ if (shouldConfigBuilder) {
674
+ const keyDisplay = builderCreds?.key ? `${builderCreds.key.substring(0, 10)}...` : "未配置";
675
+ const builderKey = await question(` - API Key [${keyDisplay}] (回车保持): `);
676
+ const secretDisplay = builderCreds?.secret ? "已配置" : "未配置";
677
+ const builderSecret = await question(` - API Secret [${secretDisplay}] (回车保持): `);
678
+ const passDisplay = builderCreds?.passphrase ? "已配置" : "未配置";
679
+ const builderPassphrase = await question(` - API Passphrase [${passDisplay}] (回车保持): `);
680
+ if (!builderKey.trim() && !builderSecret.trim() && !builderPassphrase.trim()) {
681
+ builderCreds = currentPolymarket?.builderCreds;
682
+ } else {
683
+ builderCreds = {
684
+ key: builderKey.trim() || builderCreds?.key || "",
685
+ secret: builderSecret.trim() || builderCreds?.secret || "",
686
+ passphrase: builderPassphrase.trim() || builderCreds?.passphrase || ""
687
+ };
688
+ }
689
+ } else {
690
+ builderCreds = void 0;
691
+ }
692
+ } else {
693
+ funderAddress = void 0;
694
+ builderCreds = void 0;
695
+ }
696
+ let tradingConfig = currentTrading || {};
697
+ logger.section("交易配置(可选)");
698
+ const tradingStatus = currentTrading && Object.keys(currentTrading).length > 0 ? "已配置" : "未配置";
699
+ const needTradingConfig = await question(
700
+ ` - 交易配置 [${tradingStatus}] (y=配置, 回车=跳过): `
701
+ );
702
+ if (needTradingConfig.trim().toLowerCase() === "y") {
703
+ tradingConfig = await configureTrading(currentTrading);
704
+ } else {
705
+ tradingConfig = currentTrading || {};
706
+ }
707
+ const config = {
708
+ telegram: {
709
+ botToken,
710
+ adminChatId,
711
+ targetChatIds: currentTelegram?.targetChatIds
712
+ },
713
+ polymarket: {
714
+ privateKey: privateKeyTrimmed || "",
715
+ funderAddress,
716
+ builderCreds
717
+ },
718
+ trading: tradingConfig
719
+ };
720
+ if (tradingConfig.sellPrice === 1 && !builderCreds) {
721
+ logger.warning("sellPrice = 1 但未配置 builderCreds,Redeem 功能将无法使用");
722
+ logger.line("", "请配置 Builder API 凭证,或将卖价设置为 < 1 使用限价卖出模式");
723
+ }
724
+ configLocal.setItem("telegram", config.telegram);
725
+ configLocal.setItem("polymarket", config.polymarket);
726
+ const cleanTradingConfig = {};
727
+ if (config.trading.amountMode !== void 0) cleanTradingConfig.amountMode = config.trading.amountMode;
728
+ if (config.trading.amountValue !== void 0) cleanTradingConfig.amountValue = config.trading.amountValue;
729
+ if (config.trading.buyPrice !== void 0) cleanTradingConfig.buyPrice = config.trading.buyPrice;
730
+ if (config.trading.sellPrice !== void 0) cleanTradingConfig.sellPrice = config.trading.sellPrice;
731
+ configLocal.setItem("trading", cleanTradingConfig);
732
+ logger.success("配置已保存!");
733
+ if (privateKeyTrimmed) {
734
+ logger.success("PolyMarket 配置已保存!");
735
+ if (Object.keys(tradingConfig).length > 0) {
736
+ logger.success("交易配置已保存!");
737
+ } else {
738
+ logger.info("ℹ️ 已跳过交易配置");
739
+ }
740
+ } else {
741
+ logger.info("ℹ️ 已跳过 PolyMarket 配置");
742
+ }
743
+ return config;
744
+ }
745
+ async function initConfig() {
746
+ return await runConfigWizard();
747
+ }
748
+ async function configureTrading(existingConfig) {
749
+ logger.section("交易配置");
750
+ console.log("提示: 配置不完整时将无法自动跟单");
751
+ const existingMode = existingConfig?.amountMode;
752
+ const defaultMode = existingMode || "fixed";
753
+ let amountMode = defaultMode;
754
+ while (true) {
755
+ const modeDisplay = existingMode ? existingMode === "fixed" ? "固定金额" : "动态比例" : "固定金额";
756
+ const modeInput = await question(
757
+ ` - 使用金模式 [${modeDisplay}] (1=固定金额, 2=动态比例, 回车=保持): `
758
+ );
759
+ const mode = modeInput.trim();
760
+ if (!mode) {
761
+ amountMode = existingMode || defaultMode;
762
+ break;
763
+ }
764
+ if (mode === "1") {
765
+ amountMode = "fixed";
766
+ break;
767
+ } else if (mode === "2") {
768
+ amountMode = "percentage";
769
+ break;
770
+ } else {
771
+ console.log("无效选择,请输入 1 或 2\n");
772
+ }
773
+ }
774
+ const existingValue = existingConfig?.amountValue;
775
+ let amountValue;
776
+ while (true) {
777
+ const unit = amountMode === "fixed" ? " USDC" : "";
778
+ const valueDisplay = existingValue !== void 0 ? `${existingValue}${unit}` : "未配置";
779
+ const valueHint = amountMode === "fixed" ? "USDC金额" : "0-1比例";
780
+ const valueInput = await question(
781
+ ` - 使用金数值 [${valueDisplay}] (${valueHint}, skip=跳过): `
782
+ );
783
+ if (!valueInput.trim()) {
784
+ if (existingValue !== void 0) {
785
+ amountValue = existingValue;
786
+ break;
787
+ } else {
788
+ amountValue = void 0;
789
+ break;
790
+ }
791
+ }
792
+ if (valueInput.trim().toLowerCase() === "skip") {
793
+ amountValue = void 0;
794
+ break;
795
+ }
796
+ const value = parseFloat(valueInput.trim());
797
+ if (isNaN(value) || value <= 0) {
798
+ console.log("请输入有效的正数,或输入 'skip' 跳过\n");
799
+ continue;
800
+ }
801
+ if (amountMode === "percentage" && value > 1) {
802
+ console.log("比例不能大于 1,请重新输入\n");
803
+ continue;
804
+ }
805
+ amountValue = value;
806
+ break;
807
+ }
808
+ const existingBuyPrice = existingConfig?.buyPrice;
809
+ const defaultBuyPrice = existingBuyPrice ?? 0.98;
810
+ let buyPrice = defaultBuyPrice;
811
+ while (true) {
812
+ const buyDisplay = existingBuyPrice !== void 0 ? existingBuyPrice : defaultBuyPrice;
813
+ const buyPriceInput = await question(
814
+ ` - 买价 [${buyDisplay}] (0.1-0.99, skip=跳过): `
815
+ );
816
+ if (!buyPriceInput.trim()) {
817
+ buyPrice = existingBuyPrice ?? defaultBuyPrice;
818
+ break;
819
+ }
820
+ if (buyPriceInput.trim().toLowerCase() === "skip") {
821
+ buyPrice = void 0;
822
+ break;
823
+ }
824
+ const price = parseFloat(buyPriceInput.trim());
825
+ if (isNaN(price) || price < 0.1 || price > 0.99) {
826
+ console.log("买价必须在 0.1 到 0.99 之间,或输入 'skip' 跳过\n");
827
+ continue;
828
+ }
829
+ buyPrice = price;
830
+ break;
831
+ }
832
+ const existingSellPrice = existingConfig?.sellPrice;
833
+ const defaultSellPrice = existingSellPrice ?? 1;
834
+ let sellPrice = defaultSellPrice;
835
+ while (true) {
836
+ const sellDisplay = (existingSellPrice ?? defaultSellPrice) === 1 ? "1 (redeem)" : String(existingSellPrice ?? defaultSellPrice);
837
+ const minSell = buyPrice ?? 0.98;
838
+ const sellPriceInput = await question(
839
+ ` - 卖价 [${sellDisplay}] (>${minSell} 且 <=1, 1=redeem, skip=跳过): `
840
+ );
841
+ if (!sellPriceInput.trim()) {
842
+ sellPrice = existingSellPrice ?? defaultSellPrice;
843
+ break;
844
+ }
845
+ if (sellPriceInput.trim().toLowerCase() === "skip") {
846
+ sellPrice = void 0;
847
+ break;
848
+ }
849
+ const price = parseFloat(sellPriceInput.trim());
850
+ if (isNaN(price) || buyPrice !== void 0 && price <= buyPrice || price > 1) {
851
+ console.log(`卖价必须大于 ${buyPrice ?? 0.98} 且小于等于 1,或输入 'skip' 跳过
852
+ `);
853
+ continue;
854
+ }
855
+ sellPrice = price;
856
+ break;
857
+ }
858
+ const tradingConfig = {
859
+ amountMode,
860
+ amountValue,
861
+ buyPrice,
862
+ sellPrice
863
+ };
864
+ logger.success("交易配置已保存!");
865
+ if (tradingConfig.sellPrice === 1) {
866
+ logger.line("ℹ️", "卖价设置为 1,将使用自动 redeem 模式");
867
+ } else if (tradingConfig.sellPrice !== void 0) {
868
+ logger.line("ℹ️", `卖价设置为 ${tradingConfig.sellPrice},将使用限价卖出模式`);
869
+ }
870
+ const hasAllConfig = tradingConfig.amountMode && tradingConfig.amountValue !== void 0 && tradingConfig.buyPrice !== void 0 && tradingConfig.sellPrice !== void 0;
871
+ if (!hasAllConfig) {
872
+ logger.warning("交易配置不完整,将无法自动跟单");
873
+ }
874
+ return tradingConfig;
875
+ }
876
+ async function ensureConfig() {
877
+ const telegram = configLocal.getItem("telegram");
878
+ const polymarket = configLocal.getItem("polymarket");
879
+ const trading = configLocal.getItem("trading");
880
+ if (!telegram && !polymarket && !trading) {
881
+ logger.info("未找到配置文件,开始初始化配置...");
882
+ return await initConfig();
883
+ }
884
+ const config = {
885
+ telegram: telegram || { botToken: "" },
886
+ polymarket: polymarket || { privateKey: "" },
887
+ trading: trading || {}
888
+ };
889
+ const validation = validateConfig(config);
890
+ if (!validation.valid) {
891
+ logger.warning("配置文件不完整,缺少以下字段:");
892
+ validation.missingFields.forEach((field) => logger.item(`- ${field}`, 1));
893
+ logger.info("开始重新配置...");
894
+ return await initConfig();
895
+ }
896
+ if (!hasPolyMarketConfig(config)) {
897
+ logger.info("未检测到 PolyMarket 配置");
898
+ const useFullWizard = await question(
899
+ "是否要运行完整配置向导?(y/n,默认 n,选择 n 将跳过配置): "
900
+ );
901
+ if (useFullWizard.trim().toLowerCase() === "y") {
902
+ return await runConfigWizard(config);
903
+ } else {
904
+ logger.info("ℹ️ 已跳过 PolyMarket 配置");
905
+ }
906
+ }
907
+ if (hasPolyMarketConfig(config) && !hasTradingConfig(config)) {
908
+ const needTradingConfig = await question(
909
+ "\n交易配置 [未完整] (y=配置, 回车=跳过): "
910
+ );
911
+ if (needTradingConfig.trim().toLowerCase() === "y") {
912
+ const tradingConfig = await configureTrading(config.trading);
913
+ config.trading = {
914
+ ...config.trading,
915
+ ...tradingConfig
916
+ };
917
+ const cleanTradingConfig = {};
918
+ if (config.trading.amountMode !== void 0) cleanTradingConfig.amountMode = config.trading.amountMode;
919
+ if (config.trading.amountValue !== void 0) cleanTradingConfig.amountValue = config.trading.amountValue;
920
+ if (config.trading.buyPrice !== void 0) cleanTradingConfig.buyPrice = config.trading.buyPrice;
921
+ if (config.trading.sellPrice !== void 0) cleanTradingConfig.sellPrice = config.trading.sellPrice;
922
+ configLocal.setItem("trading", cleanTradingConfig);
923
+ }
924
+ }
925
+ return config;
926
+ }
927
+ exports.TelegramListener = TelegramListener;
928
+ exports.canAutoTrade = canAutoTrade;
929
+ exports.configLocal = configLocal;
930
+ exports.ensureConfig = ensureConfig;
931
+ exports.logger = logger;
932
+ exports.runConfigWizard = runConfigWizard;