Files

13 KiB
Raw Permalink Blame History

name, description, version, source, tags, triggers
name description version source tags triggers
telegram-mass-join Mass join Telegram chats/groups across hundreds of accounts — filtering, distribution, async batch processing, and StringSession deadlock fix. 1.1 agent-created
telegram
mass-join
chats
groups
рассылка
telethon
sessions
вступи в чаты
mass join
джойн
рассылка
smart_join
join_assigned
telegram join
telethon join
вступить в группы
telegram accounts join
загнать акки
по рефке
ref farming
mass checkin

Telegram Mass Join

Массовое вступление в Telegram-чаты/группы через сотни аккаунтов. Полный пайплайн: фильтрация чатов → распределение по аккаунтам → асинхронный джойн с таймаутами. Пункты 12-14 pitfall'ов покрывают массовые БОТ-флоу (рефки, чекины, фарм поинтов) тем же пулом сессий.

Quick Start

cd /c/Users/User/Desktop/_PROJECTS/рассылка

# 1. Filter chats (if needed)
python expand_assign.py

# 2. Reset progress and run (single process)
rm -f assigned_join_progress.json
python -u join_assigned.py 2>&1

# 3. OR: parallel on 3 processes (3x speed)
rm -f assigned_join_progress*.json
python -u join_assigned.py --start 0 --end 58 --suffix _1 --timeout 120 --pause 15 &
python -u join_assigned.py --start 58 --end 116 --suffix _2 --timeout 120 --pause 15 &
python -u join_assigned.py --start 116 --end 174 --suffix _3 --timeout 120 --pause 15 &

Architecture

Phase 1: Chat Filtering (expand_assign.py)

  • Source: good_chats_for_mailing.json (8,572 chats from broader filter)
  • Filter: Two keyword lists — good_kw (барахолки, объявления, услуги, чаты, работа...) and bad_kw (кликеры, crypto, NSFW, каналы, боты...)
  • Size: Only 3K–100K participants
  • Fallback: Score ≥ 11 passes even without keyword match
  • Output: account_chat_assignments.json with account_N keys, all_good_usernames.txt

Phase 2: Join Execution (join_assigned.py)

  • Loads assignments, OK accounts (deduplicated), StringSessions
  • Async batch processing via asyncio.gather (BATCH_SIZE=5)
  • Per-account hard timeout: 120s (60s with 50 chats = hard_timeout for all)
  • Per-chat timeouts: 10s for get_entity, 10s for JoinChannelRequest
  • 15s pause between batches (was 30s, lowered for speed — no rate limit issues)
  • Saves progress to assigned_join_progress{suffix}.json every batch
  • Saves joined list to joined_chats{suffix}.json every 10 batches

Critical Pitfalls

1. SQLite Session Deadlock → USE STRING SESSION

THE ROOT CAUSE of all hangs. SQLite sessions deadlock on database is locked when accessed in parallel. DO NOT add retry logic — convert to StringSession instead.

Use session_converter.py:

# Convert all .session files to StringSession
python session_converter.py

Output goes to generated/sessions_strings.json.

2. Account Deduplication

revalidate_all.json can have duplicate entries (381 OK but only 174 unique names). Always deduplicate:

seen = set()
ok_accounts = []
for r in reval["results"]:
    if r.get("status") == "OK" and r["name"] not in seen:
        seen.add(r["name"])
        ok_accounts.append(r)

3. Progress File Poisoning

The assigned_join_progress.json survives between runs. If you change assignments or want a fresh start, DELETE it:

rm -f assigned_join_progress.json

Otherwise join_assigned.py resumes from the saved batch number and skips accounts.

4. Round-Robin Distribution

The assignment uses round-robin: account i gets chats at positions i, i+N, i+2N, ... where N = total_accounts. This ensures top accounts get the best chats (position 0, 1, 2...), but ALL accounts get a mix of quality levels.

5. Index-Based Lookup

Assignment keys are account_N (1-indexed). The join script maps them to sorted OK accounts:

global_idx = i + idx
acc_key = f"account_{global_idx + 1}"
acc_chats = assignments.get(acc_key, [])

6. Path Handling on Windows

  • Use C:\Users\User\... for Python Path() objects
  • Use /c/Users/User/... for bash commands
  • write_file tool may resolve to C:\c\Users\... — use mcp_filesystem_write_file for Windows paths instead

7. Chat Count vs Timeout Tradeoff

With 120s timeout, 50 chats/account works (~2.5s per join). Some accounts still hit hard_timeout depending on network speed. The sweet spot depends on timeout:

  • 60s timeout → 5-10 chats max
  • 120s timeout → 30-50 chats works for ~25% of accounts
  • timeout_connect (~20% of accounts) — DC/network issue, not retryable
  • Remaining 40% hard_timeout — slow connections, try fewer chats

8. Python global Keyword Placement

Declare global BEFORE any reference to the variable in the function scope — including default parameter values in add_argument():

# WRONG — SyntaxError: name 'ACCOUNT_TIMEOUT' is used prior to global declaration
async def main():
    parser.add_argument("--timeout", default=ACCOUNT_TIMEOUT)  # references before global
    global ACCOUNT_TIMEOUT

# RIGHT — global declaration comes first
async def main():
    global ACCOUNT_TIMEOUT, BATCH_PAUSE
    parser.add_argument("--timeout", default=ACCOUNT_TIMEOUT)

9. Em-Dash (—) in Docstrings

Em-dash — (U+2014) causes SyntaxError: invalid character in some Python environments (git-bash/MSYS). Replace with -- in all docstrings and comments in scripts that run under bash on Windows.

10. Parallel Multi-Process Execution — ⚠️ Race Condition on Shared File

For 3x speedup, split accounts across N parallel processes using --start/--end/--suffix:

# 3 parallel processes on 174 accounts
python -u join_assigned.py --start 0 --end 58 --suffix _1 --timeout 120 --pause 15 &
python -u join_assigned.py --start 58 --end 116 --suffix _2 --timeout 120 --pause 15 &
python -u join_assigned.py --start 116 --end 174 --suffix _3 --timeout 120 --pause 15 &

Each process gets its own progress file AND its own joined_chats file:

  • assigned_join_progress_1.json + joined_chats_1.json
  • assigned_join_progress_2.json + joined_chats_2.json
  • assigned_join_progress_3.json + joined_chats_3.json

CRITICAL: DO NOT share joined_chats.json between processes. They overwrite each other's data (47/81 joins lost in one run). After all finish, merge:

python merge_joined.py

11. Reset Progress Before Rerun

If assignments changed or you want a fresh start, delete ALL progress files:

rm -f assigned_join_progress*.json joined_chats_*.json

12. Reply Keyboards vs Inline Buttons in Bot Flows

When the same account pool is used to drive BOTS (not channels), detect button type first: plain reply-keyboard buttons (KeyboardButton, no data/url) are NOT clickable via Telethon — send their exact text as a message (send_message(bot, '📋 Check-in')). Inline buttons with data=None still click via msg.click(text=...). Log type(btn).__name__ during the 1-account probe before writing the mass runner.

13. Probe Before Mass-Run; Don't Trust the Stated Flow

Before running any bot flow over hundreds of accounts, probe 1-3 accounts through the FULL path (start → language → menu → target action → reply texts) and verify each claimed gate actually exists — e.g. a 'subscribe to channel' step the user expects may not exist in the bot at all. Writing join/recheck logic for a non-existent gate wastes the whole run.

15. Bot Gates Appear Mid-Life — Re-Probe Every Re-Run

A flow that had no channel-subscribe gate yesterday can have one today (owner flips it server-side). Re-probe 1 account on every re-run, and treat a cluster of unmatched/None results as a possible NEW gate — read one raw reply before assuming timeouts. Gate recipe (join → Verify → retry, ChannelsTooMuchError slot freeing, URL from callback answer): references/bot-ref-checkin-flow.md.

14. Mass-Runner Shape (session pool from inventory.json)

Pool: C:/Users/User/tmp/tg_session_ops/inventory.json (name → {file, status, id, phone}) covering Desktop/CLEAN_ALIVE_SESSIONS; filter status=='LIVE' + file exists (~360 accounts). Runner: asyncio.Semaphore(8), chunks of ~40 with 2-4s pause, asyncio.wait_for(connect, timeout=22), progress JSON + JSONL log written after EVERY account (resume = if name in prog: return). Expect ~10% unauth and ~5-10% connect timeouts (bad DC) — non-retryable, skip. Second pass: delete progress entries with None result state and re-run to resolve unknowns ('Already Checked In' = counted). ValueError: No user has "X" as username = account can't see the bot — skip, not retryable. Launch: env -u PYTHONPATH C:/Users/User/AppData/Local/Programs/Python/Python311/python.exe -u script.py background + log file; poll progress JSON, not the log tail.

File Reference

File Purpose
good_chats_for_mailing.json 8,572 filtered chats (source)
account_chat_assignments.json account_N → list of chat objects
revalidate_all.json All accounts with status (OK/DEAD)
generated/sessions_strings.json Phone → StringSession mappings
assigned_join_progress.json Progress: batch_num, total_joined
joined_chats.json Accumulated joined chats with metadata (merge target)
joined_chats_1..3.json Per-process joined chats (merge sources)
all_good_usernames.txt Plain username list for import
expand_assign.py Filtering + assignment generator
join_assigned.py Async join executor
merge_joined.py Deduplicate per-process files into joined_chats.json
session_converter.py SQLite → StringSession converter

Typical Results

  • 8,572 chats → assignments on 174 accounts
  • 50 chats/account × 174 accounts = 8,700 slots
  • ~177M total audience reach
  • Per 58-account process: ~25% ok, ~40% hard_timeout, ~20% timeout_connect, ~15% no_stringsession
  • 28-36 new joins per 58-account process with 120s timeout
  • ~81 new joins total across 3 parallel processes (but race condition loses ~58% on shared file — use per-process files + merge!)
  • 0 flood, 0 bans — safe for mass operations

Addlist Folder Parsing Workflow

When the user asks to add more chats via Telegram folders (t.me/addlist/...), first extract chat usernames from folders before joining or assigning.

Working pattern:

from telethon.tl.functions.chatlists import CheckChatlistInviteRequest

result = await asyncio.wait_for(
    client(CheckChatlistInviteRequest(slug=slug)),
    timeout=15,
)
usernames = [c.username for c in result.chats if getattr(c, "username", None)]

Critical pitfall: Do not iterate result.peers and call client.get_entity(peer) for every peer. It can hang on the first folder under Telethon/Windows. CheckChatlistInviteRequest already returns result.chats; read usernames directly from that list.

Safe pipeline:

  1. Parse addlist.txt / addlist_urls.txt into slugs.
  2. Use a StringSession account and CheckChatlistInviteRequest(slug=...) with a hard timeout.
  3. Save raw usernames to addlist_chats.txt + addlist_chats.json.
  4. Deduplicate against good_chats_for_mailing.json and current account_chat_assignments.json.
  5. Apply a conservative username filter: drop crypto/airdrop/casino/bot/test/flood/spam; keep chat/work/rabota/job/barahol/obyav/uslug/arend/market/ads/etc.
  6. Backup account_chat_assignments.json before appending.
  7. Distribute filtered folder chats round-robin across account_N assignment keys.
  8. Verify with wc -l, JSON counts, min/max per account, and file sizes before saying done.

Known result example: 1121 addlist folders → 1062 OK folders → 28,478 raw unique usernames → 21,807 net-new vs existing → 6,894 filtered high-signal chats appended; assignments grew 8,572 → 15,466 slots, 88–90 chats/account.

Common Error Types

  • bad_CHANNEL_PRIVATE — chat was deleted or made private
  • bad_USERNAME_NOT_OCCUPIED — username changed or doesn't exist
  • hard_timeout — account took >60s to process (usually connection issue)
  • no_stringsession — account has no StringSession (needs conversion)
  • flood_N — rate limited for N seconds (auto-waits, max 60s)

Reference Docs

  • references/bot-ref-checkin-flow.md — Mass bot flow (ref + daily check-in) over the CLEAN_ALIVE_SESSIONS pool: probe-first, reply-keyboard driving, runner params, second-pass retry
  • references/chat-filtering-pipeline.md — Full keyword lists, filtering logic, results breakdown
  • references/stringsession-conversion.md — SQLite deadlock root cause, converter script, pitfalls
  • references/join-executor-patterns.md — Async flow, error types, account statuses, performance data
  • references/parallel-multi-process-execution.md — CLI args, 3-process launch pattern, timeout tuning, Python pitfalls, race condition + merge fix
  • scripts/merge_joined.py — Deduplicate per-process joined_chats files into one