polycopy 0.1.0 → 0.1.3

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/dist/cli.js CHANGED
@@ -184,7 +184,7 @@ function stopDaemon() {
184
184
  attempts++;
185
185
  try {
186
186
  process.kill(pid, 0);
187
- if (attempts >= 10) {
187
+ if (attempts >= 20) {
188
188
  process.kill(pid, "SIGKILL");
189
189
  console.log("⚠️ 进程未响应,已强制终止");
190
190
  clearInterval(checkInterval);
package/dist/config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const init = require("./init-OIFlNHFx.js");
2
+ const init = require("./init-C9dmq3BS.js");
3
3
  async function main() {
4
4
  try {
5
5
  await init.runConfigWizard();
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const init = require("./init-OIFlNHFx.js");
2
+ const init = require("./init-C9dmq3BS.js");
3
3
  const grammy = require("grammy");
4
4
  const wallet = require("@ethersproject/wallet");
5
5
  const clobClient = require("@polymarket/clob-client");
@@ -32,23 +32,445 @@ function _interopNamespace(e) {
32
32
  }
33
33
  const setPromiseInterval__default = /* @__PURE__ */ _interopDefault(setPromiseInterval);
34
34
  const fs__namespace = /* @__PURE__ */ _interopNamespace(fs);
35
- class Notifier {
35
+ function parseSignalMetadata(messageText) {
36
+ const match = messageText.match(/🤖\s*({.*})/s);
37
+ if (!match) {
38
+ return null;
39
+ }
40
+ try {
41
+ const jsonStr = match[1];
42
+ const metadata = JSON.parse(jsonStr);
43
+ if (!metadata.eventId || !metadata.assetId || !metadata.outcome || !metadata.endTime) {
44
+ init.logger.error("解析失败: 缺少必需字段", {
45
+ hasEventId: !!metadata.eventId,
46
+ hasAssetId: !!metadata.assetId,
47
+ hasOutcome: !!metadata.outcome,
48
+ hasEndTime: !!metadata.endTime
49
+ });
50
+ return null;
51
+ }
52
+ if (metadata.outcome !== "Up" && metadata.outcome !== "Down") {
53
+ init.logger.error("解析失败: outcome 值无效", {
54
+ outcome: metadata.outcome,
55
+ expected: "Up 或 Down"
56
+ });
57
+ return null;
58
+ }
59
+ if (typeof metadata.endTime !== "number" || metadata.endTime <= 0) {
60
+ init.logger.error("解析失败: endTime 值无效", {
61
+ endTime: metadata.endTime,
62
+ expected: "毫秒级时间戳"
63
+ });
64
+ return null;
65
+ }
66
+ return metadata;
67
+ } catch (error) {
68
+ init.logger.error("解析信号元数据失败:", error);
69
+ if (error instanceof Error) {
70
+ init.logger.line("", `错误详情: ${error.message}`);
71
+ }
72
+ return null;
73
+ }
74
+ }
75
+ class TelegramListener {
76
+ bot;
77
+ onSignalCallback;
78
+ onChatIdsChangedCallback;
79
+ targetChatIds;
80
+ // 运行时监听的频道 ID 集合(自动管理)
81
+ constructor(bot, targetChatIds = []) {
82
+ this.bot = bot;
83
+ this.targetChatIds = new Set(targetChatIds);
84
+ this.setupHandlers();
85
+ }
86
+ /**
87
+ * 设置消息处理器
88
+ */
89
+ setupHandlers() {
90
+ this.bot.on("channel_post", async (ctx) => {
91
+ await this.handleChannelPost(ctx);
92
+ });
93
+ this.bot.on("edited_channel_post", async (ctx) => {
94
+ await this.handleChannelPost(ctx);
95
+ });
96
+ this.bot.on("my_chat_member", async (ctx) => {
97
+ await this.handleMyChatMemberUpdate(ctx);
98
+ });
99
+ this.bot.catch((err) => {
100
+ init.logger.error("Telegram Bot 错误:", err);
101
+ if (err.error) {
102
+ init.logger.line("", `错误详情: ${err.error}`);
103
+ }
104
+ if (err.ctx) {
105
+ init.logger.line("", `上下文信息:`, {
106
+ chatId: err.ctx.chat?.id,
107
+ messageId: err.ctx.message?.message_id
108
+ });
109
+ }
110
+ });
111
+ }
112
+ /**
113
+ * 处理 Bot 自己的成员状态变更
114
+ */
115
+ async handleMyChatMemberUpdate(ctx) {
116
+ const update = ctx.myChatMember;
117
+ if (!update) {
118
+ return;
119
+ }
120
+ const chatId = update.chat.id;
121
+ const chatTitle = update.chat.title || "未知频道";
122
+ const oldStatus = update.old_chat_member.status;
123
+ const newStatus = update.new_chat_member.status;
124
+ const chatType = update.chat.type;
125
+ if (chatType !== "channel") {
126
+ return;
127
+ }
128
+ const isAdmin = newStatus === "administrator" || newStatus === "creator";
129
+ const wasAdmin = oldStatus === "administrator" || oldStatus === "creator";
130
+ const isRemoved = newStatus === "left" || newStatus === "kicked";
131
+ const wasRemoved = oldStatus === "left" || oldStatus === "kicked";
132
+ if (oldStatus !== newStatus) {
133
+ let action = "";
134
+ let canListen = false;
135
+ let reason = "";
136
+ if (isRemoved) {
137
+ action = "❌ 频道主已移除 Bot";
138
+ canListen = false;
139
+ reason = "Bot 已被移除";
140
+ } else if (wasRemoved && isAdmin) {
141
+ action = "✅ 频道主已添加 Bot 为管理员";
142
+ canListen = true;
143
+ } else if (isAdmin && !wasAdmin) {
144
+ action = "✅ 频道主已授予 Bot 管理员权限";
145
+ canListen = true;
146
+ } else if (wasAdmin && !isAdmin) {
147
+ action = "⚠️ 频道主已移除 Bot 的管理员权限";
148
+ canListen = false;
149
+ reason = "Bot 不再是管理员";
150
+ } else if (wasRemoved && !isAdmin) {
151
+ action = "⚠️ 频道主已添加 Bot,但未授予管理员权限";
152
+ canListen = false;
153
+ reason = "需要管理员权限才能监听";
154
+ } else if (oldStatus === "restricted" && isAdmin) {
155
+ action = "✅ 频道主已授予 Bot 管理员权限";
156
+ canListen = true;
157
+ } else if (wasAdmin && newStatus === "restricted") {
158
+ action = "⚠️ 频道主已限制 Bot 的权限";
159
+ canListen = false;
160
+ reason = "Bot 权限受限,无法监听";
161
+ } else if (oldStatus === "restricted" && !isAdmin) {
162
+ action = `状态变更: ${oldStatus} → ${newStatus}`;
163
+ canListen = false;
164
+ reason = "需要管理员权限才能监听";
165
+ } else {
166
+ action = `状态变更: ${oldStatus} → ${newStatus}`;
167
+ canListen = isAdmin;
168
+ if (!canListen) {
169
+ reason = "需要管理员权限才能监听";
170
+ }
171
+ }
172
+ init.logger.info(`📢 频道状态更新: ${chatTitle}`);
173
+ init.logger.line("", action);
174
+ init.logger.line(
175
+ "",
176
+ `当前状态: ${canListen ? "✅ 可监听预测消息" : "❌ 无法监听预测消息"}`
177
+ );
178
+ if (reason) {
179
+ init.logger.line("", `原因: ${reason}`);
180
+ }
181
+ }
182
+ if (isAdmin && !this.targetChatIds.has(chatId)) {
183
+ this.targetChatIds.add(chatId);
184
+ await this.saveChatIds();
185
+ init.logger.line("📡", "已自动开始监听此频道");
186
+ }
187
+ if ((isRemoved || !isAdmin) && this.targetChatIds.has(chatId)) {
188
+ this.targetChatIds.delete(chatId);
189
+ await this.saveChatIds();
190
+ if (isRemoved) {
191
+ init.logger.line("⏹️", "已停止监听此频道(Bot 已被移除)");
192
+ } else {
193
+ init.logger.line("⏹️", "已停止监听此频道(Bot 不再是管理员)");
194
+ }
195
+ }
196
+ }
197
+ /**
198
+ * 保存频道 ID 列表到配置(用于持久化)
199
+ */
200
+ async saveChatIds() {
201
+ if (!this.onChatIdsChangedCallback) {
202
+ return;
203
+ }
204
+ try {
205
+ const chatIds = Array.from(this.targetChatIds);
206
+ await this.onChatIdsChangedCallback(chatIds);
207
+ } catch (error) {
208
+ init.logger.error("保存频道 ID 列表时出错:", error);
209
+ }
210
+ }
211
+ /**
212
+ * 处理频道消息(channel_post 和 edited_channel_post)
213
+ */
214
+ async handleChannelPost(ctx) {
215
+ const message = ctx.channelPost || ctx.editedChannelPost;
216
+ if (!message) {
217
+ return;
218
+ }
219
+ await this.processMessage(message, ctx);
220
+ }
221
+ /**
222
+ * 处理频道消息(统一的消息处理逻辑)
223
+ */
224
+ async processMessage(message, ctx) {
225
+ if (!message) {
226
+ return;
227
+ }
228
+ const chatId = message.chat.id;
229
+ const chatTitle = message.chat.title || "未知频道";
230
+ const isChannel = chatId < 0 && chatId.toString().startsWith("-100");
231
+ const messageText = message.text || message.caption || "";
232
+ if (!messageText) {
233
+ return;
234
+ }
235
+ if (isChannel && !this.targetChatIds.has(chatId)) {
236
+ this.targetChatIds.add(chatId);
237
+ init.logger.info(`➕ 自动添加频道到监听列表: ${chatTitle} (ID: ${chatId})`);
238
+ await this.saveChatIds();
239
+ init.logger.line("💾", "已保存到配置文件");
240
+ }
241
+ if (!isChannel) {
242
+ return;
243
+ }
244
+ if (!this.targetChatIds.has(chatId)) {
245
+ return;
246
+ }
247
+ const metadata = parseSignalMetadata(messageText);
248
+ if (!metadata) {
249
+ if (messageText.includes("🤖")) {
250
+ const emojiIndex = messageText.indexOf("🤖");
251
+ const jsonStart = emojiIndex + 1;
252
+ const jsonEnd = Math.min(jsonStart + 200, messageText.length);
253
+ init.logger.warning(`解析预测消息失败:`);
254
+ init.logger.line("", `频道 ID: ${chatId}`);
255
+ init.logger.line(
256
+ "",
257
+ `消息片段: ${messageText.substring(emojiIndex, jsonEnd)}${messageText.length > jsonEnd ? "..." : ""}`
258
+ );
259
+ init.logger.line("", "提示: 请检查消息格式是否正确");
260
+ }
261
+ return;
262
+ }
263
+ const parsedSignal = {
264
+ metadata,
265
+ rawMessage: messageText,
266
+ messageId: message.message_id,
267
+ chatId,
268
+ timestamp: message.date * 1e3
269
+ };
270
+ if (this.onSignalCallback) {
271
+ try {
272
+ await this.onSignalCallback(parsedSignal);
273
+ } catch (error) {
274
+ init.logger.error("处理信号时出错:", error);
275
+ }
276
+ }
277
+ }
278
+ /**
279
+ * 设置信号回调函数
280
+ */
281
+ onSignal(callback) {
282
+ this.onSignalCallback = callback;
283
+ }
284
+ /**
285
+ * 设置频道 ID 列表变更回调函数(当频道自动添加/移除时调用,用于持久化)
286
+ */
287
+ onChatIdsChanged(callback) {
288
+ this.onChatIdsChangedCallback = callback;
289
+ }
290
+ /**
291
+ * 设置监听的频道 ID 列表(用于启动时加载已保存的配置)
292
+ */
293
+ setTargetChatIds(chatIds) {
294
+ this.targetChatIds = new Set(chatIds);
295
+ }
296
+ /**
297
+ * 启动监听
298
+ * @param onReady 可选回调,在频道检查完成后、开始监听之前调用
299
+ */
300
+ async start(onReady) {
301
+ try {
302
+ init.logger.info("正在启动 Bot...");
303
+ const botInfo = await this.bot.api.getMe();
304
+ const botId = botInfo.id;
305
+ init.logger.info(`Bot 信息: @${botInfo.username} (ID: ${botId})`);
306
+ await this.checkAndDisplayChannels(botId);
307
+ if (onReady) {
308
+ await onReady();
309
+ }
310
+ init.logger.info("开始监听预测消息...");
311
+ await this.bot.start({
312
+ drop_pending_updates: true,
313
+ allowed_updates: [
314
+ "channel_post",
315
+ "edited_channel_post",
316
+ "my_chat_member"
317
+ ]
318
+ });
319
+ } catch (error) {
320
+ init.logger.error("启动 Bot 失败:", error);
321
+ throw error;
322
+ }
323
+ }
324
+ /**
325
+ * 检查并显示 Bot 所在的频道
326
+ * 验证配置文件中保存的频道,检查 Bot 是否仍在频道中以及是否是管理员
327
+ */
328
+ async checkAndDisplayChannels(botId) {
329
+ const chatIds = Array.from(this.targetChatIds);
330
+ if (chatIds.length === 0) {
331
+ init.logger.info(`📋 当前没有保存的频道配置`);
332
+ init.logger.line(
333
+ "",
334
+ "提示: 当 Bot 被添加为频道管理员时,会自动添加到监听列表"
335
+ );
336
+ return;
337
+ }
338
+ init.logger.info(`📋 检查 Bot 所在的频道:`);
339
+ const validChannels = [];
340
+ const invalidChannels = [];
341
+ for (const chatId of chatIds) {
342
+ try {
343
+ const chat = await this.bot.api.getChat(chatId);
344
+ const chatTitle = chat.title || "未知频道";
345
+ try {
346
+ const member = await this.bot.api.getChatMember(chatId, botId);
347
+ const isAdmin = member.status === "administrator" || member.status === "creator";
348
+ validChannels.push({
349
+ id: chatId,
350
+ title: chatTitle,
351
+ isAdmin
352
+ });
353
+ } catch (error) {
354
+ invalidChannels.push({
355
+ id: chatId,
356
+ reason: "无法获取成员信息(可能已被移除)"
357
+ });
358
+ }
359
+ } catch (error) {
360
+ invalidChannels.push({
361
+ id: chatId,
362
+ reason: "无法获取频道信息(可能已被移除)"
363
+ });
364
+ }
365
+ }
366
+ if (validChannels.length > 0) {
367
+ const adminChannels = validChannels.filter((c) => c.isAdmin);
368
+ const nonAdminChannels = validChannels.filter((c) => !c.isAdmin);
369
+ if (adminChannels.length > 0) {
370
+ init.logger.info(`📡 可监听频道 (${adminChannels.length} 个):`);
371
+ adminChannels.forEach((channel, index) => {
372
+ init.logger.item(
373
+ `${index + 1}. ${channel.title} (ID: ${channel.id}) 👑 管理员`,
374
+ 1
375
+ );
376
+ });
377
+ }
378
+ if (nonAdminChannels.length > 0) {
379
+ init.logger.warning(`无法监听频道 (${nonAdminChannels.length} 个):`);
380
+ nonAdminChannels.forEach((channel, index) => {
381
+ init.logger.item(`${index + 1}. ${channel.title}`, 1);
382
+ init.logger.item(`ID: ${channel.id}`, 2);
383
+ init.logger.item(`状态: 🚫 非管理员(无法接收预测消息)`, 2);
384
+ init.logger.item(`原因: 需要管理员权限才能监听`, 2);
385
+ });
386
+ nonAdminChannels.forEach((channel) => {
387
+ this.targetChatIds.delete(channel.id);
388
+ });
389
+ if (nonAdminChannels.length > 0) {
390
+ await this.saveChatIds();
391
+ init.logger.line("💾", "已清理无法监听的频道并保存配置");
392
+ }
393
+ }
394
+ }
395
+ if (invalidChannels.length > 0) {
396
+ init.logger.error(`无效频道 (${invalidChannels.length} 个):`);
397
+ invalidChannels.forEach((channel, index) => {
398
+ init.logger.item(`${index + 1}. ID: ${channel.id}`, 1);
399
+ init.logger.item(`原因: ${channel.reason}`, 2);
400
+ });
401
+ invalidChannels.forEach((channel) => {
402
+ this.targetChatIds.delete(channel.id);
403
+ });
404
+ if (invalidChannels.length > 0) {
405
+ await this.saveChatIds();
406
+ init.logger.line("💾", "已清理无效频道并保存配置");
407
+ }
408
+ }
409
+ }
410
+ /**
411
+ * 停止监听
412
+ */
413
+ async stop() {
414
+ await this.bot.stop();
415
+ }
416
+ /**
417
+ * 获取 bot 信息(用于验证 token 是否有效)
418
+ */
419
+ async getBotInfo() {
420
+ try {
421
+ return this.bot.api.getMe();
422
+ } catch (error) {
423
+ throw new Error(`获取 Bot 信息失败: ${error}`);
424
+ }
425
+ }
426
+ }
427
+ class TelegramService {
36
428
  bot = null;
429
+ listener = null;
37
430
  adminChatId = null;
38
- initialized = false;
431
+ notifyEnabled = false;
39
432
  /**
40
- * 初始化通知器
433
+ * 初始化 Telegram 服务
41
434
  */
42
- init(botToken, adminChatId) {
435
+ init(botToken, adminChatId, targetChatIds = []) {
436
+ if (this.listener) {
437
+ return;
438
+ }
439
+ this.bot = new grammy.Bot(botToken);
440
+ this.listener = new TelegramListener(this.bot, targetChatIds);
43
441
  if (!adminChatId) {
44
442
  init.logger.warning("未配置管理员 Chat ID,通知功能已禁用");
443
+ this.notifyEnabled = false;
45
444
  return;
46
445
  }
47
- this.bot = new grammy.Bot(botToken);
48
446
  this.adminChatId = adminChatId;
49
- this.initialized = true;
447
+ this.notifyEnabled = true;
50
448
  init.logger.info("管理员通知已启用");
51
449
  }
450
+ /**
451
+ * 设置频道 ID 列表变更回调
452
+ */
453
+ onChatIdsChanged(callback) {
454
+ this.ensureListener().onChatIdsChanged(callback);
455
+ }
456
+ /**
457
+ * 设置预测信号回调
458
+ */
459
+ onSignal(callback) {
460
+ this.ensureListener().onSignal(callback);
461
+ }
462
+ /**
463
+ * 启动监听
464
+ */
465
+ async start(onReady) {
466
+ await this.ensureListener().start(onReady);
467
+ }
468
+ /**
469
+ * 停止监听
470
+ */
471
+ async stop() {
472
+ await this.ensureListener().stop();
473
+ }
52
474
  /**
53
475
  * 发送错误通知
54
476
  */
@@ -67,11 +489,17 @@ class Notifier {
67
489
  async success(message, details) {
68
490
  await this.send(`🟢 ${message}`, details);
69
491
  }
492
+ ensureListener() {
493
+ if (!this.listener) {
494
+ throw new Error("TelegramService 未初始化");
495
+ }
496
+ return this.listener;
497
+ }
70
498
  /**
71
499
  * 发送消息给管理员
72
500
  */
73
501
  async send(message, details) {
74
- if (!this.initialized || !this.bot || !this.adminChatId) {
502
+ if (!this.notifyEnabled || !this.bot || !this.adminChatId) {
75
503
  return;
76
504
  }
77
505
  const fullMessage = details ? `${message}
@@ -87,7 +515,7 @@ ${details}` : message;
87
515
  }
88
516
  }
89
517
  }
90
- const notifier = new Notifier();
518
+ const telegramService = new TelegramService();
91
519
  const CLOB_HOST = "https://clob.polymarket.com";
92
520
  class ClobClientWrapper {
93
521
  client = null;
@@ -204,13 +632,13 @@ class ClobClientWrapper {
204
632
  clobClient.OrderType.GTC
205
633
  );
206
634
  if (response.success) {
207
- init.logger.success(`买单创建成功: ${response.orderID}`);
635
+ init.logger.success(`买单已提交: ${response.orderID}`);
208
636
  return {
209
637
  success: true,
210
638
  orderId: response.orderID
211
639
  };
212
640
  } else {
213
- init.logger.error(`买单创建失败: ${response.errorMsg}`);
641
+ init.logger.error(`买单提交失败: ${response.errorMsg}`);
214
642
  return {
215
643
  success: false,
216
644
  orderId: "",
@@ -219,7 +647,7 @@ class ClobClientWrapper {
219
647
  }
220
648
  } catch (error) {
221
649
  const errorMsg = error instanceof Error ? error.message : String(error);
222
- init.logger.error(`买单创建异常: ${errorMsg}`);
650
+ init.logger.error(`买单提交失败: ${errorMsg}`);
223
651
  return {
224
652
  success: false,
225
653
  orderId: "",
@@ -410,7 +838,7 @@ class AssetFilter {
410
838
  this.traded.set(endTime, /* @__PURE__ */ new Set());
411
839
  }
412
840
  this.traded.get(endTime).add(assetId);
413
- init.logger.info(`已标记 assetId 交易完成: ${assetId.slice(0, 10)}...`);
841
+ init.logger.info(`已标记 assetId: ${assetId.slice(0, 10)}...`);
414
842
  }
415
843
  /**
416
844
  * 清理过期记录
@@ -530,10 +958,20 @@ class Redeemer {
530
958
  records = /* @__PURE__ */ new Map();
531
959
  intervalId;
532
960
  delayTimeoutId;
533
- running = false;
961
+ // stopped: 未启动; running: 正常轮询; paused: 限流等待恢复
962
+ state = "stopped";
963
+ scheduler;
534
964
  redeemFn = null;
535
965
  funderAddress = "";
536
966
  onRedeemSuccess = null;
967
+ constructor(scheduler = {
968
+ setInterval: setPromiseInterval__default.default,
969
+ clearInterval: setPromiseInterval.clearPromiseInterval,
970
+ setTimeout,
971
+ clearTimeout
972
+ }) {
973
+ this.scheduler = scheduler;
974
+ }
537
975
  /**
538
976
  * 启动 Redeemer
539
977
  * @param redeemFn 执行 Redeem 的函数(返回交易哈希)
@@ -541,26 +979,24 @@ class Redeemer {
541
979
  * @param onRedeemSuccess Redeem 成功后的回调(用于更新余额缓存)
542
980
  */
543
981
  start(redeemFn, funderAddress, onRedeemSuccess) {
544
- if (this.running) {
982
+ if (this.state !== "stopped") {
545
983
  return;
546
984
  }
547
985
  this.redeemFn = redeemFn;
548
986
  this.funderAddress = funderAddress;
549
987
  this.onRedeemSuccess = onRedeemSuccess || null;
550
- this.running = true;
551
- init.logger.info("自动 redeem 已启动");
552
- this.intervalId = setPromiseInterval__default.default(async () => {
553
- await this.runOnce();
554
- }, BASE_INTERVAL);
988
+ this.state = "running";
989
+ init.logger.success("自动 redeem 已启动");
990
+ this.startInterval();
555
991
  }
556
992
  /**
557
- * 启动定时任务
993
+ * 启动定时任务(仅在 running 状态生效)
558
994
  */
559
995
  startInterval() {
560
- if (!this.running || this.intervalId !== void 0) {
996
+ if (this.state !== "running" || this.intervalId !== void 0) {
561
997
  return;
562
998
  }
563
- this.intervalId = setPromiseInterval__default.default(async () => {
999
+ this.intervalId = this.scheduler.setInterval(async () => {
564
1000
  await this.runOnce();
565
1001
  }, BASE_INTERVAL);
566
1002
  }
@@ -569,15 +1005,19 @@ class Redeemer {
569
1005
  */
570
1006
  pauseAndRestart(delayMs) {
571
1007
  if (this.intervalId !== void 0) {
572
- setPromiseInterval.clearPromiseInterval(this.intervalId);
1008
+ this.scheduler.clearInterval(this.intervalId);
573
1009
  this.intervalId = void 0;
574
1010
  }
575
- this.delayTimeoutId = setTimeout(() => {
1011
+ if (this.delayTimeoutId) {
1012
+ this.scheduler.clearTimeout(this.delayTimeoutId);
1013
+ this.delayTimeoutId = void 0;
1014
+ }
1015
+ this.state = "paused";
1016
+ this.delayTimeoutId = this.scheduler.setTimeout(() => {
576
1017
  this.delayTimeoutId = void 0;
577
- if (this.running) {
578
- this.runOnce().finally(() => {
579
- this.startInterval();
580
- });
1018
+ if (this.state === "paused") {
1019
+ this.state = "running";
1020
+ this.startInterval();
581
1021
  }
582
1022
  }, delayMs);
583
1023
  }
@@ -586,14 +1026,14 @@ class Redeemer {
586
1026
  */
587
1027
  stop() {
588
1028
  if (this.intervalId !== void 0) {
589
- setPromiseInterval.clearPromiseInterval(this.intervalId);
1029
+ this.scheduler.clearInterval(this.intervalId);
590
1030
  this.intervalId = void 0;
591
1031
  }
592
1032
  if (this.delayTimeoutId) {
593
- clearTimeout(this.delayTimeoutId);
1033
+ this.scheduler.clearTimeout(this.delayTimeoutId);
594
1034
  this.delayTimeoutId = void 0;
595
1035
  }
596
- this.running = false;
1036
+ this.state = "stopped";
597
1037
  init.logger.info("Redeemer 已停止");
598
1038
  }
599
1039
  /**
@@ -621,7 +1061,9 @@ class Redeemer {
621
1061
  positions.push({
622
1062
  conditionId: item.conditionId,
623
1063
  assetId: item.asset,
624
- currentValue
1064
+ currentValue,
1065
+ slug: item.slug,
1066
+ outcome: item.outcome
625
1067
  });
626
1068
  } else {
627
1069
  break;
@@ -633,7 +1075,7 @@ class Redeemer {
633
1075
  * 执行一轮 Redeem
634
1076
  */
635
1077
  async runOnce() {
636
- if (!this.running || !this.redeemFn) {
1078
+ if (this.state !== "running" || !this.redeemFn) {
637
1079
  return;
638
1080
  }
639
1081
  let positions;
@@ -644,14 +1086,26 @@ class Redeemer {
644
1086
  if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
645
1087
  const resetSeconds = this.parseResetSeconds(errorMsg);
646
1088
  init.logger.warning(`获取仓位限流,${resetSeconds} 秒后重试`);
647
- notifier.warning("API 限流", `获取仓位限流,${resetSeconds} 秒后重试`);
1089
+ telegramService.warning(
1090
+ "API 限流",
1091
+ `获取仓位限流,${resetSeconds} 秒后重试`
1092
+ );
648
1093
  this.pauseAndRestart((resetSeconds + 60) * 1e3);
649
1094
  return;
650
1095
  }
651
1096
  init.logger.error(`获取仓位失败: ${errorMsg}`);
652
1097
  return;
653
1098
  }
654
- init.logger.info(`自动 redeem: ${positions.length} 个可 Redeem 仓位`);
1099
+ const pendingRedeemablePositions = positions.filter((position) => {
1100
+ const record = this.records.get(position.conditionId);
1101
+ return !record?.success;
1102
+ });
1103
+ if (pendingRedeemablePositions.length > 0) {
1104
+ this.logPositions(
1105
+ `自动 redeem: 发现 ${pendingRedeemablePositions.length} 个可 Redeem 仓位`,
1106
+ pendingRedeemablePositions.map((position) => ({ position }))
1107
+ );
1108
+ }
655
1109
  for (const position of positions) {
656
1110
  let record = this.records.get(position.conditionId);
657
1111
  if (!record) {
@@ -670,7 +1124,7 @@ class Redeemer {
670
1124
  init.logger.error(
671
1125
  `Redeem 失败: 执行成功但仍存在 ${record.matchedCount} 次 (conditionId: ${position.conditionId.slice(0, 10)}...)`
672
1126
  );
673
- notifier.error(
1127
+ telegramService.error(
674
1128
  "Redeem 失败",
675
1129
  `执行成功但仓位仍存在 ${record.matchedCount} 次
676
1130
  conditionId: ${position.conditionId}
@@ -679,24 +1133,24 @@ conditionId: ${position.conditionId}
679
1133
  }
680
1134
  } else if (record.failedCount < MAX_MATCH_TIMES) {
681
1135
  try {
682
- const txHash = await this.redeemFn(position.conditionId);
1136
+ const txHash = await this.redeemFn?.(position.conditionId);
683
1137
  record.success = true;
684
- init.logger.info(`Redeem 交易已提交: ${txHash}`);
1138
+ init.logger.info(`Redeem 已执行: ${txHash}`);
685
1139
  } catch (error) {
686
- record.failedCount++;
687
1140
  const errorMsg = error instanceof Error ? error.message : String(error);
688
1141
  if (errorMsg.includes("429") || errorMsg.includes("Too Many Requests")) {
689
1142
  const resetSeconds = this.parseResetSeconds(errorMsg);
690
1143
  init.logger.warning(`Redeem 限流,${resetSeconds} 秒后重试`);
691
- notifier.warning("Redeem 限流", `${resetSeconds} 秒后重试`);
1144
+ telegramService.warning("Redeem 限流", `${resetSeconds} 秒后重试`);
692
1145
  this.pauseAndRestart((resetSeconds + 60) * 1e3);
693
1146
  return;
694
1147
  }
1148
+ record.failedCount++;
695
1149
  init.logger.error(
696
1150
  `Redeem 异常 (第 ${record.failedCount} 次): ${errorMsg}`
697
1151
  );
698
1152
  if (record.failedCount >= MAX_MATCH_TIMES) {
699
- notifier.error(
1153
+ telegramService.error(
700
1154
  "Redeem 异常",
701
1155
  `重试 ${record.failedCount} 次仍失败
702
1156
  conditionId: ${position.conditionId}
@@ -706,24 +1160,49 @@ conditionId: ${position.conditionId}
706
1160
  }
707
1161
  }
708
1162
  }
709
- const successList = [];
1163
+ const successItems = [];
710
1164
  for (const [conditionId, record] of this.records.entries()) {
711
1165
  const stillExists = positions.some((p) => p.conditionId === conditionId);
712
1166
  if (!stillExists) {
713
- successList.push(
714
- `${record.position.assetId.slice(0, 10)}... (${record.position.currentValue} USDC, matched: ${record.matchedCount})`
715
- );
1167
+ successItems.push({
1168
+ position: record.position,
1169
+ matchedCount: record.matchedCount
1170
+ });
716
1171
  this.records.delete(conditionId);
717
1172
  }
718
1173
  }
719
- if (successList.length > 0) {
720
- init.logger.success(`Redeem 成功: ${successList.length} 个`);
721
- for (const item of successList) {
722
- init.logger.line("", `- ${item}`);
723
- }
1174
+ if (successItems.length > 0) {
1175
+ const successTitle = `自动 redeem: 成功 ${successItems.length} 个`;
1176
+ const successLines = this.logPositions(successTitle, successItems);
1177
+ telegramService.success(successTitle, successLines.join("\n"));
724
1178
  this.onRedeemSuccess?.();
725
1179
  }
726
- init.logger.info(`redeem 记录: ${this.records.size} 个待确认`);
1180
+ init.logger.info(`redeem 状态: ${this.records.size} 个待确认`);
1181
+ }
1182
+ /**
1183
+ * 统一格式化仓位信息
1184
+ */
1185
+ formatPositionLine(position, matchedCount) {
1186
+ const label = position.slug || `${position.assetId.slice(0, 10)}...`;
1187
+ const outcomeLabel = position.outcome ? ` ${position.outcome}` : "";
1188
+ const matchedLabel = matchedCount !== void 0 ? ` (matched: ${matchedCount})` : "";
1189
+ return `${label}${outcomeLabel}: ${position.currentValue} USDC${matchedLabel}`;
1190
+ }
1191
+ /**
1192
+ * 统一输出 Redeem 仓位列表
1193
+ */
1194
+ logPositions(title, items) {
1195
+ init.logger.info(title);
1196
+ const lines = [];
1197
+ for (const item of items) {
1198
+ const line = this.formatPositionLine(
1199
+ item.position,
1200
+ item.matchedCount
1201
+ );
1202
+ lines.push(line);
1203
+ init.logger.line("", `- ${line}`);
1204
+ }
1205
+ return lines;
727
1206
  }
728
1207
  /**
729
1208
  * 解析 429 错误中的 resets in 时间
@@ -833,7 +1312,7 @@ class Trader {
833
1312
  if (amountMode === "fixed" && balance < amountValue) {
834
1313
  const msg = `余额不足: ${balance} USDC < ${amountValue} USDC`;
835
1314
  init.logger.warning(msg);
836
- notifier.warning(
1315
+ telegramService.warning(
837
1316
  "余额不足",
838
1317
  `当前余额: ${balance} USDC
839
1318
  需要金额: ${amountValue} USDC`
@@ -842,7 +1321,7 @@ class Trader {
842
1321
  }
843
1322
  if (amountMode === "percentage" && balance <= 0) {
844
1323
  init.logger.warning(`余额为零: ${balance} USDC`);
845
- notifier.warning("余额为零", `当前余额: ${balance} USDC`);
1324
+ telegramService.warning("余额为零", `当前余额: ${balance} USDC`);
846
1325
  return;
847
1326
  }
848
1327
  const orderAmount = amountMode === "fixed" ? amountValue : balance * amountValue;
@@ -853,7 +1332,7 @@ class Trader {
853
1332
  const buyResult = await this.client.createBuyOrder(assetId, buyPrice, size);
854
1333
  if (!buyResult.success) {
855
1334
  init.logger.error(`买单失败: ${buyResult.errorMsg}`);
856
- notifier.error(
1335
+ telegramService.error(
857
1336
  "买单失败",
858
1337
  `事件: ${metadata.eventId}
859
1338
  错误: ${buyResult.errorMsg}`
@@ -862,8 +1341,7 @@ class Trader {
862
1341
  }
863
1342
  this.assetFilter.markTraded(assetId, endTime);
864
1343
  if (sellPrice === 1) {
865
- init.logger.success(`买单已提交: ${buyResult.orderId}`);
866
- init.logger.line("", "sellPrice = 1,成交后将自动 redeem");
1344
+ init.logger.info("sellPrice = 1,成交后将自动 redeem");
867
1345
  return;
868
1346
  }
869
1347
  this.watchAndHandle(
@@ -894,7 +1372,7 @@ class Trader {
894
1372
  } catch (error) {
895
1373
  const errorMsg = error instanceof Error ? error.message : String(error);
896
1374
  init.logger.error(`订单监控异常: ${errorMsg}`);
897
- notifier.error(
1375
+ telegramService.error(
898
1376
  "订单监控异常",
899
1377
  `事件: ${eventId}
900
1378
  订单: ${orderId}
@@ -922,11 +1400,11 @@ class Trader {
922
1400
  break;
923
1401
  case "FAILED":
924
1402
  init.logger.error("订单执行失败");
925
- notifier.error("订单执行失败", `订单 ID: ${orderId}`);
1403
+ telegramService.error("订单执行失败", `订单 ID: ${orderId}`);
926
1404
  break;
927
1405
  case "TIMEOUT":
928
1406
  init.logger.warning("订单超时,尝试取消");
929
- notifier.warning("订单超时", `订单 ID: ${orderId}
1407
+ telegramService.warning("订单超时", `订单 ID: ${orderId}
930
1408
  已尝试取消`);
931
1409
  await this.client.cancelOrder(orderId);
932
1410
  break;
@@ -969,7 +1447,7 @@ class Trader {
969
1447
  if (!this.relayClient.isInitialized()) {
970
1448
  const msg = "仓位同步超时且 Redeem 不可用";
971
1449
  init.logger.warning(msg);
972
- notifier.warning(
1450
+ telegramService.warning(
973
1451
  msg,
974
1452
  `Asset: ${assetId.substring(0, 20)}...
975
1453
  预期数量: ${expectedSize.toFixed(4)}
@@ -990,10 +1468,10 @@ class Trader {
990
1468
  init.logger.info(`创建卖单: ${size.toFixed(4)} 份 @ ${price}`);
991
1469
  const sellResult = await this.client.createSellOrder(assetId, price, size);
992
1470
  if (sellResult.success) {
993
- init.logger.success(`卖单已创建: ${sellResult.orderId}`);
1471
+ init.logger.success(`卖单已提交: ${sellResult.orderId}`);
994
1472
  } else {
995
- init.logger.error(`卖单创建失败: ${sellResult.errorMsg}`);
996
- notifier.error("卖单创建失败", `错误: ${sellResult.errorMsg}`);
1473
+ init.logger.error(`卖单提交失败: ${sellResult.errorMsg}`);
1474
+ telegramService.error("卖单提交失败", `错误: ${sellResult.errorMsg}`);
997
1475
  }
998
1476
  }
999
1477
  /**
@@ -1045,7 +1523,11 @@ async function main() {
1045
1523
  init.logger.init();
1046
1524
  init.logger.section("PolyMarket 跟单机器人");
1047
1525
  const config = await init.ensureConfig();
1048
- notifier.init(config.telegram.botToken, config.telegram.adminChatId);
1526
+ telegramService.init(
1527
+ config.telegram.botToken,
1528
+ config.telegram.adminChatId,
1529
+ config.telegram.targetChatIds || []
1530
+ );
1049
1531
  const autoTradeEnabled = init.canAutoTrade(config);
1050
1532
  if (!autoTradeEnabled) {
1051
1533
  init.logger.warning("自动跟单功能未启用");
@@ -1075,11 +1557,7 @@ async function main() {
1075
1557
  init.logger.line("", "当前不会跟单任何预测消息");
1076
1558
  }
1077
1559
  }
1078
- const listener = new init.TelegramListener(
1079
- config.telegram.botToken,
1080
- config.telegram.targetChatIds || []
1081
- );
1082
- listener.onChatIdsChanged((chatIds) => {
1560
+ telegramService.onChatIdsChanged((chatIds) => {
1083
1561
  const telegram = init.configLocal.getItem("telegram");
1084
1562
  if (!telegram) {
1085
1563
  init.logger.error("警告: 无法保存频道 ID,telegram 配置不存在");
@@ -1089,7 +1567,7 @@ async function main() {
1089
1567
  init.configLocal.setItem("telegram", telegram);
1090
1568
  init.logger.line("💾", `已保存频道 ID 列表: ${chatIds.length} 个频道`);
1091
1569
  });
1092
- listener.onSignal(async (signal) => {
1570
+ telegramService.onSignal(async (signal) => {
1093
1571
  init.logger.info(`📨 收到预测信号:`);
1094
1572
  init.logger.line("", `事件: ${signal.metadata.eventId}`);
1095
1573
  init.logger.line(
@@ -1114,29 +1592,24 @@ async function main() {
1114
1592
  enterDaemonMode();
1115
1593
  return;
1116
1594
  }
1117
- const runMode = process.env.POLYCOPY_DAEMON_CHILD === "1" ? "后台模式" : "前台模式";
1118
- notifier.success("程序已启动", `模式: ${runMode}
1119
- PID: ${process.pid}`);
1120
1595
  let isShuttingDown = false;
1121
- const shutdown = () => {
1596
+ const shutdown = async () => {
1122
1597
  if (isShuttingDown) return;
1123
1598
  isShuttingDown = true;
1124
1599
  process.removeAllListeners("SIGINT");
1125
1600
  process.removeAllListeners("SIGTERM");
1126
1601
  init.logger.blank();
1127
1602
  init.logger.info("正在退出...");
1128
- notifier.warning("程序已停止", `模式: ${runMode}
1129
- PID: ${process.pid}`);
1130
- setTimeout(() => {
1131
- process.exit(0);
1132
- }, 1e3);
1133
- Promise.all([trader.shutdown(), listener.stop()]).finally(() => {
1134
- process.exit(0);
1135
- });
1603
+ await Promise.all([trader.shutdown(), telegramService.stop()]);
1604
+ process.exit(0);
1136
1605
  };
1137
- process.on("SIGINT", shutdown);
1138
- process.on("SIGTERM", shutdown);
1139
- await listener.start(() => {
1606
+ process.prependListener("SIGINT", () => {
1607
+ shutdown();
1608
+ });
1609
+ process.prependListener("SIGTERM", () => {
1610
+ shutdown();
1611
+ });
1612
+ await telegramService.start(() => {
1140
1613
  if (trader.isInitialized()) {
1141
1614
  trader.startRedeemer();
1142
1615
  }
@@ -192,398 +192,6 @@ ${this.withTimestamp(`❌ ${formatted}`)}`);
192
192
  }
193
193
  }
194
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(
335
- "",
336
- `当前状态: ${canListen ? "✅ 可监听预测消息" : "❌ 无法监听预测消息"}`
337
- );
338
- if (reason) {
339
- logger.line("", `原因: ${reason}`);
340
- }
341
- }
342
- if (isAdmin && !this.targetChatIds.has(chatId)) {
343
- this.targetChatIds.add(chatId);
344
- await this.saveChatIds();
345
- logger.line("📡", "已自动开始监听此频道");
346
- }
347
- if ((isRemoved || !isAdmin) && this.targetChatIds.has(chatId)) {
348
- this.targetChatIds.delete(chatId);
349
- await this.saveChatIds();
350
- if (isRemoved) {
351
- logger.line("⏹️", "已停止监听此频道(Bot 已被移除)");
352
- } else {
353
- logger.line("⏹️", "已停止监听此频道(Bot 不再是管理员)");
354
- }
355
- }
356
- }
357
- /**
358
- * 保存频道 ID 列表到配置(用于持久化)
359
- */
360
- async saveChatIds() {
361
- if (!this.onChatIdsChangedCallback) {
362
- return;
363
- }
364
- try {
365
- const chatIds = Array.from(this.targetChatIds);
366
- await this.onChatIdsChangedCallback(chatIds);
367
- } catch (error) {
368
- logger.error("保存频道 ID 列表时出错:", error);
369
- }
370
- }
371
- /**
372
- * 处理频道消息(channel_post 和 edited_channel_post)
373
- */
374
- async handleChannelPost(ctx) {
375
- const message = ctx.channelPost || ctx.editedChannelPost;
376
- if (!message) {
377
- return;
378
- }
379
- await this.processMessage(message, ctx);
380
- }
381
- /**
382
- * 处理频道消息(统一的消息处理逻辑)
383
- */
384
- async processMessage(message, ctx) {
385
- if (!message) {
386
- return;
387
- }
388
- const chatId = message.chat.id;
389
- const chatTitle = message.chat.title || "未知频道";
390
- const isChannel = chatId < 0 && chatId.toString().startsWith("-100");
391
- const messageText = message.text || message.caption || "";
392
- if (!messageText) {
393
- return;
394
- }
395
- if (isChannel && !this.targetChatIds.has(chatId)) {
396
- this.targetChatIds.add(chatId);
397
- logger.info(`➕ 自动添加频道到监听列表: ${chatTitle} (ID: ${chatId})`);
398
- await this.saveChatIds();
399
- logger.line("💾", "已保存到配置文件");
400
- }
401
- if (!isChannel) {
402
- return;
403
- }
404
- if (!this.targetChatIds.has(chatId)) {
405
- return;
406
- }
407
- const metadata = parseSignalMetadata(messageText);
408
- if (!metadata) {
409
- if (messageText.includes("🤖")) {
410
- const emojiIndex = messageText.indexOf("🤖");
411
- const jsonStart = emojiIndex + 1;
412
- const jsonEnd = Math.min(jsonStart + 200, messageText.length);
413
- logger.warning(`解析预测消息失败:`);
414
- logger.line("", `频道 ID: ${chatId}`);
415
- logger.line(
416
- "",
417
- `消息片段: ${messageText.substring(emojiIndex, jsonEnd)}${messageText.length > jsonEnd ? "..." : ""}`
418
- );
419
- logger.line("", "提示: 请检查消息格式是否正确");
420
- }
421
- return;
422
- }
423
- const parsedSignal = {
424
- metadata,
425
- rawMessage: messageText,
426
- messageId: message.message_id,
427
- chatId,
428
- timestamp: message.date * 1e3
429
- };
430
- if (this.onSignalCallback) {
431
- try {
432
- await this.onSignalCallback(parsedSignal);
433
- } catch (error) {
434
- logger.error("处理信号时出错:", error);
435
- }
436
- }
437
- }
438
- /**
439
- * 设置信号回调函数
440
- */
441
- onSignal(callback) {
442
- this.onSignalCallback = callback;
443
- }
444
- /**
445
- * 设置频道 ID 列表变更回调函数(当频道自动添加/移除时调用,用于持久化)
446
- */
447
- onChatIdsChanged(callback) {
448
- this.onChatIdsChangedCallback = callback;
449
- }
450
- /**
451
- * 设置监听的频道 ID 列表(用于启动时加载已保存的配置)
452
- */
453
- setTargetChatIds(chatIds) {
454
- this.targetChatIds = new Set(chatIds);
455
- }
456
- /**
457
- * 启动监听
458
- * @param onReady 可选回调,在频道检查完成后、开始监听之前调用
459
- */
460
- async start(onReady) {
461
- try {
462
- logger.info("正在启动 Bot...");
463
- const botInfo = await this.bot.api.getMe();
464
- const botId = botInfo.id;
465
- logger.success(`Bot 信息: @${botInfo.username} (ID: ${botId})`);
466
- await this.checkAndDisplayChannels(botId);
467
- if (onReady) {
468
- await onReady();
469
- }
470
- logger.info("开始监听预测消息...");
471
- await this.bot.start({
472
- drop_pending_updates: true,
473
- allowed_updates: [
474
- "channel_post",
475
- "edited_channel_post",
476
- "my_chat_member"
477
- ]
478
- });
479
- } catch (error) {
480
- logger.error("启动 Bot 失败:", error);
481
- throw error;
482
- }
483
- }
484
- /**
485
- * 检查并显示 Bot 所在的频道
486
- * 验证配置文件中保存的频道,检查 Bot 是否仍在频道中以及是否是管理员
487
- */
488
- async checkAndDisplayChannels(botId) {
489
- const chatIds = Array.from(this.targetChatIds);
490
- if (chatIds.length === 0) {
491
- logger.info(`📋 当前没有保存的频道配置`);
492
- logger.line(
493
- "",
494
- "提示: 当 Bot 被添加为频道管理员时,会自动添加到监听列表"
495
- );
496
- return;
497
- }
498
- logger.info(`📋 检查 Bot 所在的频道:`);
499
- const validChannels = [];
500
- const invalidChannels = [];
501
- for (const chatId of chatIds) {
502
- try {
503
- const chat = await this.bot.api.getChat(chatId);
504
- const chatTitle = chat.title || "未知频道";
505
- try {
506
- const member = await this.bot.api.getChatMember(chatId, botId);
507
- const isAdmin = member.status === "administrator" || member.status === "creator";
508
- validChannels.push({
509
- id: chatId,
510
- title: chatTitle,
511
- isAdmin
512
- });
513
- } catch (error) {
514
- invalidChannels.push({
515
- id: chatId,
516
- reason: "无法获取成员信息(可能已被移除)"
517
- });
518
- }
519
- } catch (error) {
520
- invalidChannels.push({
521
- id: chatId,
522
- reason: "无法获取频道信息(可能已被移除)"
523
- });
524
- }
525
- }
526
- if (validChannels.length > 0) {
527
- const adminChannels = validChannels.filter((c) => c.isAdmin);
528
- const nonAdminChannels = validChannels.filter((c) => !c.isAdmin);
529
- if (adminChannels.length > 0) {
530
- logger.info(`📡 可监听频道 (${adminChannels.length} 个):`);
531
- adminChannels.forEach((channel, index) => {
532
- logger.item(
533
- `${index + 1}. ${channel.title} (ID: ${channel.id}) 👑 管理员`,
534
- 1
535
- );
536
- });
537
- }
538
- if (nonAdminChannels.length > 0) {
539
- logger.warning(`无法监听频道 (${nonAdminChannels.length} 个):`);
540
- nonAdminChannels.forEach((channel, index) => {
541
- logger.item(`${index + 1}. ${channel.title}`, 1);
542
- logger.item(`ID: ${channel.id}`, 2);
543
- logger.item(`状态: 🚫 非管理员(无法接收预测消息)`, 2);
544
- logger.item(`原因: 需要管理员权限才能监听`, 2);
545
- });
546
- nonAdminChannels.forEach((channel) => {
547
- this.targetChatIds.delete(channel.id);
548
- });
549
- if (nonAdminChannels.length > 0) {
550
- await this.saveChatIds();
551
- logger.line("💾", "已清理无法监听的频道并保存配置");
552
- }
553
- }
554
- }
555
- if (invalidChannels.length > 0) {
556
- logger.error(`无效频道 (${invalidChannels.length} 个):`);
557
- invalidChannels.forEach((channel, index) => {
558
- logger.item(`${index + 1}. ID: ${channel.id}`, 1);
559
- logger.item(`原因: ${channel.reason}`, 2);
560
- });
561
- invalidChannels.forEach((channel) => {
562
- this.targetChatIds.delete(channel.id);
563
- });
564
- if (invalidChannels.length > 0) {
565
- await this.saveChatIds();
566
- logger.line("💾", "已清理无效频道并保存配置");
567
- }
568
- }
569
- }
570
- /**
571
- * 停止监听
572
- */
573
- async stop() {
574
- await this.bot.stop();
575
- }
576
- /**
577
- * 获取 bot 信息(用于验证 token 是否有效)
578
- */
579
- async getBotInfo() {
580
- try {
581
- return this.bot.api.getMe();
582
- } catch (error) {
583
- throw new Error(`获取 Bot 信息失败: ${error}`);
584
- }
585
- }
586
- }
587
195
  let globalRl = null;
588
196
  function getReadlineInterface() {
589
197
  if (!globalRl) {
@@ -602,9 +210,8 @@ function question(query) {
602
210
  }
603
211
  async function validateBotToken(botToken) {
604
212
  try {
605
- const listener = new TelegramListener(botToken);
606
- await listener.getBotInfo();
607
- await listener.stop();
213
+ const bot = new grammy.Bot(botToken);
214
+ await bot.api.getMe();
608
215
  return true;
609
216
  } catch (error) {
610
217
  logger.error(`Bot Token 验证失败: ${error}`);
@@ -966,7 +573,6 @@ async function ensureConfig() {
966
573
  }
967
574
  return config;
968
575
  }
969
- exports.TelegramListener = TelegramListener;
970
576
  exports.canAutoTrade = canAutoTrade;
971
577
  exports.configLocal = configLocal;
972
578
  exports.ensureConfig = ensureConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polycopy",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "polycopy test",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -15,7 +15,8 @@
15
15
  "cli": "node dist/cli.js",
16
16
  "preuninstall": "node dist/cli.js stop 2>/dev/null || true",
17
17
  "prepublishOnly": "npm run build",
18
- "test": "echo \"Error: no test specified\" && exit 1"
18
+ "test": "HOME=$PWD node --import tsx tests/redeemer.test.ts",
19
+ "test:redeemer": "HOME=$PWD node --import tsx tests/redeemer.test.ts"
19
20
  },
20
21
  "dependencies": {
21
22
  "@polymarket/builder-relayer-client": "^0.0.8",
@@ -33,6 +34,7 @@
33
34
  "@types/lodash": "^4.17.23",
34
35
  "@types/node": "^20.0.0",
35
36
  "prettier": "^3.7.4",
37
+ "tsx": "^4.19.2",
36
38
  "typescript": "^5.9.3",
37
39
  "vite": "^7.3.1",
38
40
  "vite-tsconfig-paths": "^6.0.4"