[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-70507":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":14,"stars7d":16,"stars30d":17,"stars90d":15,"forks30d":15,"starsTrendScore":16,"compositeScore":18,"rankGlobal":9,"rankLanguage":9,"license":9,"archived":19,"fork":19,"defaultBranch":20,"hasWiki":21,"hasPages":19,"topics":22,"createdAt":9,"pushedAt":9,"updatedAt":23,"readmeContent":24,"aiSummary":25,"trendingCount":15,"starSnapshotCount":15,"syncStatus":13,"lastSyncTime":26,"discoverSource":27},70507,"zalo-tg","williamcachamwri\u002Fzalo-tg","williamcachamwri","A bidirectional message bridge between Zalo and Telegram, implemented in TypeScript on Node.js",null,"TypeScript",245,133,2,1,0,3,61,6.38,false,"main",true,[],"2026-06-12 02:02:34","\u003Cdiv align=\"center\">\n\n# ⚡ zalo-tg\n\n### A production-oriented, stateful interoperability bridge between **Zalo** and **Telegram**\n\n`zalo-tg` transforms Telegram Forum Topics into a structured operational console for Zalo conversations, while preserving message identity, media semantics, replies, reactions, recalls, mentions, polls, and long-lived conversation state.\n\n\u003Cbr \u002F>\n\n[![TypeScript](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FTypeScript-5.x-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](#)\n[![Node.js](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FNode.js-%E2%89%A5_18-339933?style=for-the-badge&logo=node.js&logoColor=white)](#requirements)\n[![Telegram Bot API](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FTelegram-Bot_API-26A5E4?style=for-the-badge&logo=telegram&logoColor=white)](#architecture)\n[![Zalo](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FZalo-Bridge-0068FF?style=for-the-badge&logo=zalo&logoColor=white)](#zalo-authentication)\n[![License](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLicense-Repository_File-111827?style=for-the-badge)](#license)\n\n\u003Cbr \u002F>\n\n[Overview](#-system-overview) •\n[Architecture](#-architecture) •\n[Features](#-capability-surface) •\n[Installation](#-installation) •\n[Configuration](#-configuration) •\n[Commands](#-bot-command-surface) •\n[Security](#-security-model)\n\n\u003Cbr \u002F>\n\n> **Vietnamese documentation:** [README.vi.md](README.vi.md)\n\n\u003C\u002Fdiv>\n\n---\n\n## 📌 Table of Contents\n\n- [System Overview](#-system-overview)\n- [Architectural Principles](#-architectural-principles)\n- [Architecture](#-architecture)\n- [Capability Surface](#-capability-surface)\n- [Message Compatibility Matrix](#-message-compatibility-matrix)\n- [Interaction Synchronisation](#-interaction-synchronisation)\n- [Poll Synchronisation](#-poll-synchronisation)\n- [Group and Topic Lifecycle](#-group-and-topic-lifecycle)\n- [Requirements](#-requirements)\n- [Installation](#-installation)\n- [Configuration](#-configuration)\n- [Running the Bridge](#-running-the-bridge)\n- [Zalo Authentication](#-zalo-authentication)\n- [Large File Transfer](#-large-file-transfer--20-mb)\n- [Bot Command Surface](#-bot-command-surface)\n- [Project Structure](#-project-structure)\n- [Persistent Data Model](#-persistent-data-model)\n- [Security Model](#-security-model)\n- [Contributors](#-contributors)\n- [License](#-license)\n\n---\n\n## 🧭 System Overview\n\n`zalo-tg` is a **bidirectional, state-aware synchronisation layer** that connects the Zalo messaging ecosystem with Telegram through a Telegram bot. Each Zalo conversation—either a direct message or a group conversation—is deterministically represented as an isolated **Telegram Forum Topic** inside a configured Telegram supergroup.\n\nUnlike a conventional relay bot that merely forwards text payloads, this project implements a richer interoperability model. It maintains cross-platform message correlation, reconstructs reply chains, translates media objects, mirrors selected interaction primitives, resolves mentions and aliases, and coordinates poll state between two messaging platforms with fundamentally different event models.\n\n> [!IMPORTANT]\n> `zalo-tg` is designed as an operational bridge, not a stateless message forwarder. Its correctness depends on persisted topic mappings, runtime message indexes, session material, and careful event translation across Zalo and Telegram.\n\n### High-level Behaviour\n\n| Domain | Behaviour |\n|---|---|\n| Conversation mapping | Each Zalo thread maps to one Telegram Forum Topic. |\n| Inbound Zalo events | Zalo messages are decoded, normalised, enriched, then emitted to Telegram. |\n| Inbound Telegram updates | Telegram messages are interpreted, transformed, uploaded, then delivered to Zalo. |\n| Message identity | Zalo and Telegram message identifiers are indexed bidirectionally. |\n| Context preservation | Replies, recalls, reactions, mentions, media albums, and poll updates are reconstructed when enough metadata is available. |\n| Failure posture | Missing historical mappings degrade gracefully instead of breaking the forwarding pipeline. |\n\n---\n\n## 🧠 Architectural Principles\n\n`zalo-tg` is built around a few core engineering principles:\n\n\u003Ctable>\n\u003Ctr>\n\u003Ctd width=\"33%\">\n\n### 🧩 Semantic Preservation\n\nThe bridge attempts to preserve the *meaning* of messages, not only their raw textual content. Attachments, replies, mentions, reactions, locations, contacts, and polls are translated into the closest platform-native representation.\n\n\u003C\u002Ftd>\n\u003Ctd width=\"33%\">\n\n### 🔁 Bidirectional Correlation\n\nEvery supported event direction maintains a correlation layer between Zalo message identifiers and Telegram message identifiers, enabling reply resolution, recall propagation, and contextual reconstruction.\n\n\u003C\u002Ftd>\n\u003Ctd width=\"33%\">\n\n### 🛡️ Graceful Degradation\n\nWhen a mapping, media object, quote target, or metadata fragment cannot be resolved, the system continues forwarding the message while omitting only the unavailable semantic layer.\n\n\u003C\u002Ftd>\n\u003C\u002Ftr>\n\u003C\u002Ftable>\n\n---\n\n## 🏗️ Architecture\n\nThe bridge executes as a single long-lived **Node.js** process. It maintains two concurrently active client layers:\n\n1. A **Telegram Bot API client**, implemented with [`Telegraf`](https:\u002F\u002Fgithub.com\u002Ftelegraf\u002Ftelegraf), using long polling.\n2. A **Zalo client**, implemented with [`zca-js`](https:\u002F\u002Fgithub.com\u002FRFS-ADRENO\u002Fzca-js), connected to Zalo's internal WebSocket interface.\n\nBoth clients communicate through shared runtime stores and persisted metadata. These stores act as the correlation substrate required to translate stateful message semantics between the two platforms.\n\n```mermaid\nflowchart LR\n    ZALO[\"Zalo WebSocket API\"]\n    ZClient[\"src\u002Fzalo\u002Fclient.ts\u003Cbr\u002F>Session lifecycle\u003Cbr\u002F>Web API login\"]\n    LoginApp[\"src\u002Fzalo\u002FloginApp.ts\u003Cbr\u002F>PC App QR login\"]\n    AppApi[\"src\u002Fzalo\u002FappApi.ts\u003Cbr\u002F>PC App API\u003Cbr\u002F>Rate-limit isolation\"]\n    ZHandler[\"src\u002Fzalo\u002Fhandler.ts\u003Cbr\u002F>Zalo event decoder\"]\n    Store[(\"src\u002Fstore.ts\u003Cbr\u002F>Runtime + persistent state\")]\n    THandler[\"src\u002Ftelegram\u002Fhandler.ts\u003Cbr\u002F>Telegram update decoder\"]\n    TBot[\"Telegram Bot API\u003Cbr\u002F>Long polling\"]\n\n    ZALO --> ZClient\n    ZClient --> LoginApp\n    ZClient --> AppApi\n    ZClient --> ZHandler\n    ZHandler --> Store\n    Store --> THandler\n    THandler --> TBot\n    TBot --> THandler\n    THandler --> Store\n    Store --> ZHandler\n    ZHandler --> ZClient\n```\n\n### Runtime State Plane\n\n```mermaid\nflowchart TB\n    Store[\"Central Store\"]\n    Topic[\"topicStore\u003Cbr\u002F>Zalo conversation ↔ Telegram topic\"]\n    Msg[\"msgStore\u003Cbr\u002F>Zalo message ↔ Telegram message\"]\n    Sent[\"sentMsgStore\u003Cbr\u002F>Telegram-originated reverse index\"]\n    Poll[\"pollStore\u003Cbr\u002F>Poll metadata + score message\"]\n    Media[\"mediaGroupStore\u003Cbr\u002F>Telegram album buffer\"]\n    Album[\"zaloAlbumStore\u003Cbr\u002F>Zalo album buffer\"]\n    User[\"userCache\u003Cbr\u002F>UID\u002Fdisplay-name lookup\"]\n    Alias[\"aliasCache\u003Cbr\u002F>Local nickname resolution\"]\n    Friends[\"friendsCache\u003Cbr\u002F>5-minute friends TTL\"]\n\n    Store --> Topic\n    Store --> Msg\n    Store --> Sent\n    Store --> Poll\n    Store --> Media\n    Store --> Album\n    Store --> User\n    Store --> Alias\n    Store --> Friends\n```\n\n### State Model\n\nThe topic mapping is persisted in `data\u002Ftopics.json`, ensuring that known Zalo conversations remain attached to stable Telegram Forum Topics across process restarts.\n\nMessage-ID mappings are primarily maintained in memory with LRU-style eviction. A compressed persisted mapping file, `data\u002Fmsg-map.json`, allows reply-chain resolution to survive restarts. If a historical mapping is unavailable, the system deliberately degrades by omitting Telegram `reply_parameters` or Zalo quote metadata rather than failing the forwarding operation.\n\n---\n\n## ✨ Capability Surface\n\n| Capability | Status | Technical Notes |\n|---|:---:|---|\n| Bidirectional message forwarding | ✅ | Zalo ⇄ Telegram event projection. |\n| Forum Topic provisioning | ✅ | Automatic topic creation per Zalo conversation. |\n| Rich media forwarding | ✅ | Photos, albums, videos, GIFs, files, stickers, voice notes, contacts, locations, and selected web content. |\n| Reply-chain preservation | ✅ | Requires available message correlation metadata. |\n| Reaction propagation | ✅ | Emoji compatibility mapping and contextual fallback messages. |\n| Message recall | ✅ | Zalo undo → Telegram deletion; Telegram `\u002Frecall` → Zalo undo. |\n| Poll synchronisation | ✅ | Native polls, score messages, vote propagation, and lock handling. |\n| Mention resolution | ✅ | Display names, Telegram usernames, Zalo UIDs, and aliases. |\n| Rate-limit mitigation | ✅ | Optional PC App API session for selected lookup paths. |\n| Large file transfer | ✅ | Optional local Telegram Bot API server, up to 2 GB. |\n\n---\n\n## 🧾 Message Compatibility Matrix\n\n### Zalo → Telegram\n\n| Zalo message type | Telegram representation | Notes |\n|---|---|---|\n| `webchat` | `sendMessage` | HTML parse mode; Zalo mentions are rendered safely. |\n| `chat.photo` | `sendPhoto` \u002F `sendMediaGroup` | Albums are buffered for 600 ms before emission. |\n| `chat.video.msg` | `sendVideo` | Preserves native video representation. |\n| `chat.gif` | `sendAnimation` | Uses Telegram animation semantics. |\n| `share.file` | `sendDocument` | Retains the original filename. |\n| `chat.voice` | `sendVoice` | Preserves voice-note UX. |\n| `chat.sticker` | `sendSticker` \u002F `sendPhoto` | WebP sticker path with photo fallback for oversized assets. |\n| `chat.doodle` | `sendPhoto` | Rendered as an image asset. |\n| `chat.recommended` | `sendMessage` | Inline link preview. |\n| `chat.location.new` | `sendLocation` | Telegram native map widget. |\n| `chat.webcontent` — bank card | `sendPhoto` | VietQR image plus account metadata. |\n| `chat.webcontent` — generic | `sendMessage` | Icon and label metadata. |\n| Contact card | `sendPhoto` \u002F text fallback | QR code, name, and ID when available. |\n| `group.poll` — create | `sendPoll` + score message | Includes editable score message and inline lock control. |\n| `group.poll` — vote update | Score-message edit | Updated counts with compact bar visualization. |\n\n### Telegram → Zalo\n\n| Telegram content | Zalo operation | Notes |\n|---|---|---|\n| Text | `sendMessage` | Includes mention-resolution pipeline. |\n| Single photo | `sendMessage` with image attachment | Caption participates in mention resolution. |\n| Photo album | `sendMessage` with multiple attachments | Albums are buffered for 500 ms. |\n| Single video | `sendMessage` with video attachment | Native attachment upload. |\n| Video album | `sendMessage` with multiple attachments | Buffered media-group handling. |\n| Animation \u002F GIF | `sendMessage` with attachment | Download and upload pipeline. |\n| Document | `sendMessage` with attachment | Preserves document payload. |\n| Voice note | `sendVoice` | Converts OGG Opus to M4A through `ffmpeg`. |\n| Static WebP sticker | `sendMessage` with attachment | Static sticker forwarding. |\n| Animated\u002Fvideo sticker | Thumbnail attachment | JPEG thumbnail fallback. |\n| Location | `sendLink` \u002F `sendMessage` fallback | Google Maps URL bridge. |\n| Contact | `sendMessage` | Name and phone number serialization. |\n| Poll | `createPoll` | Also creates a bot-owned non-anonymous Telegram clone poll for vote tracking. |\n\n---\n\n## 🔄 Interaction Synchronisation\n\n### Reply Chains\n\nWhen a Telegram message replies to another Telegram message, the bridge attempts to resolve the target message back to a Zalo-compatible quote object. That quote metadata is then passed to `sendMessage`, allowing the forwarded Zalo message to retain conversational context.\n\nFor messages originally sent from Telegram to Zalo, the reverse lookup is performed through `sentMsgStore`, ensuring that replies remain coherent even when the original message did not originate from Zalo.\n\n```mermaid\nsequenceDiagram\n    participant TG as Telegram Topic\n    participant Store as Mapping Store\n    participant Bridge as zalo-tg\n    participant Zalo as Zalo Conversation\n\n    TG->>Bridge: Reply message update\n    Bridge->>Store: Resolve reply target\n    alt Mapping exists\n        Store-->>Bridge: Zalo quote metadata\n        Bridge->>Zalo: sendMessage(text, quote)\n    else Mapping missing\n        Store-->>Bridge: No historical mapping\n        Bridge->>Zalo: sendMessage(text)\n    end\n```\n\n### Reactions\n\nTelegram `message_reaction` updates are mapped through a static emoji compatibility table and forwarded to Zalo with `addReaction`. In the opposite direction, Zalo reactions are represented in Telegram as concise contextual replies so that reaction activity remains visible even when Telegram lacks a one-to-one representation for the source event.\n\n### Message Recall\n\nZalo `undo` events are mirrored by deleting the corresponding Telegram message when a mapping exists. On the Telegram side, the `\u002Frecall` command invokes `api.undo` for messages previously sent by the bot into Zalo.\n\n### Mentions and Aliases\n\nZalo `@mention` spans are rendered on Telegram with safe HTML formatting. Telegram `@username` entities and plain-text `@Name` patterns are resolved to Zalo UIDs through `userCache`.\n\nThe bridge also supports Zalo contact aliases. If a Zalo user has a local nickname configured in the address book, `@Alias` can resolve to the correct UID even when the visible display name differs. Captions attached to photos, videos, and documents participate in the same mention-resolution pipeline.\n\n---\n\n## 🗳️ Poll Synchronisation\n\nPoll synchronisation is implemented as a coordinated state machine rather than a naive forwarding rule. This is necessary because Zalo and Telegram expose materially different poll models, authoring constraints, and vote-update events.\n\n```mermaid\nstateDiagram-v2\n    [*] --> Created\n    Created --> Mirrored: Create native counterpart\n    Mirrored --> Voting: Receive vote event\n    Voting --> Refreshing: Fetch authoritative poll detail\n    Refreshing --> Mirrored: Edit score message\n    Mirrored --> Locked: Close \u002F lock poll\n    Locked --> Finalized: Stop poll + final score update\n    Finalized --> [*]\n```\n\nSupported flows:\n\n| Flow | Implementation |\n|---|---|\n| Zalo poll creation → Telegram | Creates a native Telegram poll and an editable score message. |\n| Telegram poll creation → Zalo | Calls Zalo `createPoll` and creates a bot-owned Telegram clone poll. |\n| Telegram `poll_answer` → Zalo | Calls Zalo `votePoll`, then refreshes score state through `getPollDetail`. |\n| Zalo vote event → Telegram | Handles `group_event` with `boardType=3`, then edits the score message. |\n| Poll closure | Calls Zalo `lockPoll`, Telegram `stopPoll`, and final score-message update. |\n\n> [!NOTE]\n> The bot-owned clone poll is required because Telegram only emits `poll_answer` updates for polls created by the bot itself. This design preserves vote visibility while keeping the user-facing interface native to Telegram.\n\n---\n\n## 🧵 Group and Topic Lifecycle\n\nWhen the bridge observes a new Zalo group conversation, it automatically creates a dedicated Telegram Forum Topic for that conversation. If a group avatar is available, the avatar is fetched and pinned as the first topic message, making the topic immediately recognisable.\n\nGroup lifecycle events—joins, leaves, removals, blocks, and selected administrative updates—are forwarded as italicised system messages inside the corresponding Telegram topic.\n\n```mermaid\nflowchart LR\n    A[\"Observe unknown Zalo group\"] --> B[\"Create Telegram Forum Topic\"]\n    B --> C[\"Persist mapping in data\u002Ftopics.json\"]\n    C --> D[\"Fetch group avatar if available\"]\n    D --> E[\"Pin avatar \u002F identity message\"]\n    E --> F[\"Forward future group events into topic\"]\n```\n\n---\n\n## 📦 Requirements\n\n| Dependency | Required Version | Purpose |\n|---|---:|---|\n| Node.js | `>= 18` | Runtime with native ESM support. |\n| npm | `>= 9` | Dependency installation and script execution. |\n| ffmpeg | Recent version | OGG Opus → M4A conversion for Telegram voice notes. |\n| Telegram Bot | — | Created through [@BotFather](https:\u002F\u002Ft.me\u002FBotFather). |\n| Telegram Supergroup | — | Forum Topics must be enabled. |\n| Zalo account | — | Active account with persisted session material. |\n\n### Required Telegram Administrator Permissions\n\n- Manage topics.\n- Delete messages.\n- Pin messages.\n- Manage the group, including reaction-related update access.\n\n---\n\n## 🚀 Installation\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002Fwilliamcachamwri\u002Fzalo-tg\ncd zalo-tg\nnpm install\ncp .env.example .env\n```\n\nAfter installing dependencies, configure the environment variables in `.env` before starting the bridge.\n\n---\n\n## ⚙️ Configuration\n\nEdit `.env` with the required runtime configuration:\n\n```env\n# Telegram Bot token obtained from @BotFather\nTG_TOKEN=123456789:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n# Telegram supergroup ID. This is a negative integer, for example: -1001234567890\nTG_GROUP_ID=-1001234567890\n\n# Directory for persistent bridge state. Defaults to .\u002Fdata when omitted.\nDATA_DIR=.\u002Fdata\n\n# Skip forwarding messages from muted Zalo groups.\n# Accepted truthy values: true, 1, yes, on\nZALO_SKIP_MUTED_GROUPS=false\n```\n\n### Configuration Reference\n\n| Variable | Required | Default | Description |\n|---|:---:|---|---|\n| `TG_TOKEN` | ✅ | — | Telegram bot token issued by BotFather. |\n| `TG_GROUP_ID` | ✅ | — | Target Telegram supergroup ID with Forum Topics enabled. |\n| `DATA_DIR` | ❌ | `.\u002Fdata` | Directory used for persistent bridge state. |\n| `ZALO_SKIP_MUTED_GROUPS` | ❌ | `false` | Skips forwarding from muted Zalo groups when enabled. |\n| `LOCAL_BOT_API` | ❌ | `0` | Enables local Telegram Bot API mode when set to `1`. |\n| `TG_LOCAL_SERVER` | Conditional | — | Local Bot API base URL. |\n| `TG_API_ID` | Conditional | — | Telegram application API ID for local Bot API setup. |\n| `TG_API_HASH` | Conditional | — | Telegram application API hash for local Bot API setup. |\n\n---\n\n## ▶️ Running the Bridge\n\n### Development Mode\n\n```bash\nnpm run dev\n```\n\nDevelopment mode uses `tsx watch`, enabling hot reload during local iteration.\n\n### Production Mode\n\n```bash\nnpm run build\nnpm start\n```\n\nProduction mode compiles the TypeScript source before starting the Node.js process.\n\n### Recommended Runtime Checklist\n\n- [ ] `.env` is configured.\n- [ ] Telegram bot is added to the target supergroup.\n- [ ] Forum Topics are enabled in the supergroup.\n- [ ] Bot has the required administrator permissions.\n- [ ] Zalo session has been created through `\u002Floginweb` or `\u002Flogin`.\n- [ ] Optional PC App session has been created through `\u002Floginapp`.\n- [ ] `ffmpeg` is available in `PATH` if voice-note bridging is required.\n\n---\n\n## 🔐 Zalo Authentication\n\nThe bridge supports two independent Zalo authentication mechanisms. Either flow can be initiated from the configured Telegram group through bot commands.\n\n### `\u002Floginweb` — Web API Session\n\n`\u002Floginweb` creates a standard `zca-js` Web API session. This is equivalent to the legacy `\u002Flogin` command.\n\n**Procedure**\n\n1. Send `\u002Floginweb` in any topic of the bridged Telegram group.\n2. The bot replies with a Zalo QR code image.\n3. Scan the QR code in the Zalo mobile app through **Settings → QR Code Login**.\n4. The session is persisted to `data\u002Fcredentials.json`.\n\n> [!WARNING]\n> The Web API is subject to endpoint-level rate limits. During startup with many groups, HTTP `221` rate-limit responses may occur. The PC App session provides a separate lookup path for selected operations.\n\n### `\u002Floginapp` — PC App API Session\n\n`\u002Floginapp` creates a Zalo PC App session using the `wpa.zaloapp.com` and `zaloapp.com` cookie domains. This session is stored independently from the Web API session and is primarily used for group-member lookup operations.\n\n**Procedure**\n\n1. Send `\u002Floginapp` in any topic of the bridged Telegram group.\n2. The bot replies with a Zalo QR code.\n3. Scan the QR code in the Zalo mobile app. Zalo treats this as a PC App login.\n4. The session is persisted to `data\u002Fapp-session.json`.\n\nThe stored session includes:\n\n| Field | Description |\n|---|---|\n| `zpw_enk` | Base64-encoded AES session encryption key. |\n| `imei` | Device identifier. |\n| `cookies` | Raw `zaloapp.com` cookie array. |\n\n### Why the PC App Session Matters\n\nThe PC App session allows `populateGroupMemberCache` to query `group-wpa.zaloapp.com` instead of relying exclusively on the Web API. This places member lookup traffic into a different rate-limit bucket and substantially reduces startup failure probability when many groups must be indexed.\n\nThe same session is also used by member-name lookups through `profile-wpa.zaloapp.com\u002Fapi\u002Fsocial\u002Fgroup\u002Fmembers`. If no PC App session is available, the bridge falls back to the Web API automatically.\n\n### Member Cache Population Strategy\n\n| Tier | Source | Additional API Call | Operational Cost |\n|---:|---|:---:|---|\n| 1 | `currentMems` embedded in `getGroupInfo` | No | Lowest |\n| 2 | `profile-wpa.zaloapp.com\u002Fapi\u002Fsocial\u002Fgroup\u002Fmembers` through PC App API | Yes | Isolated rate-limit bucket |\n| 3 | `getUserInfo` through Web API | Yes | Rate-limited fallback |\n\nTiers 2 and 3 are only used for UIDs not already resolved by tier 1, which is typically sufficient for groups below approximately 200 members.\n\n---\n\n## 📁 Large File Transfer &gt; 20 MB\n\nThe official Telegram Bot API imposes restrictive file-size limits for bot downloads and uploads. To support larger transfers—up to **2 GB**—`zalo-tg` can optionally operate against a **local Telegram Bot API server**.\n\n### Quick Start\n\n1. Build or download the local Telegram Bot API server. See [Local Bot API Setup Guide](LOCAL_BOT_API_SETUP.md).\n\n2. Log the bot out of the official Telegram Bot API once:\n\n   ```bash\n   curl \"https:\u002F\u002Fapi.telegram.org\u002Fbot\u003CYOUR_BOT_TOKEN>\u002FlogOut\"\n   ```\n\n3. Start the local Bot API server:\n\n   ```bash\n   telegram-bot-api \\\n     --api-id=\u003CYOUR_API_ID> \\\n     --api-hash=\u003CYOUR_API_HASH> \\\n     --local \\\n     --dir=~\u002Fzalo-tg-bot-api\u002Fdata \\\n     --http-port=8081\n   ```\n\n4. Enable local mode in `.env`:\n\n   ```env\n   LOCAL_BOT_API=1\n   TG_LOCAL_SERVER=http:\u002F\u002Flocalhost:8081\n   TG_API_ID=your_api_id\n   TG_API_HASH=your_api_hash\n   ```\n\n5. Rebuild and restart the bridge:\n\n   ```bash\n   npm run build\n   npm start\n   ```\n\n### `LOCAL_BOT_API` Behaviour\n\n| Value | Behaviour |\n|---|---|\n| `LOCAL_BOT_API=1` | Uses the local server configured by `TG_LOCAL_SERVER`. File transfers can reach up to **2 GB**. |\n| `LOCAL_BOT_API=0` | Uses the official `api.telegram.org` endpoint. File limits follow official Bot API constraints. |\n\nImportant operational details:\n\n- When local mode is enabled, all Telegram Bot API traffic is routed through the configured local server.\n- When local mode is disabled or omitted, `TG_LOCAL_SERVER`, `TG_API_ID`, and `TG_API_HASH` are ignored.\n- Switching between official and local modes requires a logout\u002Flogin cycle.\n- `file_id` values are not portable between official and local Bot API modes.\n- Upload timeouts are computed dynamically from file size, with a 30-second minimum and a 10-minute upper cap.\n\nTo switch from local mode back to the official Telegram Bot API:\n\n```bash\ncurl \"http:\u002F\u002Flocalhost:8081\u002Fbot\u003CYOUR_BOT_TOKEN>\u002FlogOut\"\n```\n\nThen stop the local server and set:\n\n```env\nLOCAL_BOT_API=0\n```\n\n### Local Bot API Advantages\n\n- Supports file transfers up to **2 GB**.\n- Avoids unnecessary download overhead by copying files directly from the local server when possible.\n- Can be enabled or disabled through configuration without changing source code.\n- Preserves compatibility with older official-api `file_id` values through fallback logic.\n- Performs automatic cleanup of local files after successful delivery.\n\nFor platform-specific setup instructions, including macOS, Linux, Windows, systemd, Windows Task Scheduler, and troubleshooting, see [Local Bot API Setup Guide](LOCAL_BOT_API_SETUP.md).\n\n> Vietnamese version: [Hướng dẫn thiết lập Local Bot API](LOCAL_BOT_API_SETUP.vi.md)\n\n---\n\n## 🤖 Bot Command Surface\n\n| Command | Description |\n|---|---|\n| `\u002Flogin` | Starts Zalo QR login through the Web API. Equivalent to `\u002Floginweb`. |\n| `\u002Floginweb` | Starts Zalo QR login through the Web API and persists the session to `credentials.json`. |\n| `\u002Floginapp` | Starts Zalo QR login through the PC App API and persists the session to `app-session.json`. Enables lower-pressure group-member lookups. |\n| `\u002Fsearch \u003Cquery>` | Searches the Zalo friends list and allows the user to create a direct-message topic from a selected result. |\n| `\u002Frecall` | Retracts a message previously sent by the bot from Telegram to Zalo. Must be used as a reply to the target message. |\n| `\u002Ftopic list` | Lists active Telegram-topic-to-Zalo-conversation mappings. |\n| `\u002Ftopic info` | Shows the Zalo conversation metadata associated with the current topic. |\n| `\u002Ftopic delete` | Removes the mapping associated with the current topic. |\n\n---\n\n## 🧬 Project Structure\n\n```text\nsrc\u002F\n├── index.ts                  Application entry point. Initialises Telegraf,\n│                             creates the Zalo client, attaches handlers,\n│                             and starts polling.\n│\n├── config.ts                 Reads, validates, and normalises environment variables.\n│\n├── store.ts                  Centralised runtime and persistent state management:\n│                               - topicStore       persisted topic mappings\n│                               - msgStore         Zalo msgId ↔ Telegram message_id\n│                               - sentMsgStore     Telegram-to-Zalo reverse index\n│                               - pollStore        poll and score-message mappings\n│                               - mediaGroupStore  Telegram media-group buffer\n│                               - zaloAlbumStore   Zalo album buffer\n│                               - userCache        UID and display-name lookup\n│                               - aliasCache       alias-to-UID resolution\n│                               - friendsCache     friends list with 5-minute TTL\n│\n├── telegram\u002F\n│   ├── bot.ts                Telegraf instance configuration, allowed updates,\n│   │                         and bot-command registration.\n│   └── handler.ts            Telegram update processor. Handles text, media,\n│                             voice, stickers, polls, locations, contacts,\n│                             reactions, callback queries, and poll answers.\n│                             Mention resolution uses display names first,\n│                             followed by aliases.\n│\n├── zalo\u002F\n│   ├── client.ts             Zalo API initialisation and Web API QR login.\n│   ├── loginApp.ts           PC App QR login flow and zaloapp.com session storage.\n│   ├── appApi.ts             Direct PC App API helpers for group and member\n│   │                         lookups. Supports AES-128, AES-192, and AES-256\n│   │                         session-key detection.\n│   ├── types.ts              TypeScript interfaces and ZALO_MSG_TYPES constants.\n│   └── handler.ts            Zalo listener processor. Handles message events,\n│                             undo events, reactions, and group events including\n│                             joins, leaves, poll updates, and board updates.\n│\n└── utils\u002F\n    ├── format.ts             HTML escaping, mention application, and caption helpers.\n    └── media.ts              Temporary media download, cleanup, and OGG-to-M4A conversion.\n```\n\n---\n\n## 🗄️ Persistent Data Model\n\n### `data\u002Fcredentials.json`\n\nStores the Zalo **Web API** session created by `\u002Floginweb` or `\u002Flogin`. This file contains authentication material equivalent to account credentials and must be protected accordingly.\n\n### `data\u002Fapp-session.json`\n\nStores the Zalo **PC App** session created by `\u002Floginapp`.\n\n| Field | Purpose |\n|---|---|\n| `zpw_enk` | Base64-encoded AES session key. AES-128, AES-192, and AES-256 are auto-detected by key length. |\n| `imei` | Device identifier. |\n| `cookies` | Raw `zaloapp.com` cookie array. |\n\nThis session is consumed exclusively by `appApi.ts` for calls to `group-wpa.zaloapp.com` and `profile-wpa.zaloapp.com`. The file is listed in `.gitignore` and must be handled with the same care as `credentials.json`.\n\n### `data\u002Ftopics.json`\n\nStores the persistent mapping between each Zalo conversation ID and its Telegram Forum Topic ID. The file also includes conversation metadata such as display name and conversation type. It is written whenever a new topic mapping is created and read once at startup.\n\n### `data\u002Fmsg-map.json`\n\nStores the bidirectional relationship between Zalo message IDs and Telegram message IDs. Despite the `.json` suffix, the file is gzip-compressed and detected through the `0x1F 0x8B` gzip magic bytes at load time.\n\nThe current v2 format uses string interning and positional arrays to reduce I\u002FO overhead and disk usage.\n\n#### v2 Format\n\n```jsonc\n{\n  \"v\": 2,\n  \u002F\u002F String intern table. Repeated strings such as zaloId, msgType, and UID\n  \u002F\u002F are stored once and referenced by index in the arrays below.\n  \"s\": [\"850431…\", \"webchat\", \"uid123\", …],\n\n  \u002F\u002F Message pairs: [zaloMsgIdIndex, telegramMessageId]\n  \"p\": [[0, 123456], [1, 123457], …],\n\n  \u002F\u002F Quote metadata used for reply-chain reconstruction and auto-mention:\n  \u002F\u002F [tgId, msgIdIdx, cliMsgIdIdx, uidFromIdx, ts, msgTypeIdx, content, ttl, zaloIdIdx, threadType]\n  \"q\": [[123456, 0, 1, 2, 1746000000, 5, \"hello\", 0, 0, 1], …]\n}\n```\n\n#### Filtering `\"0\"` Message IDs\n\nZalo may emit `realMsgId = 0` for messages without a secondary identifier. In earlier formats, these values were serialised as the string `\"0\"`, which caused unrelated messages to collide under the same lookup key.\n\nThe current implementation discards these entries during both save and load:\n\n- Legitimate Zalo message lookups do not target `\"0\"`; real Zalo message IDs are timestamp-like identifiers.\n- Retaining `\"0\"` entries can produce false-positive reply targets because the most recent message with `realMsgId = 0` overwrites previous entries.\n- Filtering these entries substantially reduces persisted mapping size.\n\n#### Size Evolution\n\n| Format | Approximate Size |\n|---|---:|\n| v1 plain JSON | ~80 KB |\n| v2 interned strings and positional arrays | ~46 KB, approximately 44% smaller |\n| v2 with gzip level 9 and zero-ID filtering | ~13 KB, approximately 85% smaller |\n\nFor the current mapping size, `gunzipSync` on the compressed file is measurably faster than parsing the original 80 KB JSON representation. Compression is implemented with Node.js' built-in `node:zlib` module and requires no additional dependency.\n\n---\n\n## 🛡️ Security Model\n\n> [!CAUTION]\n> Treat `.env`, `credentials.json`, and `app-session.json` as sensitive operational secrets. They should never be committed, shared, or copied into untrusted environments.\n\nSecurity considerations:\n\n- Never commit `.env`, `credentials.json`, or `app-session.json` to version control.\n- `credentials.json` contains a Zalo Web API session and should be treated as equivalent to an account password.\n- `app-session.json` contains a Zalo PC App session and must be protected with the same level of care.\n- The bridge is designed for a trusted, single-operator or small-team environment.\n- The Telegram supergroup should remain private and restricted to trusted members, because group members can send messages through the bridge.\n- Outbound requests to Telegram and Zalo are transmitted over TLS.\n- The bridge does not intentionally log credentials.\n- The `\u002Frecall` command is available to group members and can retract messages sent by the bot. Restrict group membership and bot permissions according to your operational risk model.\n\n---\n\n## 👥 Contributors\n\nThanks to everyone who has contributed to this project.\n\n### Code Contributors\n\n- [@thanhnguyenhy234](https:\u002F\u002Fgithub.com\u002Fthanhnguyenhy234)\n- [@leolionart](https:\u002F\u002Fgithub.com\u002Fleolionart)\n\n### Contributing\n\nContributions are welcome. Bug fixes, documentation improvements, architectural refinements, compatibility patches, and feature proposals can be submitted through pull requests.\n\nTo be listed as a contributor, submit a meaningful contribution through a pull request that is reviewed and merged into the project.\n\n---\n\n## 📜 License\n\nSee the repository license file for licensing terms.\n\n\u003Cdiv align=\"center\">\n\n\u003Cbr \u002F>\n\n**Built for resilient cross-platform messaging operations.**\n\n\u003C\u002Fdiv>\n","`zalo-tg` 是一个在 Zalo 和 Telegram 之间建立双向消息桥的项目，使用 TypeScript 在 Node.js 上实现。该项目的核心功能包括将 Zalo 会话转换为 Telegram 论坛主题，并保持消息身份、媒体语义、回复、反应、撤回、提及、投票和持久会话状态的一致性。适合需要跨平台沟通协作的场景，特别是当团队成员分布在不同的即时通讯平台上时，能够提供无缝的消息同步体验。","2026-06-11 03:32:32","CREATED_QUERY"]