mirror of
https://github.com/xCyanGrizzly/DragonsStash.git
synced 2026-05-11 06:11:15 +00:00
add TG skill
This commit is contained in:
301
.claude/skills/tdlib-telegram/SKILL.md
Normal file
301
.claude/skills/tdlib-telegram/SKILL.md
Normal file
@@ -0,0 +1,301 @@
|
||||
---
|
||||
name: tdlib-telegram
|
||||
description: >
|
||||
Reference guide for building Telegram integrations with TDLib (tdl/node).
|
||||
Covers rate limits, FLOOD_WAIT handling, file size constraints, bot vs user account
|
||||
differences, and safe code patterns. Use this skill whenever writing or modifying code
|
||||
that calls Telegram APIs via TDLib, the Bot API, or any Telegram client library — including
|
||||
sending messages, downloading/uploading files, scanning channels, forwarding messages,
|
||||
managing subscriptions, or handling notifications. Also use when debugging 429 errors,
|
||||
FLOOD_WAIT, or silent message drops.
|
||||
---
|
||||
|
||||
# TDLib / Telegram Development Guide
|
||||
|
||||
This skill provides the rate limits, constraints, and patterns you need to write correct
|
||||
Telegram integrations. The limits below come from official Telegram documentation and
|
||||
well-established community findings (Telegram does not publish exact numbers for all limits).
|
||||
|
||||
## Telegram Rate Limits
|
||||
|
||||
These are approximate safe boundaries. Telegram's actual limits are dynamic and depend on
|
||||
account age, history, and request type. The correct strategy is to respect these as guidelines
|
||||
and always handle FLOOD_WAIT errors gracefully.
|
||||
|
||||
### Bot Accounts
|
||||
|
||||
| Operation | Limit | Notes |
|
||||
|-----------|-------|-------|
|
||||
| Messages to same chat | ~1 msg/sec | Bursts OK, sustained exceeds limit |
|
||||
| Messages in a group | 20 msgs/min | Hard limit per group chat |
|
||||
| Bulk notifications (different users) | ~30 msgs/sec | Global across all chats |
|
||||
| Message edits in a group | ~20 edits/min | Community-observed |
|
||||
| API requests (global) | ~30 req/sec | All request types combined |
|
||||
| Paid broadcasts | up to 1000 msgs/sec | Requires Telegram Stars balance |
|
||||
|
||||
### User Accounts (TDLib)
|
||||
|
||||
| Operation | Limit | Notes |
|
||||
|-----------|-------|-------|
|
||||
| API requests (global) | ~30 req/sec | All request types combined |
|
||||
| Messages in a group | ~20 msgs/min | Same as bot |
|
||||
| Channel history reads | No published limit | But pagination + delay is essential |
|
||||
| Joining groups | Very strict | FLOOD_WAIT often 30-300+ seconds |
|
||||
|
||||
### File Size Limits
|
||||
|
||||
| Context | Upload | Download |
|
||||
|---------|--------|----------|
|
||||
| Bot API (standard) | 50 MB | 20 MB |
|
||||
| Bot API (local server) | 2,000 MB | 2,000 MB |
|
||||
| User account (TDLib) | 2 GB | 2 GB |
|
||||
| Premium user (TDLib) | 4 GB | 4 GB |
|
||||
|
||||
### Message & Content Limits
|
||||
|
||||
| Item | Limit |
|
||||
|------|-------|
|
||||
| Message text length | 4,096 chars |
|
||||
| Media caption | 1,024 chars (4,096 premium) |
|
||||
| Album / media group | 10 items max |
|
||||
| Forwarded messages per request | `forwarded_message_count_max` (TDLib option) |
|
||||
| Inline keyboard buttons | 100 entities |
|
||||
| Formatting entities per message | 100 |
|
||||
| Scheduled messages per chat | 100 |
|
||||
| Bot commands | 100 max |
|
||||
|
||||
### Forum & Group Limits
|
||||
|
||||
| Item | Limit |
|
||||
|------|-------|
|
||||
| Topics per group | 1,000,000 |
|
||||
| Topic title | 128 chars |
|
||||
| Group members | 200,000 |
|
||||
| Admins per group | 50 |
|
||||
| Bots per group | 20 |
|
||||
| Pinned topics | 5 |
|
||||
|
||||
## FLOOD_WAIT — How It Works
|
||||
|
||||
When you exceed rate limits, Telegram returns a `FLOOD_WAIT_X` error (or HTTP 429 with
|
||||
`retry_after`). This is a **mandatory pause** — the value `X` is the number of seconds you
|
||||
must wait before ANY request will succeed. It blocks the entire client, not just the
|
||||
operation that triggered it.
|
||||
|
||||
### The Right Way to Handle It
|
||||
|
||||
```typescript
|
||||
// Extract the wait duration from the error
|
||||
function extractFloodWaitSeconds(err: unknown): number | null {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
|
||||
// Pattern 1: FLOOD_WAIT_30
|
||||
const flood = message.match(/FLOOD_WAIT_(\d+)/i);
|
||||
if (flood) return parseInt(flood[1], 10);
|
||||
|
||||
// Pattern 2: "retry after 30"
|
||||
const retry = message.match(/retry after (\d+)/i);
|
||||
if (retry) return parseInt(retry[1], 10);
|
||||
|
||||
// Pattern 3: HTTP 429 without explicit seconds
|
||||
if (String((err as any)?.code) === "429") return 30;
|
||||
|
||||
return null; // Not a rate limit error
|
||||
}
|
||||
|
||||
// Wrap any TDLib call with automatic retry
|
||||
async function withFloodWait<T>(fn: () => Promise<T>, maxRetries = 5): Promise<T> {
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await fn();
|
||||
} catch (err) {
|
||||
const wait = extractFloodWaitSeconds(err);
|
||||
if (wait === null || attempt >= maxRetries) throw err;
|
||||
|
||||
// Add 1-5s jitter to prevent thundering herd
|
||||
const jitter = 1000 + Math.random() * 4000;
|
||||
await sleep(wait * 1000 + jitter);
|
||||
}
|
||||
}
|
||||
throw new Error("Unreachable");
|
||||
}
|
||||
```
|
||||
|
||||
### Key Rules
|
||||
|
||||
- **Always respect the wait duration.** Never retry before `retry_after` expires.
|
||||
- **Add jitter.** Without it, multiple clients retry simultaneously and trigger another FLOOD_WAIT.
|
||||
- **Non-rate-limit errors should fail fast.** Only retry on FLOOD_WAIT, not on other errors.
|
||||
- **Don't artificially throttle below ~1 req/sec.** Telegram's own guidance (via grammY docs)
|
||||
is to send requests as fast as you need and handle 429 errors. Fixed low-frequency throttling
|
||||
wastes throughput without preventing floods.
|
||||
|
||||
## Code Patterns
|
||||
|
||||
### Pattern: Sequential Send Queue
|
||||
|
||||
When sending notifications to multiple users, use a sequential queue with a per-message delay.
|
||||
Never fire concurrent sends — you will hit the 30 msg/sec global limit instantly.
|
||||
|
||||
```typescript
|
||||
let sendQueue: Promise<void> = Promise.resolve();
|
||||
|
||||
function queueSend(chatId: bigint, text: string): void {
|
||||
sendQueue = sendQueue
|
||||
.then(() => withFloodWait(() => sendTextMessage(chatId, text)))
|
||||
.then(() => sleep(50)) // ~20 msgs/sec, well under 30 limit
|
||||
.catch((err) => log.error({ err, chatId }, "Send failed"));
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern: Paginated Scanning with Delay
|
||||
|
||||
When reading channel history or enumerating topics, always add a delay between pages:
|
||||
|
||||
```typescript
|
||||
while (hasMorePages) {
|
||||
const result = await invokeWithTimeout(client, { _: "getChatHistory", ... });
|
||||
processMessages(result.messages);
|
||||
|
||||
if (result.messages.length < limit) break;
|
||||
|
||||
await sleep(1000); // 1 second between pages — prevents FLOOD_WAIT on large channels
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern: Event Listener Before Action
|
||||
|
||||
When waiting for TDLib async events (upload confirmation, download completion), always
|
||||
attach the event listener BEFORE starting the operation. If you attach after, fast
|
||||
operations can complete before the listener exists, causing the promise to hang forever.
|
||||
|
||||
```typescript
|
||||
// CORRECT: listener first, then action
|
||||
client.on("update", handleUpdate);
|
||||
const tempMsg = await client.invoke({ _: "sendMessage", ... });
|
||||
tempMsgId = tempMsg.id; // handler now knows which message to match
|
||||
|
||||
// WRONG: action first, then listener — race condition!
|
||||
const tempMsg = await client.invoke({ _: "sendMessage", ... });
|
||||
client.on("update", handleUpdate); // may miss updateMessageSendSucceeded
|
||||
```
|
||||
|
||||
### Pattern: Download/Upload Timeouts
|
||||
|
||||
Scale timeouts with file size. TDLib downloads/uploads are asynchronous — without a timeout,
|
||||
a stalled transfer hangs the entire pipeline.
|
||||
|
||||
```typescript
|
||||
const timeoutMs = Math.max(
|
||||
10 * 60_000, // minimum 10 minutes
|
||||
(fileSizeMB / 1024) * 10 * 60_000 // 10 minutes per GB
|
||||
);
|
||||
```
|
||||
|
||||
### Pattern: TDLib Client Lifecycle
|
||||
|
||||
Always close TDLib clients in a `finally` block. Unclosed clients leak memory and file
|
||||
descriptors, and can leave TDLib's internal database locked.
|
||||
|
||||
```typescript
|
||||
const client = await createTdlibClient(account);
|
||||
try {
|
||||
// ... use client ...
|
||||
} finally {
|
||||
await closeTdlibClient(client);
|
||||
}
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
### Never: Concurrent TDLib Sends Without Queue
|
||||
|
||||
```typescript
|
||||
// BAD: fires all sends concurrently — will trigger FLOOD_WAIT immediately
|
||||
await Promise.all(users.map((u) => sendTextMessage(u.chatId, msg)));
|
||||
|
||||
// GOOD: sequential with delay
|
||||
for (const user of users) {
|
||||
await withFloodWait(() => sendTextMessage(user.chatId, msg));
|
||||
await sleep(50);
|
||||
}
|
||||
```
|
||||
|
||||
### Never: Bare client.invoke() Without Retry
|
||||
|
||||
Every `client.invoke()` call can return FLOOD_WAIT at any time. Bare calls will crash
|
||||
on rate limits instead of retrying.
|
||||
|
||||
```typescript
|
||||
// BAD: crashes on FLOOD_WAIT
|
||||
await client.invoke({ _: "sendMessage", ... });
|
||||
|
||||
// GOOD: retries automatically
|
||||
await withFloodWait(() => client.invoke({ _: "sendMessage", ... }));
|
||||
```
|
||||
|
||||
### Never: Retry Without Respecting retry_after
|
||||
|
||||
```typescript
|
||||
// BAD: fixed 1-second retry ignores Telegram's wait requirement
|
||||
catch (err) { await sleep(1000); retry(); }
|
||||
|
||||
// GOOD: extract and respect the actual wait time
|
||||
catch (err) {
|
||||
const wait = extractFloodWaitSeconds(err);
|
||||
if (wait !== null) await sleep(wait * 1000 + jitter);
|
||||
else throw err;
|
||||
}
|
||||
```
|
||||
|
||||
### Never: Ignore FLOOD_WAIT in Bots
|
||||
|
||||
Bot accounts get the same FLOOD_WAIT as user accounts. The bot API's 429 response
|
||||
blocks ALL operations for the specified duration — not just the chat that triggered it.
|
||||
A single unhandled flood in a notification loop can make the entire bot unresponsive.
|
||||
|
||||
## Bot vs User Account Differences
|
||||
|
||||
| Capability | Bot | User (TDLib) |
|
||||
|-----------|-----|-------------|
|
||||
| Read channel history | No (unless admin) | Yes |
|
||||
| Send to users who haven't started bot | No | N/A |
|
||||
| Join groups via invite link | No (must be added) | Yes |
|
||||
| Forward messages (send_copy) | Yes | Yes |
|
||||
| File upload limit | 50 MB (standard API) | 2 GB |
|
||||
| File download limit | 20 MB (standard API) | 2 GB |
|
||||
| Auth method | Bot token | Phone + SMS code |
|
||||
| Rate limit profile | Same FLOOD_WAIT | Same FLOOD_WAIT |
|
||||
|
||||
## TDLib-Specific Notes
|
||||
|
||||
### BigInt Chat IDs
|
||||
|
||||
TDLib uses numeric chat IDs. Supergroups and channels use negative IDs (e.g., `-1001234567890`).
|
||||
When passing to `client.invoke()`, convert with `Number(chatId)` — TDLib's JSON interface
|
||||
doesn't handle BigInt. Be aware that very large IDs may lose precision with `Number()`,
|
||||
though current Telegram IDs are within safe integer range.
|
||||
|
||||
### TDLib Options (Runtime Queryable)
|
||||
|
||||
These are read-only values you can query at runtime via `getOption`:
|
||||
- `message_text_length_max` — max message text length
|
||||
- `message_caption_length_max` — max caption length
|
||||
- `forwarded_message_count_max` — max forwards per request
|
||||
|
||||
### Session State
|
||||
|
||||
TDLib persists session state to disk. Each account needs its own state directory.
|
||||
Running two clients on the same state directory simultaneously will corrupt the database.
|
||||
Use separate directories per account, and separate volumes in Docker for worker vs bot.
|
||||
|
||||
## Docker Considerations
|
||||
|
||||
- **prebuilt-tdlib**: The `prebuilt-tdlib` npm package provides platform-specific TDLib
|
||||
binaries. Container base image must match (e.g., `node:20-bookworm-slim` for Debian x64).
|
||||
- **Volumes**: Mount persistent volumes for TDLib state directories — losing state forces
|
||||
full re-authentication.
|
||||
- **Graceful shutdown**: Wait for active operations to finish before closing DB connections.
|
||||
TDLib operations in flight will fail if the database pool is closed underneath them.
|
||||
- **Health checks**: TDLib services don't expose HTTP — use database connectivity as the
|
||||
health signal instead.
|
||||
23
.claude/skills/tdlib-telegram/evals/evals.json
Normal file
23
.claude/skills/tdlib-telegram/evals/evals.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"skill_name": "tdlib-telegram",
|
||||
"evals": [
|
||||
{
|
||||
"id": 1,
|
||||
"prompt": "Add a new bot command /broadcast that sends a text message to ALL users who have a TelegramLink in the database. The admin triggers it from the web app. Add it to the bot's command handler and create an API endpoint that triggers it.",
|
||||
"expected_output": "Code that uses a sequential send queue with withFloodWait wrapping each sendTextMessage call, a delay between sends (~50ms), and does NOT use Promise.all or concurrent sends. Should handle errors per-user without stopping the broadcast.",
|
||||
"files": []
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"prompt": "The worker keeps crashing with 'FLOOD_WAIT_35' errors when scanning a source channel that has about 10,000 messages. It happens during the getChannelMessages pagination loop. How do I fix this?",
|
||||
"expected_output": "Diagnosis that the apiDelayMs between pages may be too low or the retry logic isn't wrapping the pagination calls. Should recommend ensuring all getChatHistory/searchChatMessages calls go through withFloodWait/invokeWithTimeout, and that sleep(config.apiDelayMs) exists between pages. Should NOT suggest reducing scan frequency as the primary fix.",
|
||||
"files": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"prompt": "Write a utility function for the worker that downloads a file from a source Telegram channel message and re-uploads it to the destination channel. It needs to handle files up to 2GB and work reliably in Docker.",
|
||||
"expected_output": "Code that: (1) wraps download in withFloodWait with size-scaled timeout, (2) attaches upload event listener BEFORE calling sendMessage, (3) uses temp directory with cleanup in finally block, (4) handles the 2GB Telegram limit correctly, (5) uses try/finally for client cleanup if applicable.",
|
||||
"files": []
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user