Spaces:
Sleeping
Sleeping
Update bot_core.py
Browse files- bot_core.py +45 -118
bot_core.py
CHANGED
|
@@ -4,7 +4,6 @@ from telethon import TelegramClient, events
|
|
| 4 |
from telethon.sessions import StringSession
|
| 5 |
from telethon.errors.rpcerrorlist import FloodWaitError
|
| 6 |
|
| 7 |
-
|
| 8 |
# ========= LOGGING =========
|
| 9 |
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO").upper()
|
| 10 |
logger = logging.getLogger("midastouch-bot")
|
|
@@ -24,7 +23,6 @@ API_ID = int(os.environ.get("API_ID", "0"))
|
|
| 24 |
API_HASH = os.environ.get("API_HASH", "")
|
| 25 |
STRING_SESSION = os.environ.get("STRING_SESSION", "")
|
| 26 |
|
| 27 |
-
# Comma-separated; each item may be a full t.me URL or @username or bare username
|
| 28 |
SOURCE_CHATS_ENV = os.environ.get("SOURCE_CHATS", "@KOLscopeAlertsBot")
|
| 29 |
SOURCE_CHATS = [s.strip() for s in SOURCE_CHATS_ENV.split(",") if s.strip()]
|
| 30 |
TARGET_CHAT = os.environ.get("TARGET_CHAT", "@solana_trojanbot").strip()
|
|
@@ -37,8 +35,8 @@ RETRY_LABELS = [x.strip() for x in os.environ.get(
|
|
| 37 |
RETRY_MAX_ATTEMPTS = int(os.environ.get("RETRY_MAX_ATTEMPTS", "12"))
|
| 38 |
RETRY_SLEEP_SECONDS = float(os.environ.get("RETRY_SLEEP_SECONDS", "1.2"))
|
| 39 |
RETRY_OVERALL_TIMEOUT = float(os.environ.get("RETRY_OVERALL_TIMEOUT", "60"))
|
| 40 |
-
|
| 41 |
-
|
| 42 |
|
| 43 |
if not (API_ID and API_HASH and STRING_SESSION):
|
| 44 |
raise RuntimeError("API_ID/API_HASH/STRING_SESSION belum di-set di Secrets.")
|
|
@@ -49,8 +47,8 @@ nest_asyncio.apply()
|
|
| 49 |
CA_SOL_RE = re.compile(r"\b([1-9A-HJ-NP-Za-km-z]{32,44})(?:pump)?\b")
|
| 50 |
CA_EVM_RE = re.compile(r"\b0x[a-fA-F0-9]{40}\b")
|
| 51 |
|
| 52 |
-
BUY_PAT
|
| 53 |
-
RETRY_PAT
|
| 54 |
|
| 55 |
# ========= UTIL =========
|
| 56 |
def _normalize_chat(s: str) -> str:
|
|
@@ -91,10 +89,36 @@ def find_first_ca(text: str) -> Optional[Tuple[str, str]]:
|
|
| 91 |
logger.info("CA | ditemukan chain=%s addr=%s", chain, addr)
|
| 92 |
return chain, addr
|
| 93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
# ========= STATE =========
|
| 95 |
client = TelegramClient(StringSession(STRING_SESSION), API_ID, API_HASH)
|
| 96 |
ca_queue: "asyncio.Queue[str]" = asyncio.Queue()
|
| 97 |
-
# addr -> (timestamp, first_source_label)
|
| 98 |
recent_ca_info: dict[str, tuple[float, str]] = {}
|
| 99 |
|
| 100 |
def _is_recent(addr: str) -> bool:
|
|
@@ -104,7 +128,7 @@ def _is_recent(addr: str) -> bool:
|
|
| 104 |
def _mark_ca(addr: str, source_label: str):
|
| 105 |
recent_ca_info[addr] = (time.time(), source_label)
|
| 106 |
|
| 107 |
-
# =========
|
| 108 |
async def refresh_message(msg):
|
| 109 |
return await client.get_messages(msg.chat_id, ids=msg.id)
|
| 110 |
|
|
@@ -114,10 +138,8 @@ async def wait_new_keyboard(chat, *, min_id: int, timeout=25, interval=0.8):
|
|
| 114 |
msgs = await client.get_messages(chat, limit=8)
|
| 115 |
for m in msgs:
|
| 116 |
if m.id > min_id and m.buttons:
|
| 117 |
-
logger.debug("KB | keyboard baru id=%s", m.id)
|
| 118 |
return m
|
| 119 |
await asyncio.sleep(interval)
|
| 120 |
-
logger.warning("KB | tidak menemukan keyboard (timeout=%ss)", timeout)
|
| 121 |
return None
|
| 122 |
|
| 123 |
async def click_first_match(msg, pat) -> bool:
|
|
@@ -130,134 +152,44 @@ async def click_first_match(msg, pat) -> bool:
|
|
| 130 |
for j, btn in enumerate(row):
|
| 131 |
label = (getattr(btn, "text", "") or "").strip()
|
| 132 |
if label and pat.search(label):
|
| 133 |
-
logger.info("UI | klik tombol: %s (i=%d, j=%d)", label, i, j)
|
| 134 |
await msg.click(i, j)
|
| 135 |
return True
|
| 136 |
return False
|
| 137 |
|
| 138 |
-
async def search_latest_with_button(chat, pat, *, min_id: int, limit=10):
|
| 139 |
-
msgs = await client.get_messages(chat, limit=limit)
|
| 140 |
-
for m in msgs:
|
| 141 |
-
if m.id >= min_id and m.buttons:
|
| 142 |
-
m2 = await client.get_messages(chat, ids=m.id)
|
| 143 |
-
if m2 and await click_first_match(m2, pat):
|
| 144 |
-
logger.debug("UI | tombol ditemukan di pesan id=%s", m.id)
|
| 145 |
-
return m2
|
| 146 |
-
return None
|
| 147 |
-
|
| 148 |
-
async def click_retry_until_gone(anchor_msg, *, baseline_id: int) -> bool:
|
| 149 |
-
attempts = 0
|
| 150 |
-
started = time.monotonic()
|
| 151 |
-
msg_kb = anchor_msg
|
| 152 |
-
while attempts < RETRY_MAX_ATTEMPTS and (time.monotonic() - started) < (RETRY_OVERALL_TIMEOUT):
|
| 153 |
-
msg_kb = await refresh_message(msg_kb)
|
| 154 |
-
if await click_first_match(msg_kb, RETRY_PAT):
|
| 155 |
-
attempts += 1
|
| 156 |
-
logger.info("RET | klik Retry ke-%d", attempts)
|
| 157 |
-
await asyncio.sleep(RETRY_SLEEP_SECONDS)
|
| 158 |
-
continue
|
| 159 |
-
|
| 160 |
-
msgs = await client.get_messages(TARGET_CHAT, limit=8)
|
| 161 |
-
for m in msgs:
|
| 162 |
-
if m.id >= baseline_id and m.buttons:
|
| 163 |
-
m2 = await client.get_messages(TARGET_CHAT, ids=m.id)
|
| 164 |
-
if m2 and await click_first_match(m2, RETRY_PAT):
|
| 165 |
-
attempts += 1
|
| 166 |
-
baseline_id = max(baseline_id, m2.id)
|
| 167 |
-
logger.info("RET | klik Retry di msg.id=%s (total=%d)", m.id, attempts)
|
| 168 |
-
await asyncio.sleep(RETRY_SLEEP_SECONDS)
|
| 169 |
-
break
|
| 170 |
-
else:
|
| 171 |
-
break
|
| 172 |
-
logger.info("RET | selesai (attempts=%d)", attempts)
|
| 173 |
-
return True
|
| 174 |
-
|
| 175 |
async def buy_flow_with_ca(addr: str):
|
| 176 |
logger.info("FLOW| start buy_flow CA=%s", addr)
|
| 177 |
last = await client.get_messages(TARGET_CHAT, limit=1)
|
| 178 |
baseline_id = last[0].id if last else 0
|
| 179 |
-
|
| 180 |
await client.send_message(TARGET_CHAT, addr)
|
| 181 |
-
logger.info("SEND| CA terkirim ke target bot")
|
| 182 |
-
|
| 183 |
msg_kb = await wait_new_keyboard(TARGET_CHAT, min_id=baseline_id, timeout=25)
|
| 184 |
if not msg_kb:
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
await asyncio.sleep(1.0)
|
| 188 |
-
await client.send_message(TARGET_CHAT, addr)
|
| 189 |
-
msg_kb = await wait_new_keyboard(TARGET_CHAT, min_id=baseline_id, timeout=25)
|
| 190 |
-
if not msg_kb:
|
| 191 |
-
logger.error("KB | gagal dapat keyboard setelah fallback")
|
| 192 |
-
return
|
| 193 |
-
|
| 194 |
-
if AMOUNT_LABEL:
|
| 195 |
-
ok_amt = await click_first_match(msg_kb, re.compile(re.escape(AMOUNT_LABEL), re.IGNORECASE))
|
| 196 |
-
logger.info("UI | pilih amount '%s' → %s", AMOUNT_LABEL, "OK" if ok_amt else "MISS")
|
| 197 |
-
if ok_amt:
|
| 198 |
-
await asyncio.sleep(1.0)
|
| 199 |
-
msg2 = await wait_new_keyboard(TARGET_CHAT, min_id=baseline_id, timeout=12)
|
| 200 |
-
msg_kb = msg2 or await refresh_message(msg_kb)
|
| 201 |
-
baseline_id = max(baseline_id, getattr(msg_kb, "id", baseline_id))
|
| 202 |
-
|
| 203 |
-
logger.info("STEP| klik BUY")
|
| 204 |
-
ok_buy = await click_first_match(msg_kb, BUY_PAT)
|
| 205 |
-
if not ok_buy:
|
| 206 |
-
hit = await search_latest_with_button(TARGET_CHAT, BUY_PAT, min_id=baseline_id, limit=10)
|
| 207 |
-
if hit:
|
| 208 |
-
msg_kb = hit
|
| 209 |
-
ok_buy = True
|
| 210 |
-
logger.info("UI | BUY ditemukan di pesan lain")
|
| 211 |
-
if not ok_buy and getattr(msg_kb, "buttons", None) and msg_kb.buttons[-1]:
|
| 212 |
-
try:
|
| 213 |
-
await msg_kb.click(len(msg_kb.buttons) - 1, 0)
|
| 214 |
-
ok_buy = True
|
| 215 |
-
logger.info("FALL| klik tombol terakhir sebagai BUY")
|
| 216 |
-
except Exception as e:
|
| 217 |
-
logger.debug("FALL| gagal klik tombol terakhir: %s", e)
|
| 218 |
-
|
| 219 |
-
if not ok_buy:
|
| 220 |
-
logger.warning("BUY | tombol tidak ditemukan → ulangi /start")
|
| 221 |
-
await client.send_message(TARGET_CHAT, "/start")
|
| 222 |
-
await asyncio.sleep(1.0)
|
| 223 |
-
await client.send_message(TARGET_CHAT, addr)
|
| 224 |
-
msg_kb2 = await wait_new_keyboard(TARGET_CHAT, min_id=baseline_id, timeout=25)
|
| 225 |
-
msg_kb = msg_kb2 or msg_kb
|
| 226 |
-
if not await click_first_match(msg_kb, BUY_PAT):
|
| 227 |
-
hit2 = await search_latest_with_button(TARGET_CHAT, BUY_PAT, min_id=baseline_id, limit=10)
|
| 228 |
-
if not hit2:
|
| 229 |
-
logger.error("BUY | tetap gagal setelah ulang")
|
| 230 |
-
return
|
| 231 |
-
msg_kb = hit2
|
| 232 |
-
logger.info("UI | BUY ditemukan setelah ulang")
|
| 233 |
-
|
| 234 |
-
await asyncio.sleep(1.0)
|
| 235 |
-
await click_retry_until_gone(msg_kb, baseline_id=baseline_id)
|
| 236 |
logger.info("DONE| buy_flow selesai untuk CA=%s", addr)
|
| 237 |
|
| 238 |
# ========= ACCEPT / QUEUE =========
|
| 239 |
-
|
| 240 |
def _accept_ca(addr: str, current_source: str) -> bool:
|
| 241 |
if not addr:
|
| 242 |
return False
|
| 243 |
if _is_recent(addr):
|
| 244 |
-
ts, first_src = recent_ca_info.get(addr, (0.0, "?"))
|
| 245 |
-
age = time.time() - ts
|
| 246 |
-
logger.info(
|
| 247 |
-
"SKIP| CA %s duplikat (age=%.1fs < %ds) pertama dari [%s], sekarang dari [%s]",
|
| 248 |
-
addr, age, DEDUP_TTL_MINUTES * 60, first_src, current_source
|
| 249 |
-
)
|
| 250 |
return False
|
| 251 |
_mark_ca(addr, current_source)
|
| 252 |
return True
|
| 253 |
|
| 254 |
# ========= EVENT HANDLER =========
|
| 255 |
-
|
| 256 |
-
@client.on(events.NewMessage(chats=[_normalize_chat(s) for s in SOURCE_CHATS]))
|
| 257 |
async def on_message_from_sources(event: events.NewMessage.Event):
|
| 258 |
src_label = _label_chat_from_event(event)
|
| 259 |
txt = (event.raw_text or "").strip()
|
| 260 |
-
logger.info("EVNT| pesan baru dari %s
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
|
| 262 |
found = find_first_ca(txt)
|
| 263 |
if not found:
|
|
@@ -283,18 +215,13 @@ async def _worker():
|
|
| 283 |
ca_queue.task_done()
|
| 284 |
|
| 285 |
async def start_bot_background():
|
| 286 |
-
logger.info(
|
| 287 |
-
"BOOT| starting bot (sources=%s, target=%s, dedup=%d min, log=%s)",
|
| 288 |
-
",".join([_normalize_chat(s) for s in SOURCE_CHATS]), TARGET_CHAT, DEDUP_TTL_MINUTES, LOG_LEVEL
|
| 289 |
-
)
|
| 290 |
await client.start()
|
| 291 |
asyncio.create_task(_worker())
|
| 292 |
|
| 293 |
async def stop_bot():
|
| 294 |
-
logger.info("BOOT| stopping bot…")
|
| 295 |
await client.disconnect()
|
| 296 |
|
| 297 |
-
# Optional: quick runner for manual testing
|
| 298 |
if __name__ == "__main__":
|
| 299 |
async def _main():
|
| 300 |
await start_bot_background()
|
|
|
|
| 4 |
from telethon.sessions import StringSession
|
| 5 |
from telethon.errors.rpcerrorlist import FloodWaitError
|
| 6 |
|
|
|
|
| 7 |
# ========= LOGGING =========
|
| 8 |
LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO").upper()
|
| 9 |
logger = logging.getLogger("midastouch-bot")
|
|
|
|
| 23 |
API_HASH = os.environ.get("API_HASH", "")
|
| 24 |
STRING_SESSION = os.environ.get("STRING_SESSION", "")
|
| 25 |
|
|
|
|
| 26 |
SOURCE_CHATS_ENV = os.environ.get("SOURCE_CHATS", "@KOLscopeAlertsBot")
|
| 27 |
SOURCE_CHATS = [s.strip() for s in SOURCE_CHATS_ENV.split(",") if s.strip()]
|
| 28 |
TARGET_CHAT = os.environ.get("TARGET_CHAT", "@solana_trojanbot").strip()
|
|
|
|
| 35 |
RETRY_MAX_ATTEMPTS = int(os.environ.get("RETRY_MAX_ATTEMPTS", "12"))
|
| 36 |
RETRY_SLEEP_SECONDS = float(os.environ.get("RETRY_SLEEP_SECONDS", "1.2"))
|
| 37 |
RETRY_OVERALL_TIMEOUT = float(os.environ.get("RETRY_OVERALL_TIMEOUT", "60"))
|
| 38 |
+
DEDUP_TTL_MINUTES = int(os.environ.get("DEDUP_TTL_MINUTES", "600")) # 10 jam
|
| 39 |
+
MIN_MCAP_USD = float(os.environ.get("MIN_MCAP_USD", "70000")) # batas minimal MC
|
| 40 |
|
| 41 |
if not (API_ID and API_HASH and STRING_SESSION):
|
| 42 |
raise RuntimeError("API_ID/API_HASH/STRING_SESSION belum di-set di Secrets.")
|
|
|
|
| 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 |
|
| 50 |
+
BUY_PAT = re.compile(r"(?i)buy")
|
| 51 |
+
RETRY_PAT = re.compile(r"(?i)(" + "|".join(map(re.escape, RETRY_LABELS)) + r")") if RETRY_LABELS else re.compile(r"$^")
|
| 52 |
|
| 53 |
# ========= UTIL =========
|
| 54 |
def _normalize_chat(s: str) -> str:
|
|
|
|
| 89 |
logger.info("CA | ditemukan chain=%s addr=%s", chain, addr)
|
| 90 |
return chain, addr
|
| 91 |
|
| 92 |
+
# ========= MCAP PARSER =========
|
| 93 |
+
_MC_UNITS = {"K": 1e3, "M": 1e6, "B": 1e9}
|
| 94 |
+
_MC_PAT_NUM_THEN_TAG = re.compile(r"(?i)\b\$?\s*([\d][\d.,]*)\s*([KMB])?\s*(?:mcap|mc|market\s*cap)\b")
|
| 95 |
+
_MC_PAT_TAG_THEN_NUM = re.compile(r"(?i)\b(?:mcap|mc|market\s*cap)\s*[:@]?\s*\$?\s*([\d][\d.,]*)\s*([KMB])?\b")
|
| 96 |
+
|
| 97 |
+
def _to_float_num(num_str: str) -> float:
|
| 98 |
+
s = num_str.strip().replace(" ", "").replace("$", "")
|
| 99 |
+
has_dot, has_comma = "." in s, "," in s
|
| 100 |
+
if has_dot and has_comma:
|
| 101 |
+
s = s.replace(",", "")
|
| 102 |
+
elif has_comma and not has_dot:
|
| 103 |
+
s = s.replace(",", ".")
|
| 104 |
+
return float(s)
|
| 105 |
+
|
| 106 |
+
def parse_mcap_usd(text: str) -> Optional[float]:
|
| 107 |
+
if not text:
|
| 108 |
+
return None
|
| 109 |
+
for pat in (_MC_PAT_NUM_THEN_TAG, _MC_PAT_TAG_THEN_NUM):
|
| 110 |
+
m = pat.search(text)
|
| 111 |
+
if m:
|
| 112 |
+
raw_num = m.group(1)
|
| 113 |
+
unit = (m.group(2) or "").upper()
|
| 114 |
+
base = _to_float_num(raw_num)
|
| 115 |
+
mult = _MC_UNITS.get(unit, 1.0)
|
| 116 |
+
return base * mult
|
| 117 |
+
return None
|
| 118 |
+
|
| 119 |
# ========= STATE =========
|
| 120 |
client = TelegramClient(StringSession(STRING_SESSION), API_ID, API_HASH)
|
| 121 |
ca_queue: "asyncio.Queue[str]" = asyncio.Queue()
|
|
|
|
| 122 |
recent_ca_info: dict[str, tuple[float, str]] = {}
|
| 123 |
|
| 124 |
def _is_recent(addr: str) -> bool:
|
|
|
|
| 128 |
def _mark_ca(addr: str, source_label: str):
|
| 129 |
recent_ca_info[addr] = (time.time(), source_label)
|
| 130 |
|
| 131 |
+
# ========= FLOW =========
|
| 132 |
async def refresh_message(msg):
|
| 133 |
return await client.get_messages(msg.chat_id, ids=msg.id)
|
| 134 |
|
|
|
|
| 138 |
msgs = await client.get_messages(chat, limit=8)
|
| 139 |
for m in msgs:
|
| 140 |
if m.id > min_id and m.buttons:
|
|
|
|
| 141 |
return m
|
| 142 |
await asyncio.sleep(interval)
|
|
|
|
| 143 |
return None
|
| 144 |
|
| 145 |
async def click_first_match(msg, pat) -> bool:
|
|
|
|
| 152 |
for j, btn in enumerate(row):
|
| 153 |
label = (getattr(btn, "text", "") or "").strip()
|
| 154 |
if label and pat.search(label):
|
|
|
|
| 155 |
await msg.click(i, j)
|
| 156 |
return True
|
| 157 |
return False
|
| 158 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 159 |
async def buy_flow_with_ca(addr: str):
|
| 160 |
logger.info("FLOW| start buy_flow CA=%s", addr)
|
| 161 |
last = await client.get_messages(TARGET_CHAT, limit=1)
|
| 162 |
baseline_id = last[0].id if last else 0
|
|
|
|
| 163 |
await client.send_message(TARGET_CHAT, addr)
|
|
|
|
|
|
|
| 164 |
msg_kb = await wait_new_keyboard(TARGET_CHAT, min_id=baseline_id, timeout=25)
|
| 165 |
if not msg_kb:
|
| 166 |
+
return
|
| 167 |
+
await click_first_match(msg_kb, BUY_PAT)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
logger.info("DONE| buy_flow selesai untuk CA=%s", addr)
|
| 169 |
|
| 170 |
# ========= ACCEPT / QUEUE =========
|
|
|
|
| 171 |
def _accept_ca(addr: str, current_source: str) -> bool:
|
| 172 |
if not addr:
|
| 173 |
return False
|
| 174 |
if _is_recent(addr):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
return False
|
| 176 |
_mark_ca(addr, current_source)
|
| 177 |
return True
|
| 178 |
|
| 179 |
# ========= EVENT HANDLER =========
|
| 180 |
+
@events.register(events.NewMessage(chats=[_normalize_chat(s) for s in SOURCE_CHATS]))
|
|
|
|
| 181 |
async def on_message_from_sources(event: events.NewMessage.Event):
|
| 182 |
src_label = _label_chat_from_event(event)
|
| 183 |
txt = (event.raw_text or "").strip()
|
| 184 |
+
logger.info("EVNT| pesan baru dari %s", src_label)
|
| 185 |
+
|
| 186 |
+
mc_usd = parse_mcap_usd(txt)
|
| 187 |
+
if mc_usd is not None:
|
| 188 |
+
if mc_usd < MIN_MCAP_USD:
|
| 189 |
+
logger.info("SKIP| MC=%.2f < %.2f → abaikan", mc_usd, MIN_MCAP_USD)
|
| 190 |
+
return
|
| 191 |
+
else:
|
| 192 |
+
logger.info("PASS| MC=%.2f ≥ %.2f → lanjutkan", mc_usd, MIN_MCAP_USD)
|
| 193 |
|
| 194 |
found = find_first_ca(txt)
|
| 195 |
if not found:
|
|
|
|
| 215 |
ca_queue.task_done()
|
| 216 |
|
| 217 |
async def start_bot_background():
|
| 218 |
+
logger.info("BOOT| starting bot (sources=%s, target=%s)", ",".join(SOURCE_CHATS), TARGET_CHAT)
|
|
|
|
|
|
|
|
|
|
| 219 |
await client.start()
|
| 220 |
asyncio.create_task(_worker())
|
| 221 |
|
| 222 |
async def stop_bot():
|
|
|
|
| 223 |
await client.disconnect()
|
| 224 |
|
|
|
|
| 225 |
if __name__ == "__main__":
|
| 226 |
async def _main():
|
| 227 |
await start_bot_background()
|