agus1111 commited on
Commit
4c7a3f1
·
verified ·
1 Parent(s): 2e80d36

Update bot_core.py

Browse files
Files changed (1) hide show
  1. bot_core.py +71 -91
bot_core.py CHANGED
@@ -1,49 +1,55 @@
1
- # bot-core.py (multi-source + anti-double KOL, tanpa .env)
2
- import re, time, sys, asyncio, nest_asyncio, logging
 
 
 
 
 
 
3
  from typing import Optional, Tuple
4
  from telethon import TelegramClient, events
5
  from telethon.sessions import StringSession
6
  from telethon.errors.rpcerrorlist import FloodWaitError
7
 
8
- # ========= KONFIG MANUAL =========
9
- import os
10
- API_ID = int(os.environ.get("API_ID", "0"))
11
- API_HASH = os.environ.get("API_HASH", "")
12
- STRING_SESSION = os.environ.get("STRING_SESSION", "")
13
- nest_asyncio.apply()
14
-
15
- if not (API_ID and API_HASH and STRING_SESSION):
16
- raise RuntimeError("API_ID/API_HASH/STRING_SESSION belum di-set di Secrets.")
17
- # Multi-source: langsung list
18
- SOURCE_CHATS = [
19
- "https://t.me/MidasTouchsignalll",
20
-
21
- ]
22
- TARGET_CHAT = "@solana_trojanbot"
23
-
24
- AMOUNT_LABEL = "" # isi kalau mau klik amount tertentu, misal "0.05 SOL"
25
- RETRY_LABELS = ["Retry", "Try Again", "Rerty", "RETRY", "RE-TRY", "Try again"]
26
-
27
- RETRY_MAX_ATTEMPTS = 12
28
- RETRY_SLEEP_SECONDS = 1.2
29
- RETRY_OVERALL_TIMEOUT = 60
30
- DEDUP_TTL_MINUTES = 600 # anti double KOL dalam menit
31
-
32
  # ========= LOGGING =========
 
33
  logger = logging.getLogger("midastouch-bot")
34
  if not logger.handlers:
35
  handler = logging.StreamHandler(sys.stdout)
36
  fmt = logging.Formatter(
37
  fmt="%(asctime)s | %(levelname)-7s | %(message)s",
38
- datefmt="%Y-%m-%d %H:%M:%S",
39
  )
40
  handler.setFormatter(fmt)
41
  logger.addHandler(handler)
42
- logger.setLevel(logging.INFO)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
  nest_asyncio.apply()
45
 
46
- # ========= REGEX =========
47
  CA_SOL_RE = re.compile(r"\b([1-9A-HJ-NP-Za-km-z]{32,44})(?:pump)?\b")
48
  CA_EVM_RE = re.compile(r"\b0x[a-fA-F0-9]{40}\b")
49
 
@@ -51,7 +57,7 @@ BUY_PAT = re.compile(r"(?i)buy")
51
  REFRESH_PAT = re.compile(r"(?i)refresh")
52
  RETRY_PAT = re.compile(r"(?i)(" + "|".join(map(re.escape, RETRY_LABELS)) + r")") if RETRY_LABELS else re.compile(r"$^")
53
 
54
- # ========= UTIL =========
55
  def _normalize_chat(s: str) -> str:
56
  if not s:
57
  return s
@@ -60,20 +66,10 @@ def _normalize_chat(s: str) -> str:
60
  s = m.group(1)
61
  return s.lstrip("@")
62
 
63
- def _label_chat_from_event(event) -> str:
64
- try:
65
- ent = event.chat
66
- if getattr(ent, "username", None):
67
- return f"@{ent.username}"
68
- if getattr(ent, "title", None):
69
- return ent.title
70
- except Exception:
71
- pass
72
- return f"id:{getattr(event, 'chat_id', 'unknown')}"
73
-
74
  def find_first_ca(text: str) -> Optional[Tuple[str, str]]:
75
  if not text:
76
  return None
 
77
  t = re.sub(r"(?m)^>.*", "", text)
78
  t = re.sub(r"\s+", " ", t).strip()
79
 
@@ -90,22 +86,20 @@ def find_first_ca(text: str) -> Optional[Tuple[str, str]]:
90
  logger.info("CA | ditemukan chain=%s addr=%s", chain, addr)
91
  return chain, addr
92
 
93
- # ========= STATE =========
94
  client = TelegramClient(StringSession(STRING_SESSION), API_ID, API_HASH)
95
  ca_queue: "asyncio.Queue[str]" = asyncio.Queue()
96
- recent_ca_info: dict[str, tuple[float, str]] = {}
 
97
 
98
  def _is_recent(addr: str) -> bool:
99
- ts_src = recent_ca_info.get(addr)
100
- if not ts_src:
101
- return False
102
- ts, _ = ts_src
103
- return (time.time() - ts) < (DEDUP_TTL_MINUTES * 60)
104
 
105
- def _mark_ca(addr: str, source_label: str):
106
- recent_ca_info[addr] = (time.time(), source_label)
107
 
108
- # ========= BOT FLOW HELPERS =========
109
  async def refresh_message(msg):
110
  return await client.get_messages(msg.chat_id, ids=msg.id)
111
 
@@ -122,10 +116,10 @@ async def wait_new_keyboard(chat, *, min_id: int, timeout=25, interval=0.8):
122
  return None
123
 
124
  async def click_first_match(msg, pat) -> bool:
125
- if not msg:
126
  return False
127
  msg = await refresh_message(msg)
128
- if not getattr(msg, "buttons", None):
129
  return False
130
  for i, row in enumerate(msg.buttons):
131
  for j, btn in enumerate(row):
@@ -158,6 +152,7 @@ async def click_retry_until_gone(anchor_msg, *, baseline_id: int) -> bool:
158
  await asyncio.sleep(RETRY_SLEEP_SECONDS)
159
  continue
160
 
 
161
  msgs = await client.get_messages(TARGET_CHAT, limit=8)
162
  for m in msgs:
163
  if m.id >= baseline_id and m.buttons:
@@ -173,6 +168,7 @@ async def click_retry_until_gone(anchor_msg, *, baseline_id: int) -> bool:
173
  logger.info("RET | selesai (attempts=%d)", attempts)
174
  return True
175
 
 
176
  async def buy_flow_with_ca(addr: str):
177
  logger.info("FLOW| start buy_flow CA=%s", addr)
178
  last = await client.get_messages(TARGET_CHAT, limit=1)
@@ -192,6 +188,7 @@ async def buy_flow_with_ca(addr: str):
192
  logger.error("KB | gagal dapat keyboard setelah fallback")
193
  return
194
 
 
195
  if AMOUNT_LABEL:
196
  ok_amt = await click_first_match(msg_kb, re.compile(re.escape(AMOUNT_LABEL), re.IGNORECASE))
197
  logger.info("UI | pilih amount '%s' → %s", AMOUNT_LABEL, "OK" if ok_amt else "MISS")
@@ -201,18 +198,8 @@ async def buy_flow_with_ca(addr: str):
201
  msg_kb = msg2 or await refresh_message(msg_kb)
202
  baseline_id = max(baseline_id, getattr(msg_kb, "id", baseline_id))
203
 
204
- logger.info("WAIT| 15s sebelum Refresh #1")
205
- await asyncio.sleep(15)
206
- ok_r1 = await click_first_match(msg_kb, REFRESH_PAT)
207
- logger.info("UI | Refresh #1 → %s", "OK" if ok_r1 else "MISS")
208
-
209
- logger.info("WAIT| 15s sebelum Refresh #2")
210
- await asyncio.sleep(15)
211
- msg_kb = await refresh_message(msg_kb)
212
- ok_r2 = await click_first_match(msg_kb, REFRESH_PAT)
213
- logger.info("UI | Refresh #2 → %s", "OK" if ok_r2 else "MISS")
214
-
215
- logger.info("STEP| klik BUY")
216
  ok_buy = await click_first_match(msg_kb, BUY_PAT)
217
  if not ok_buy:
218
  hit = await search_latest_with_button(TARGET_CHAT, BUY_PAT, min_id=baseline_id, limit=10)
@@ -220,7 +207,7 @@ async def buy_flow_with_ca(addr: str):
220
  msg_kb = hit
221
  ok_buy = True
222
  logger.info("UI | BUY ditemukan di pesan lain")
223
- if not ok_buy and getattr(msg_kb, "buttons", None) and msg_kb.buttons[-1]:
224
  try:
225
  await msg_kb.click(len(msg_kb.buttons) - 1, 0)
226
  ok_buy = True
@@ -247,40 +234,34 @@ async def buy_flow_with_ca(addr: str):
247
  await click_retry_until_gone(msg_kb, baseline_id=baseline_id)
248
  logger.info("DONE| buy_flow selesai untuk CA=%s", addr)
249
 
250
- # ========= ACCEPT / QUEUE =========
251
- def _accept_ca(addr: str, current_source: str) -> bool:
252
  if not addr:
253
  return False
254
- info = recent_ca_info.get(addr)
255
- if info:
256
- ts, first_src = info
257
- age = time.time() - ts
258
- if age < (DEDUP_TTL_MINUTES * 60):
259
- logger.info(
260
- "SKIP| CA %s duplikat (age=%.1fs < %ds) pertama dari [%s], sekarang dari [%s]",
261
- addr, age, DEDUP_TTL_MINUTES * 60, first_src, current_source
262
- )
263
- return False
264
- _mark_ca(addr, current_source)
265
  return True
266
 
267
- # ========= EVENT HANDLER =========
268
- @client.on(events.NewMessage(chats=[_normalize_chat(s) for s in SOURCE_CHATS]))
269
- async def on_message_from_sources(event: events.NewMessage.Event):
270
- src_label = _label_chat_from_event(event)
271
  txt = (event.raw_text or "").strip()
272
- logger.info("EVNT| pesan baru dari %s (len=%d)", src_label, len(txt))
273
 
 
274
  found = find_first_ca(txt)
275
  if not found:
 
276
  return
277
 
278
  _, addr = found
279
- if _accept_ca(addr, src_label):
280
  await ca_queue.put(addr)
281
- logger.info("QUEUE| enqueue CA %s (src=%s)", addr, src_label)
282
 
283
- # ========= LIFECYCLE =========
284
  async def _worker():
285
  while True:
286
  addr = await ca_queue.get()
@@ -295,12 +276,11 @@ async def _worker():
295
  ca_queue.task_done()
296
 
297
  async def start_bot_background():
298
- logger.info("BOOT| starting bot (sources=%s, target=%s, dedup=%d min)",
299
- ",".join([_normalize_chat(s) for s in SOURCE_CHATS]),
300
- TARGET_CHAT, DEDUP_TTL_MINUTES)
301
  await client.start()
302
  asyncio.create_task(_worker())
303
 
304
  async def stop_bot():
305
  logger.info("BOOT| stopping bot…")
306
- await client.disconnect()
 
1
+ # bot_core.py (no-tier-filter) - modified: removed refresh steps
2
+ import os
3
+ import re
4
+ import time
5
+ import sys
6
+ import asyncio
7
+ import nest_asyncio
8
+ import logging
9
  from typing import Optional, Tuple
10
  from telethon import TelegramClient, events
11
  from telethon.sessions import StringSession
12
  from telethon.errors.rpcerrorlist import FloodWaitError
13
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  # ========= LOGGING =========
15
+ LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO").upper()
16
  logger = logging.getLogger("midastouch-bot")
17
  if not logger.handlers:
18
  handler = logging.StreamHandler(sys.stdout)
19
  fmt = logging.Formatter(
20
  fmt="%(asctime)s | %(levelname)-7s | %(message)s",
21
+ datefmt="%(Y-%m-%d %H:%M:%S",
22
  )
23
  handler.setFormatter(fmt)
24
  logger.addHandler(handler)
25
+ logger.setLevel(LOG_LEVEL)
26
+
27
+ # Biar log Telethon ikut muncul saat DEBUG
28
+ logging.getLogger("telethon").setLevel(logging.WARNING if LOG_LEVEL != "DEBUG" else logging.INFO)
29
+
30
+ # ======== ENV & KONFIG ========
31
+ API_ID = int(os.environ.get("API_ID", "0"))
32
+ API_HASH = os.environ.get("API_HASH", "")
33
+ STRING_SESSION = os.environ.get("STRING_SESSION", "")
34
+
35
+ SOURCE_CHAT = os.environ.get("SOURCE_CHAT", "https://t.me/PEPE_Calls28")
36
+ TARGET_CHAT = os.environ.get("TARGET_CHAT", "@solana_trojanbot")
37
+ AMOUNT_LABEL = os.environ.get("AMOUNT_LABEL", "").strip()
38
+
39
+ RETRY_LABELS = [x.strip() for x in os.environ.get(
40
+ "RETRY_LABELS", "Retry,Try Again,Rerty,RETRY,RE-TRY,Try again"
41
+ ).split(",") if x.strip()]
42
+ RETRY_MAX_ATTEMPTS = int(os.environ.get("RETRY_MAX_ATTEMPTS", "12"))
43
+ RETRY_SLEEP_SECONDS = float(os.environ.get("RETRY_SLEEP_SECONDS", "1.2"))
44
+ RETRY_OVERALL_TIMEOUT = float(os.environ.get("RETRY_OVERALL_TIMEOUT", "60"))
45
+ RECENT_CA_TTL_MINUTES = int(os.environ.get("RECENT_CA_TTL_MINUTES", "60"))
46
+
47
+ if not (API_ID and API_HASH and STRING_SESSION):
48
+ raise RuntimeError("API_ID/API_HASH/STRING_SESSION belum di-set di Secrets.")
49
 
50
  nest_asyncio.apply()
51
 
52
+ # ======== REGEX ========
53
  CA_SOL_RE = re.compile(r"\b([1-9A-HJ-NP-Za-km-z]{32,44})(?:pump)?\b")
54
  CA_EVM_RE = re.compile(r"\b0x[a-fA-F0-9]{40}\b")
55
 
 
57
  REFRESH_PAT = re.compile(r"(?i)refresh")
58
  RETRY_PAT = re.compile(r"(?i)(" + "|".join(map(re.escape, RETRY_LABELS)) + r")") if RETRY_LABELS else re.compile(r"$^")
59
 
60
+ # ======== UTIL ========
61
  def _normalize_chat(s: str) -> str:
62
  if not s:
63
  return s
 
66
  s = m.group(1)
67
  return s.lstrip("@")
68
 
 
 
 
 
 
 
 
 
 
 
 
69
  def find_first_ca(text: str) -> Optional[Tuple[str, str]]:
70
  if not text:
71
  return None
72
+ # buang quoted > lines dan rapikan whitespace
73
  t = re.sub(r"(?m)^>.*", "", text)
74
  t = re.sub(r"\s+", " ", t).strip()
75
 
 
86
  logger.info("CA | ditemukan chain=%s addr=%s", chain, addr)
87
  return chain, addr
88
 
89
+ # ======== STATE ============
90
  client = TelegramClient(StringSession(STRING_SESSION), API_ID, API_HASH)
91
  ca_queue: "asyncio.Queue[str]" = asyncio.Queue()
92
+ recent_ca_ts: dict[str, float] = {}
93
+ _worker_task: asyncio.Task | None = None
94
 
95
  def _is_recent(addr: str) -> bool:
96
+ ts = recent_ca_ts.get(addr)
97
+ return bool(ts and (time.time() - ts) < (RECENT_CA_TTL_MINUTES * 60))
 
 
 
98
 
99
+ def _mark_ca(addr: str):
100
+ recent_ca_ts[addr] = time.time()
101
 
102
+ # ======== BOT FLOW HELPERS ========
103
  async def refresh_message(msg):
104
  return await client.get_messages(msg.chat_id, ids=msg.id)
105
 
 
116
  return None
117
 
118
  async def click_first_match(msg, pat) -> bool:
119
+ if not msg or not msg.buttons:
120
  return False
121
  msg = await refresh_message(msg)
122
+ if not msg.buttons:
123
  return False
124
  for i, row in enumerate(msg.buttons):
125
  for j, btn in enumerate(row):
 
152
  await asyncio.sleep(RETRY_SLEEP_SECONDS)
153
  continue
154
 
155
+ # cek pesan baru yang mungkin punya tombol Retry
156
  msgs = await client.get_messages(TARGET_CHAT, limit=8)
157
  for m in msgs:
158
  if m.id >= baseline_id and m.buttons:
 
168
  logger.info("RET | selesai (attempts=%d)", attempts)
169
  return True
170
 
171
+ # ======== BUY FLOW (modified: removed refresh waits) ========
172
  async def buy_flow_with_ca(addr: str):
173
  logger.info("FLOW| start buy_flow CA=%s", addr)
174
  last = await client.get_messages(TARGET_CHAT, limit=1)
 
188
  logger.error("KB | gagal dapat keyboard setelah fallback")
189
  return
190
 
191
+ # pilih amount jika diminta
192
  if AMOUNT_LABEL:
193
  ok_amt = await click_first_match(msg_kb, re.compile(re.escape(AMOUNT_LABEL), re.IGNORECASE))
194
  logger.info("UI | pilih amount '%s' → %s", AMOUNT_LABEL, "OK" if ok_amt else "MISS")
 
198
  msg_kb = msg2 or await refresh_message(msg_kb)
199
  baseline_id = max(baseline_id, getattr(msg_kb, "id", baseline_id))
200
 
201
+ # === LANGSUNG KE BUY (tanpa refresh) ===
202
+ logger.info("STEP| klik BUY (tanpa refresh)")
 
 
 
 
 
 
 
 
 
 
203
  ok_buy = await click_first_match(msg_kb, BUY_PAT)
204
  if not ok_buy:
205
  hit = await search_latest_with_button(TARGET_CHAT, BUY_PAT, min_id=baseline_id, limit=10)
 
207
  msg_kb = hit
208
  ok_buy = True
209
  logger.info("UI | BUY ditemukan di pesan lain")
210
+ if not ok_buy and msg_kb.buttons and msg_kb.buttons[-1]:
211
  try:
212
  await msg_kb.click(len(msg_kb.buttons) - 1, 0)
213
  ok_buy = True
 
234
  await click_retry_until_gone(msg_kb, baseline_id=baseline_id)
235
  logger.info("DONE| buy_flow selesai untuk CA=%s", addr)
236
 
237
+ # ======== ACCEPT / QUEUE ========
238
+ def _accept_ca(addr: str) -> bool:
239
  if not addr:
240
  return False
241
+ if _is_recent(addr):
242
+ logger.info("SKIP| CA %s sudah diproses baru-baru ini", addr)
243
+ return False
244
+ _mark_ca(addr)
 
 
 
 
 
 
 
245
  return True
246
 
247
+ # ======== EVENT HANDLER (no tier filter) ========
248
+ @client.on(events.NewMessage(chats=_normalize_chat(SOURCE_CHAT)))
249
+ async def on_message_from_source(event: events.NewMessage.Event):
 
250
  txt = (event.raw_text or "").strip()
251
+ logger.info("EVNT| pesan baru (len=%d)", len(txt))
252
 
253
+ # TANPA filter tier — langsung cari CA
254
  found = find_first_ca(txt)
255
  if not found:
256
+ logger.info("INFO| tidak ada CA di pesan ini")
257
  return
258
 
259
  _, addr = found
260
+ if _accept_ca(addr):
261
  await ca_queue.put(addr)
262
+ logger.info("QUEUE| enqueue CA %s", addr)
263
 
264
+ # ======== LIFECYCLE ========
265
  async def _worker():
266
  while True:
267
  addr = await ca_queue.get()
 
276
  ca_queue.task_done()
277
 
278
  async def start_bot_background():
279
+ logger.info("BOOT| starting bot (source=%s, target=%s, log=%s)",
280
+ SOURCE_CHAT, TARGET_CHAT, LOG_LEVEL)
 
281
  await client.start()
282
  asyncio.create_task(_worker())
283
 
284
  async def stop_bot():
285
  logger.info("BOOT| stopping bot…")
286
+ await client.disconnect()