6.7 KiB
Mass Bot Ref + Check-in Flow (session pool)
Recipe for running a points-bot flow (start with owner's ref → language → daily check-in) across the whole CLEAN_ALIVE_SESSIONS pool. Proven end-to-end on @gemini_plusv_bot over 362 sessions.
Flow discovery (ALWAYS first)
Probe 1-3 accounts through the full path and capture reply texts + button types before writing the mass runner:
client.send_message(BOT, '/start ' + REF)
msgs = await client.get_messages(BOT, limit=6)
for m in reversed(list(msgs)):
print(m.id, m.out, m.text)
for r in (m.reply_markup.rows if m.reply_markup else []):
for b in r.buttons:
print(type(b).__name__, b.text, getattr(b,'data',None), getattr(b,'url',None))
Key checks during probe:
- Does a language screen appear? (fresh vs returning user marker)
- Is there a channel-subscribe / forced-join gate anywhere in the flow? Many points bots have NONE at first — but gates can be SWITCHED ON DAYS LATER by the bot owner. Re-probe 1 account before every re-run; classify any unmatched reply text before assuming it's a timeout.
- Are menu buttons reply-keyboard (
KeyboardButton) or inline? Reply buttons are driven bysend_message(bot, '<exact button text>'), not clicks.
Channel gate handling (join → Verify → retry)
When check-in replies 'Please join our channel first!' with Join Channel + Verify inline buttons:
- Extract the channel URL by clicking the join button — the callback answer RETURNS the URL as a string (data/url fields are None):
res = await msg.click(text='📢 Join Channel') url = res if isinstance(res, str) and res.startswith('http') else None await client(JoinChannelRequest(await client.get_entity(url)))- Re-fetch messages, click the
✅ Verifyinline button, wait 2-3s - Send the Check-in text AGAIN — gate clears and check-in succeeds
ChannelsTooMuchError: farmed accounts sit at the 500-channel cap. Free a slot by leaving 1-2 unprotected channels, then retry the join in a loop (max ~4 attempts):
dialogs = await client.get_dialogs()
# candidates: broadcast/megagroup entities whose username/title does NOT match KEEP_SUBSTR
# KEEP_SUBSTR = target bot channel + owner's own channels ('ghsghs', 'alstack', 'reform', ...)
await client(LeaveChannelRequest(entity))
Never leave the target channel or the owner's channels — match a keep-list before leaving.
Import pitfall: runner scripts that call asyncio.run(main()) at module level execute on import — importing one to reuse proc() in a probe launches the whole mass run. Guard with if __name__ == '__main__': or probe with a standalone copy.
Mass runner parameters (proven)
- Pool:
tmp/tg_session_ops/inventory.json, filterstatus=='LIVE'+ file exists → ~360 sessions fromDesktop/CLEAN_ALIVE_SESSIONS - API creds:
API_ID=2040, API_HASH='[REDACTED-KEY]'(same file as tg_session_ops/clean_accounts.py) asyncio.Semaphore(8), chunks of 40, 2-4s pause between chunksasyncio.wait_for(client.connect(), timeout=22)— ~10% of pool hits connect TimeoutError (bad DC), non-retryable, skip- Progress dict keyed by session filename, saved after EVERY account → crash-safe resume
- Random 1.5-3s sleeps between bot steps; 0 flood, 0 bans observed at this rate
Per-account step sequence
send_message(BOT, '/start ' + REF)— ref registers on every start, even for already-known users- If latest bot msg has 'English' button →
msg.click(text='🇬🇧 English')(inline, data=None — click works) send_message(BOT, '📋 Check-in')(reply keyboard — send text, not click)- Read last 3 bot msgs; classify by substring: 'Check-in Successful' → ok (+5 pts, regex
Total Points:\s*?(\d+)?), 'Already Checked In' → done (counts)
Result classes and second pass
unauth(~10%): is_user_authorized() False — dead sessionerr: No user has "X" as username(1-2%): account can't resolve the bot (frozen/blocked) — skipcheckin=None(~15% first pass): reply not matched within wait window — delete these progress entries and re-run; most resolve to 'done' on pass 2. If a batch of Nones shares one reply text, it's a NEW GATE, not noise — read the actual reply before retrying.- Corrupt session files raise
ValueError: not enough values to unpackin TelegramClient init — catch per-account, mark unauth, continue. - Final on 362 sessions: 315 ok / 310 check-ins counted; 5 accounts hit the channel gate, 4 resolved via join→Verify→retry
Daily cron automation (check-in resets 00:00 UTC)
Turn the mass runner into a no_agent cron job:
- Wrapper script (separate from the runner): load progress JSON → clear
checkin/points/joined_channelfields on every record (keepref=Trueso the runner skips re-start) →subprocess.run([python, runner], timeout=3600, capture_output)→ print a compact summary (status counter, ok/done/none, points sum, lastDONE.line). The summary IS the delivery — no_agent cron posts stdout verbatim. - Script location rule: Hermes cron
script=rejects absolute paths — the file MUST live in~/AppData/Local/hermes/scripts/and be passed as a bare filename. Copy the wrapper there; keep the mass runner itself anywhere (wrapper calls it by absolute path). - Schedule: fire shortly after the bot's UTC reset (e.g.
10 3 * * *MSK = 00:10 UTC).no_agent=true,deliver='telegram:<owner DM id>',enabled_toolsetsirrelevant for no_agent. - Smoke-test before trusting: run the wrapper manually once in background (
> smoke.log 2>&1; echo EXIT=$? >> smoke.log) and verifyEXIT=0+ summary lines. Same-day re-run is safe — bot answers 'Already Checked In' (counts as done), no double-claim exists. - Wrapper must NOT import the runner module — runners call
asyncio.run(main())at module level (see import pitfall); subprocess isolation is the fix.
Proven artifacts: wrapper AppData/Local/hermes/scripts/gemini_daily_checkin.py, job gemini-bot-daily-checkin (~7 min full cycle, 362 sessions).
Bot economy notes (@gemini_plusv_bot)
- 25 pts on start, +5/day check-in, +10/ref to the ref owner, 50 pts = 1 credit, 1 credit = Gemini Plus 12mo, 4 = Pro 18mo
- Owner's ref link accumulates points; 20 refs = Pro 18mo
- Daily cron over the same pool is set up — see section above
Files
- Runner v2 (with channel-gate handling + slot freeing):
C:/Users/User/tmp/gemini_ref_mass2.py(parameterize BOT/REF for other bots); v1 without gate:gemini_ref_mass.py - Progress:
tmp/gemini_refprog.json, log:tmp/gemini_ref_log.jsonl, run logs:tmp/gemini_ref_run*.log - Gate channel seen on @gemini_plusv_bot:
@ghsghs_5(may rotate — always extract from the Join Channel callback, never hardcode)