agus1111 commited on
Commit
6f8bc49
Β·
verified Β·
1 Parent(s): 06758da

Update autotrack.py

Browse files
Files changed (1) hide show
  1. autotrack.py +64 -96
autotrack.py CHANGED
@@ -34,10 +34,6 @@ STOP_WHEN_HIT = False
34
 
35
  # Abaikan pesan lebih tua dari (startup - buffer)
36
  BACKFILL_BUFFER_MINUTES = int(os.environ.get("BACKFILL_BUFFER_MINUTES", "3"))
37
- CUTOFF_BUFFER = timedelta(minutes=BACKFILL_BUFFER_MINUTES)
38
-
39
- # NEW: matikan cutoff default (biar pesan yang dipost sedikit sebelum attach tetap diproses).
40
- DISABLE_OLD_MESSAGE_CUTOFF = os.environ.get("DISABLE_OLD_MESSAGE_CUTOFF", "1") == "1"
41
 
42
  # Marker antirecursive: ditambahkan ke semua pengumuman bot (milestone)
43
  BOT_MARKER = "【MT-AUTOTRACK】"
@@ -85,16 +81,26 @@ def _fmt_big(x: Optional[float]) -> str:
85
  return f"${x/1_000:.2f}K"
86
  return f"${x:.0f}"
87
 
88
- def _parse_target_username(url_or_username: str) -> Optional[str]:
89
- """Terima 'https://t.me/xxx' atau '@xxx' atau 'xxx' β†’ kembalikan 'xxx' (lower)."""
90
- if not url_or_username:
91
- return None
92
- s = url_or_username.strip()
93
- if s.startswith("https://t.me/"):
94
- s = s.split("https://t.me/", 1)[1]
95
- if s.startswith("@"):
96
- s = s[1:]
97
- return s.lower()
 
 
 
 
 
 
 
 
 
 
98
 
99
  # =========================
100
  # CA extraction
@@ -241,46 +247,30 @@ class PriceTracker:
241
  print(f"[TRACK] reply failed: {e}")
242
 
243
  def _milestone_text(self, item: TrackItem, m: float, ratio: float,
244
- cur_price: Optional[float], cur_mcap: Optional[float]) -> str:
245
- # Links & label basis
246
  dexs_link = f"https://dexscreener.com/solana/{item.ca}"
247
  axiom_link = "https://axiom.trade/@1144321"
248
- basis_label = "Market Cap" if item.basis == "mcap" else "Price"
249
-
250
- # Perubahan angka (jaga format sesuai basis)
251
  if item.basis == "mcap":
252
- change = f"{_fmt_big(item.entry_mcap)} β†’ {_fmt_big(cur_mcap)} (~{ratio:.2f}Γ—)"
253
  else:
254
- change = f"{_fmt_money(item.entry_price)} β†’ {_fmt_money(cur_price)} (~{ratio:.2f}Γ—)"
255
-
256
- # Nama token (punya fallback yg sudah ada)
257
- name = self._name(item)
258
-
259
- # Elapsed time (hh:mm:ss)
260
  elapsed = int(time.time() - item.started_at)
261
- hh, rem = divmod(elapsed, 3600)
262
- mm, ss = divmod(rem, 60)
263
- elapsed_str = (f"{hh}h {mm}m {ss}s" if hh else f"{mm}m {ss}s")
264
-
265
- # Header + body yang rapi & branded
266
- lines = [
267
- "πŸ’Ž **MidasTouch Signal**",
268
- f"**Milestone {m}Γ— {basis_label}** β€’ **Now:** ~{ratio:.2f}Γ—",
269
- "",
270
- f"**{name}**",
271
- f"{change}",
272
- f"⏱️ Since call: {elapsed_str}",
273
- "",
274
- "Quick Actions:",
275
- f"β€’ πŸ”Ž [View on Dexscreener]({dexs_link})",
276
- f"β€’ πŸ›’ [Trade with Axiom]({axiom_link})",
277
- "",
278
- f"CA: `{item.ca}`",
279
- "",
280
- "_Signals β‰  certainty. DYOR. Manage risk like a pro._"
281
- ]
282
- return "\n".join(lines)
283
-
284
 
285
  def _next_target_after(self, current_target: float) -> float:
286
  # Setelah 1.5Γ—, lanjut integer berikutnya: 2Γ—, 3Γ—, 4Γ—, ...
@@ -325,11 +315,8 @@ class PriceTracker:
325
  item.symbol_hint = snap.get("symbol_hint")
326
 
327
  if item.basis == "mcap" and not item.entry_mcap:
328
- print(f"[TRACK] {item.ca} no mcap available at entry. Fallback to price basis.")
329
  item.basis = "price"
330
 
331
- print(f"[TRACK] start {item.ca} basis={item.basis} entry_mcap={item.entry_mcap} entry_price={item.entry_price} poll={item.poll_secs}s")
332
-
333
  while True:
334
  snap = await self._get_snapshot(item.ca)
335
  if not snap:
@@ -354,35 +341,35 @@ class PriceTracker:
354
  ratio = (cur_price / item.entry_price) if item.entry_price > 0 else 0.0
355
 
356
  # === NEW: auto delete kalau drop >= threshold ===
 
357
  if DROP_DELETE_RATIO > 0 and ratio <= DROP_DELETE_RATIO:
358
- print(f"[TRACK] {item.ca} dropped to {ratio:.2f}Γ— (≀ {DROP_DELETE_RATIO}Γ—). Deleting origin & stopping.")
359
  await self._delete_origin_and_replies(item.ca)
360
  return # stop tracking setelah dihapus
361
 
362
  # --- milestone handling ---
363
  while ratio >= item.next_milestone:
364
- print(f"[TRACK] {item.ca} hit {item.next_milestone}x (ratio={ratio:.2f})")
365
  text = self._milestone_text(item, item.next_milestone, ratio, cur_price, cur_mcap)
366
- await self._send_reply_to_origin(item.ca, text)
367
-
368
- # >>> VIDEO milestone β‰₯ 3Γ— (atau sesuai VIDEO_MIN_MULTIPLE)
369
- if item.next_milestone >= VIDEO_MIN_MULTIPLE:
370
- if os.path.isfile(VIDEO_PATH):
371
- try:
372
- key = ca_key_for_db(item.ca)
373
- reply_to_id = lookup_origin_msg_id(key) if key else None
374
- await self.client.send_file(
375
- self.announce_chat,
376
- VIDEO_PATH,
377
- caption="πŸš€ To the Moon! πŸŒ•",
378
- reply_to=reply_to_id if reply_to_id else None,
379
- force_document=False
380
- )
381
- except Exception as e:
382
- print(f"[TRACK] gagal kirim video: {e}")
383
- else:
384
- print(f"[TRACK] VIDEO_PATH tidak ditemukan: {VIDEO_PATH}")
385
-
 
386
  # Naikkan target ke milestone berikut
387
  item.next_milestone = self._next_target_after(item.next_milestone)
388
 
@@ -412,7 +399,6 @@ class PriceTracker:
412
  symbol_hint=symbol_hint,
413
  next_milestone=REPLY_FROM_MULTIPLE,
414
  )
415
- print(f"[TRACK] arming {ca} (basis={item.basis}, poll={item.poll_secs}s) from handler")
416
  self._tasks[ca] = asyncio.create_task(self._loop(item))
417
 
418
 
@@ -426,8 +412,6 @@ def setup_autotrack(shared_client: TelegramClient, announce_chat: str | None = N
426
  tracker = PriceTracker(client, announce_chat=ac)
427
  startup_time_utc = datetime.now(timezone.utc)
428
  client.add_event_handler(on_new_message, events.NewMessage(chats=(TARGET_CHAT,)))
429
- # NEW: dengarkan juga edit (tanpa backfill)
430
- client.add_event_handler(on_new_message, events.MessageEdited(chats=(TARGET_CHAT,)))
431
  print("[AUTOTRACK] attached to shared client; listening on", TARGET_CHAT)
432
 
433
  # =========================
@@ -448,9 +432,9 @@ tracker: PriceTracker | None = None
448
  # =========================
449
  # Event handler: ONLY your group
450
  # =========================
 
 
451
  def _is_old_message(msg_dt: Optional[datetime]) -> bool:
452
- if DISABLE_OLD_MESSAGE_CUTOFF:
453
- return False
454
  if not isinstance(msg_dt, datetime):
455
  return False
456
  return msg_dt.replace(tzinfo=timezone.utc) < (startup_time_utc - CUTOFF_BUFFER)
@@ -461,16 +445,6 @@ def _is_bot_own_message(event) -> bool:
461
 
462
  async def on_new_message(event):
463
  try:
464
- # Handler-side filter: only process messages from TARGET_CHAT
465
- try:
466
- target_user = _parse_target_username(TARGET_CHAT)
467
- chat = await event.get_chat()
468
- uname = (getattr(chat, 'username', None) or '')
469
- if target_user and (uname.lower() != target_user):
470
- return
471
- except Exception:
472
- # If we cannot resolve chat username, continue (best-effort)
473
- pass
474
  if _is_bot_own_message(event):
475
  return
476
  msg = event.message
@@ -480,10 +454,6 @@ async def on_new_message(event):
480
  ca = extract_ca(text)
481
  if not ca:
482
  return
483
-
484
- # Log arming point for traceability
485
- print(f"[TRACK] arming {ca} from message id={getattr(msg, 'id', None)} (edited={isinstance(event, events.MessageEdited.Event)})")
486
-
487
  # Optional: link back to source (tidak dipakai di teks sekarang, tapi disimpan kalau perlu)
488
  source_link = None
489
  try:
@@ -519,10 +489,8 @@ async def main():
519
  print(f">> Logged in as: {getattr(me, 'username', None) or me_user_id}")
520
  except Exception as e:
521
  print(f"Warning: cannot resolve self id: {e}")
522
- print(f"AutoTrack running (unlimited milestones; first reply β‰₯ {REPLY_FROM_MULTIPLE}Γ—). Listening ONLY your group for NEW/EDITED messages...")
523
  client.add_event_handler(on_new_message, events.NewMessage(chats=(TARGET_CHAT,)))
524
- # NEW: dengarkan juga edit (tanpa backfill)
525
- client.add_event_handler(on_new_message, events.MessageEdited(chats=(TARGET_CHAT,)))
526
  await client.run_until_disconnected()
527
 
528
  if __name__ == "__main__":
 
34
 
35
  # Abaikan pesan lebih tua dari (startup - buffer)
36
  BACKFILL_BUFFER_MINUTES = int(os.environ.get("BACKFILL_BUFFER_MINUTES", "3"))
 
 
 
 
37
 
38
  # Marker antirecursive: ditambahkan ke semua pengumuman bot (milestone)
39
  BOT_MARKER = "【MT-AUTOTRACK】"
 
81
  return f"${x/1_000:.2f}K"
82
  return f"${x:.0f}"
83
 
84
+ def _milestone_badge(m: float, basis: str) -> str:
85
+ """
86
+ Balikin judul milestone yang menonjol untuk semua level.
87
+ basis: "mcap" atau "price"
88
+ """
89
+ unit = "Market Cap" if basis == "mcap" else "Price"
90
+
91
+ # kategorisasi level agar konsisten & gampang dibaca
92
+ if m < 2.0 - 1e-9: # 1.5Γ— (reply awal)
93
+ return f"🟨 **ARMED β€” {m:.1f}Γ— {unit}**"
94
+ elif abs(m - 2.0) < 1e-9: # 2Γ—
95
+ return f"🟩 **DOUBLE UP β€” 2Γ— {unit}!**"
96
+ elif abs(m - 3.0) < 1e-9: # 3Γ—
97
+ return f"πŸ”· **TRIPLE β€” 3Γ— {unit}!**"
98
+ elif 3.0 < m < 6.0: # 4–5Γ—
99
+ return f"πŸ”Ά **RALLY β€” {m:.0f}Γ— {unit}!**"
100
+ elif 6.0 <= m < 10.0: # 6–9Γ—
101
+ return f"πŸŸ₯ **OVERDRIVE β€” {m:.0f}Γ— {unit}!**"
102
+ else: # 10Γ—+
103
+ return f"🟣 **GOD CANDLE β€” {m:.0f}Γ— {unit}!!**"
104
 
105
  # =========================
106
  # CA extraction
 
247
  print(f"[TRACK] reply failed: {e}")
248
 
249
  def _milestone_text(self, item: TrackItem, m: float, ratio: float,
250
+ cur_price: Optional[float], cur_mcap: Optional[float]) -> str:
 
251
  dexs_link = f"https://dexscreener.com/solana/{item.ca}"
252
  axiom_link = "https://axiom.trade/@1144321"
 
 
 
253
  if item.basis == "mcap":
254
+ change = f"{_fmt_big(item.entry_mcap)} β†’ {_fmt_big(cur_mcap)} (~{ratio:.2f}Γ—)"
255
  else:
256
+ change = f"{_fmt_money(item.entry_price)} β†’ {_fmt_money(cur_price)} (~{ratio:.2f}Γ—)"
257
+ milestone_line = _milestone_badge(m, item.basis)
 
 
 
 
258
  elapsed = int(time.time() - item.started_at)
259
+ mm, ss = divmod(elapsed, 60)
260
+ header = "πŸ’Ž [MidasTouch Signal] πŸ’Ž\n━━━━━━━━━━━━━━━━"
261
+ footer = "━━━━━━━━━━━━━━━━"
262
+ body = (
263
+ f"{header}\n"
264
+ f"{milestone_line}\n"
265
+ f"{self._name(item)}\n"
266
+ f"{change}\n"
267
+ f"⏱️ {mm}m {ss}s since call\n"
268
+ f"πŸ”Ž [Dexscreener]({dexs_link})\n"
269
+ f"πŸ›’ [Trade on Axiom]({axiom_link})\n"
270
+ f"CA: `{item.ca}`\n"
271
+ f"{footer}"
272
+ )
273
+ return body
 
 
 
 
 
 
 
 
274
 
275
  def _next_target_after(self, current_target: float) -> float:
276
  # Setelah 1.5Γ—, lanjut integer berikutnya: 2Γ—, 3Γ—, 4Γ—, ...
 
315
  item.symbol_hint = snap.get("symbol_hint")
316
 
317
  if item.basis == "mcap" and not item.entry_mcap:
 
318
  item.basis = "price"
319
 
 
 
320
  while True:
321
  snap = await self._get_snapshot(item.ca)
322
  if not snap:
 
341
  ratio = (cur_price / item.entry_price) if item.entry_price > 0 else 0.0
342
 
343
  # === NEW: auto delete kalau drop >= threshold ===
344
+ # ratio <= 0.5 berarti -50% dari entry (default)
345
  if DROP_DELETE_RATIO > 0 and ratio <= DROP_DELETE_RATIO:
 
346
  await self._delete_origin_and_replies(item.ca)
347
  return # stop tracking setelah dihapus
348
 
349
  # --- milestone handling ---
350
  while ratio >= item.next_milestone:
 
351
  text = self._milestone_text(item, item.next_milestone, ratio, cur_price, cur_mcap)
352
+ key = ca_key_for_db(item.ca)
353
+ reply_to_id = lookup_origin_msg_id(key) if key else None
354
+ # Gabungkan jadi 1 pesan: kalau β‰₯ VIDEO_MIN_MULTIPLE dan ada file β†’ kirim video dengan caption = text
355
+ if item.next_milestone >= VIDEO_MIN_MULTIPLE and os.path.isfile(VIDEO_PATH):
356
+ try:
357
+ await self.client.send_file(
358
+ self.announce_chat,
359
+ VIDEO_PATH,
360
+ caption=f"{text}\n\n{BOT_MARKER}",
361
+ reply_to=reply_to_id if reply_to_id else None,
362
+ force_document=False
363
+ )
364
+ except Exception as e:
365
+ print(f"[TRACK] gagal kirim video: {e}")
366
+ else:
367
+ await self.client.send_message(
368
+ self.announce_chat,
369
+ f"{text}\n\n{BOT_MARKER}",
370
+ reply_to=reply_to_id if reply_to_id else None,
371
+ link_preview=False
372
+ )
373
  # Naikkan target ke milestone berikut
374
  item.next_milestone = self._next_target_after(item.next_milestone)
375
 
 
399
  symbol_hint=symbol_hint,
400
  next_milestone=REPLY_FROM_MULTIPLE,
401
  )
 
402
  self._tasks[ca] = asyncio.create_task(self._loop(item))
403
 
404
 
 
412
  tracker = PriceTracker(client, announce_chat=ac)
413
  startup_time_utc = datetime.now(timezone.utc)
414
  client.add_event_handler(on_new_message, events.NewMessage(chats=(TARGET_CHAT,)))
 
 
415
  print("[AUTOTRACK] attached to shared client; listening on", TARGET_CHAT)
416
 
417
  # =========================
 
432
  # =========================
433
  # Event handler: ONLY your group
434
  # =========================
435
+ CUTOFF_BUFFER = timedelta(minutes=BACKFILL_BUFFER_MINUTES)
436
+
437
  def _is_old_message(msg_dt: Optional[datetime]) -> bool:
 
 
438
  if not isinstance(msg_dt, datetime):
439
  return False
440
  return msg_dt.replace(tzinfo=timezone.utc) < (startup_time_utc - CUTOFF_BUFFER)
 
445
 
446
  async def on_new_message(event):
447
  try:
 
 
 
 
 
 
 
 
 
 
448
  if _is_bot_own_message(event):
449
  return
450
  msg = event.message
 
454
  ca = extract_ca(text)
455
  if not ca:
456
  return
 
 
 
 
457
  # Optional: link back to source (tidak dipakai di teks sekarang, tapi disimpan kalau perlu)
458
  source_link = None
459
  try:
 
489
  print(f">> Logged in as: {getattr(me, 'username', None) or me_user_id}")
490
  except Exception as e:
491
  print(f"Warning: cannot resolve self id: {e}")
492
+ print(f"AutoTrack running (unlimited milestones; first reply β‰₯ {REPLY_FROM_MULTIPLE}Γ—). Listening ONLY your group for NEW messages...")
493
  client.add_event_handler(on_new_message, events.NewMessage(chats=(TARGET_CHAT,)))
 
 
494
  await client.run_until_disconnected()
495
 
496
  if __name__ == "__main__":