Files

242 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: telegram-mass-join
description: Mass join Telegram chats/groups across hundreds of accounts — filtering, distribution, async batch processing, and StringSession deadlock fix.
version: 1.1
source: agent-created
tags: [telegram, mass-join, chats, groups, рассылка, telethon, sessions]
triggers:
- "вступи в чаты"
- "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
```bash
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`:
```python
# 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:
```python
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:
```bash
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:
```python
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()`:
```python
# 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`:
```bash
# 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:
```bash
python merge_joined.py
```
### 11. Reset Progress Before Rerun
If assignments changed or you want a fresh start, delete ALL progress files:
```bash
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:**
```python
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