CrossGram 是一个基于 cordis 和 mtcute 的 Telegram 服务器端实现。它将 Telegram 桥接到其它平台,以让你在 Telegram 客户端下获得远超原生客户端的聊天体验。
Crossgram 由一组 cordis 插件组成,插件及其配置在 app.yml 中声明。处理 Telegram 客户端请求的核心插件有三类:
- MTProto 服务(
@mtproto-relay/mtproto):监听 Telegram 客户端连接,负责 MTProto 传输、加密握手和 auth key 存储。首次启动时,服务在data/rsa-key.json生成服务器 RSA 密钥对,并把公钥另存为data/rsa-key.json.pem;客户端必须使用这个公钥才能与服务器握手。 - bridge(
@mtproto-relay/bridge):实现 Telegram API 方法,包括登录、会话列表、历史消息、媒体收发和更新推送。bridge 把平台数据转换为 Telegram 对象,为平台消息分配 Telegram 消息 ID,并通过 update store 持久化客户端需要补拉的更新。bridge 还在 WebUI 中提供平台账号、表情包和 bot 的管理页面。 - 平台适配器(
@mtproto-relay/platform-*):连接具体 IM 平台,实现 bridge 定义的IMPlatform接口。
app.yml 中的每个平台适配器配置项对应一个平台账号,也对应一个可登录的 Telegram 身份。适配器通过 capabilities 声明平台支持的操作,例如能否编辑消息、撤回时限和单条消息的媒体数量上限;bridge 依据声明决定向客户端开放的功能,并把平台不支持的请求作为 Telegram 错误返回。接口约定见 IMPlatform 适配器实现规范。
各适配器连接的对象和完成程度如下:
| 平台 | 包名 | 连接对象 | 完成程度 |
|---|---|---|---|
@mtproto-relay/platform-qqnt |
注入 QQNT 的 qqnt-bridge 本地服务 | 主要目标平台,功能最完整 | |
| Discord | @mtproto-relay/platform-discord |
Discord 普通用户账号(userbot) | 收发、频道与子频道、回应 |
| Matrix | @mtproto-relay/platform-matrix |
homeserver 的 Client-Server API | 仅支持未加密房间 |
| Satori | @mtproto-relay/platform-satori |
任意 Satori cordis adaptor | 能力取决于 adaptor |
| 微信 | @mtproto-relay/platform-wechat |
Windows 上的 ComWeChat HTTP API | 只接收消息 |
| static | @mtproto-relay/platform-static |
内存中的模拟数据 | 参考实现,用于测试 |
QQ 适配器的源码位于 packages/platform-crossgram。适配器通过 HTTP 调用 qqnt-bridge 的 API,通过 WebSocket 接收有序的事件流;图片、视频等消息媒体保持 QQ 原始格式,由客户端从 QQ CDN 直接下载。
Caution
Discord 适配器以普通用户账号的身份自动操作,违反 Discord 服务条款,账号可能因此受限或被封禁。请仅使用能够承受该风险的独立账号。
下表比较 QQ、Discord、Matrix 和 static 适配器声明的能力。Satori 和微信适配器的能力范围另见下文。
| 能力 | Discord | Matrix | static | |
|---|---|---|---|---|
| 历史消息 | ✓ | ✓ | ✓ | ✓ |
| 已读状态同步 | 仅标记已读 | ✓ | ✓ | ✓ |
| 消息搜索 | ✓ | ✗ | ✗ | ✗ |
| 发送文字、图片、文件、图文混排 | ✓ | ✓ | ✓ | ✓ |
| 私聊与群聊 | ✓ | ✓ | ✓ | ✓ |
| 频道 | ✗ | ✓ | Space | ✓ |
| 子频道(Telegram 话题) | ✗ | ✓ | ✗ | ✓ |
| 成员列表与管理员 | ✓ | ✓ | ✓ | ✓ |
| 成员权限 | ✗ | ✓ | ✓ | ✓ |
| 设置管理员 | ✓ | ✗ | ✗ | ✓ |
| 禁言与移出成员 | ✓ | ✗ | ✗ | ✗ |
| 好友与入群申请 | ✓ | ✗ | ✗ | ✗ |
| 撤回消息 | ✓[1] | ✓ | ✓ | ✓ |
| 编辑消息 | 撤回后重发[1] | ✓ | 仅纯文本 | ✓ |
| 转发 | ✓ | ✓ | ✗ | ✓ |
| 合并转发 | ✓ | ✗ | ✗ | ✗ |
| 回应(reaction) | ✓ | ✓ | ✗ | ✓ |
| 表情包 | 收藏表情 | 作为图片接收 | ✗ | ✓ |
| 系统提示(灰条) | ✓[2] | ✓ | ✗ | ✗ |
| 戳一戳 | ✓ | ✗ | ✗ | ✗ |
| 语音消息与转写 | ✓ | ✗ | ✗ | ✗ |
| 群文件 | ✓ | ✗ | ✗ | ✗ |
| 语音通话 | 私聊[3] | ✗ | ✗ | ✗ |
[1] QQ 只允许撤回 120 秒内的消息。QQ 不支持编辑已发送的消息,适配器在 120 秒内以撤回原消息并发送新消息的方式实现编辑。
[2] grayTipFilters 列出需要隐藏的灰条文字。默认配置隐藏“回应了你的消息”灰条,因为同一事件已经以消息回应的形式显示。
[3] 语音通话需要单独运行 voice-worker,见 voice-worker。如果 bridge 未配置 voiceWorkerSocketPath,那么通话不可用。视频通话不受支持。
所有适配器都不支持 Secret Chat、Stories、Premium、红包和转账。群公告既不显示也不能管理。Matrix 适配器不解密端到端加密事件,这类事件显示为占位消息;配置与限制见 Matrix 适配器文档。
微信适配器连接 Windows 上的 ComWeChat。适配器通过 endpoint 调用 ComWeChat 的 HTTP API,并在本机 callbackPort 端口上监听 ComWeChat 推送的消息回调。适配器只提供接收能力:账号资料、联系人会话、群成员和实时文字消息可以同步,媒体消息显示为文字说明,发送消息和历史消息不可用。部署方式和安全注意事项见 微信适配器文档。
@mtproto-relay/platform-satori 把 Satori 的 cordis adaptor 接入 bridge,因此 Satori 生态已有的 adaptor 都可以作为平台使用。配置时依次加载 Satori core、adaptor 和适配器。适配器的 bot 字段是 Satori 的 Bot SID,格式为 platform:selfId;如果只有一个 Bot,那么可以省略。以 Discord adaptor 为例:
yarn add @satorijs/adapter-discord- id: satori-core
name: '@satorijs/core'
- id: discord-adaptor
name: '@satorijs/adapter-discord'
config:
token: your-token
- id: discord
name: '@mtproto-relay/platform-satori'
config:
bot: discord:your-bot-id适配器覆盖账号、消息事件、会话列表、联系人、成员、文字与媒体收发、媒体下载。历史消息、成员列表、撤回和编辑需要 adaptor 在 Satori login.features 中声明对应 API,未声明的功能不会向客户端开放。适配器不支持转发、回应和表情包。
Satori 4.6 的 npm 包仍声明依赖 cordis 3。本仓库通过 Yarn patch 把 @satorijs/core 和 @satorijs/plugin-server 适配到 cordis 4,并为依赖旧接口的 adaptor 保留 HTTP 兼容 API。
服务器需要 Node.js 24 或更高版本,以及通过 corepack 启用的 Yarn 4。
corepack enable
yarn install
yarn dev # 开发模式,启用热重载
yarn build && yarn start # 生产模式yarn dev 和 yarn build 都会先构建 WebUI。首次启动时,服务器在 data/ 下生成 RSA 密钥、auth key 存储文件和 SQLite 数据库 cordis.db。WebUI 位于 http://127.0.0.1:3140。
普通运行不需要 Rust 或 C++ 工具链。只有语音通话使用的 voice-worker 和 native/ 下的 tgcalls 封装需要原生构建,构建环境由 flake.nix 提供。
服务器的网络配置分两部分:MTProto 服务的 host 和 port 决定实际监听的地址,bridge 的 serverHost 和 serverPort 决定在 help.getConfig 中向客户端公布的地址。服务器位于 NAT 或反向代理之后时,两组地址可能不同;serverHost 必须是客户端能够访问的地址。
- id: mtproto01
name: '@mtproto-relay/mtproto'
config:
host: 0.0.0.0
port: 4430
rsaKeyPath: ./data/rsa-key.json
authKeyStorePath: ./data/auth-keys.json
- id: bridge01
name: '@mtproto-relay/bridge'
config:
dcId: 1
serverHost: 192.168.1.10
serverPort: 4430
altEndpoints:
- bridge-backup.example:8443
- '[2001:db8::1]:4430'
- id: qqnt
name: '@mtproto-relay/platform-qqnt'
config:
endpoint: http://127.0.0.1:18767/v1
token: your-qqnt-bridge-tokenbridge 在 help.getConfig 中只公布 dcId 指定的一个 DC。altEndpoints 是该 DC 的备用地址,按配置顺序排在主地址之后公布,格式为 host:port 或 [IPv6]:port。bridge 只公布备用地址,不检查地址是否可用;主地址不可用时是否改用备用地址,由客户端决定。客户端首次连接时尚未获取服务器配置,因此使用导入的服务器 JSON 中的地址(见 连接 Telegram 客户端);该地址应与 serverHost 和 serverPort 一致。
QQ 适配器的 token 是 qqnt-bridge 的访问令牌。如果配置中未提供 token,那么适配器读取环境变量 QQNT_BRIDGE_TOKEN。适配器默认通过 ${endpoint}/events/ws 接收事件;如果事件流使用不同的地址,那么可以用 webSocketEndpoint 单独指定。其它适配器的配置见 app.yml 和各适配器文档。
deploy/ 提供 Linux 上的 systemd 部署方式。安装脚本创建 crossgram 系统用户,把仓库检出到 /opt/crossgram,并把数据保存在 /var/lib/crossgram/data。生产配置使用 PostgreSQL 存储数据,并让 WebUI 只监听 127.0.0.1:3140,因此需要通过 SSH 隧道或反向代理访问 WebUI。
curl -fsSL https://github.com/@raw/std-microblock/crossgram/main/deploy/install.sh \
| sudo CROSSGRAM_PUBLIC_HOST=203.0.113.10 sh安装脚本不创建数据库,也不生成密钥。首次启动前,需要运行 deploy/provision-postgres.sh 创建 PostgreSQL 角色和数据库,并在 /etc/crossgram.env 中设置 Telegram Bot API 使用的 TELEGRAM_BOT_TOKEN_VERIFIER_SECRET。之后通过 sudo crossgram-update 更新:更新脚本只接受 origin/main 的快进合并,重新安装依赖、构建并重启服务,不修改数据目录。完整步骤见 Linux 部署文档。
官方 Telegram 客户端内置官方服务器的地址和 RSA 公钥,客户端只与持有对应私钥的服务器完成握手。连接 Crossgram 需要把这两项替换为 Crossgram 服务器的地址和 rsa-key.json.pem 中的公钥,替换通过修改客户端源码实现。
crossgram-project 组织下的 patcher 仓库修改上游客户端的源码,并在 GitHub Releases 发布构建产物。除服务器选择外,Android 和桌面端的修改还使客户端能够直接从平台 CDN 下载媒体、利用 QQ 秒传上传文件,并显示戳一戳、合并转发和已撤回消息。
| 仓库 | 上游客户端 | 系统 |
|---|---|---|
| crossgram-desktop | Telegram Desktop、64Gram、AyuGram、materialgram | Windows、macOS、Linux |
| crossgram-android | Telegram、Nagram、Nnngram、Nullgram、Mercurygram、Forkgram | Android |
| crossgram-unigram | Unigram | Windows |
| crossgram-telegram-x | Telegram X | Android |
| crossgram-mithka | Mithka | Android、iOS、桌面 |
Unigram、Telegram X 和 Mithka 基于 TDLib,三者共用 crossgram-tdlib 对 TDLib 的修改。Telegram Web 和官方 iOS、macOS 原生客户端没有对应的 patcher。
这些客户端在登录页提供服务器选择,每个账号可以分别选择服务器,也可以继续使用官方 Telegram 服务器。服务器以 JSON 描述,所有客户端使用相同的格式:
{
"name": "Crossgram",
"enable_special_config": false,
"host": "203.0.113.10",
"port": 4430,
"rsa_key": "-----BEGIN RSA PUBLIC KEY-----\n...\n-----END RSA PUBLIC KEY-----",
"dcs": [{ "id": 1, "ip": "203.0.113.10", "port": 4430 }]
}rsa_key 是 PKCS#1 格式的服务器公钥,dcs 列出各 DC 的地址,缺少的 DC 1–5 由客户端按 host 和 port 补齐。enable_special_config 为 false 时,客户端不使用 Telegram 的 special config 机制获取官方备用地址。WebUI 的平台账号页面和平台管理 bot 的 /server 命令按 bridge 的 serverHost 和 serverPort 输出该 JSON;生产部署中也可以用 crossgram-client-config --host <IP> --port 4430 生成。
bridge 为每个平台账号分配 +888 开头的虚拟手机号。QQ 账号的手机号由 QQ 号确定,例如 QQ 号 1234567890 对应 +888 1234567890;其它平台的账号分配随机的 12 位号码,分配后保持不变。
登录码是 6 位的 TOTP 码,每 30 秒更新一次,服务器只接受当前时间段的登录码。服务器不向客户端发送登录码,用户需要在以下位置查看:
- WebUI 的平台账号页面(
/platform-accounts)显示每个账号的虚拟手机号和当前登录码。 - 已登录的客户端中,平台管理 bot
@CrossGramAdminBot可以显示登录码,用于登录其它设备,也可以批准其它设备的二维码登录。
在客户端中输入虚拟手机号和当前登录码即可登录。如果在 WebUI 中为账号设置了登录密码,那么输入全零的登录码(如 000000)后,客户端进入密码验证步骤。
以下功能由独立的 cordis 插件或 bridge 的可选模块提供,可以在 app.yml 中单独启用或停用。
@mtproto-relay/merged-forward 把 QQ 的合并转发消息显示为“查看聊天记录”链接。点击链接后,客户端以只读群组的形式打开被转发的聊天记录,其中嵌套的合并转发可以继续打开。链接中编码了消息在数据库中的位置,服务器重启后仍然有效。
bridge 的 groupFilesMiniApp 模块在群聊的附件菜单中提供“群文件”Mini App,用于浏览、搜索和下载平台的群文件,不支持上传或删除。只有 QQ 适配器提供群文件数据。
Mini App 以网页形式在客户端中打开,publicUrl 是客户端访问该网页的地址;移动端客户端需要可以访问的 HTTPS 地址,通常经由反向代理提供。Mini App 的访问令牌由 secret 签名。如果 secret 和环境变量 CROSSGRAM_GROUP_FILES_SECRET 都未设置,那么服务器每次启动时生成随机密钥,已签发的令牌在重启后失效。
@mtproto-relay/platform-admin-bot 在客户端中提供 @CrossGramAdminBot 会话,用于查看服务器状态、平台账号、登录码和客户端会话,输出服务器 JSON,以及管理表情包关联。默认情况下,每个平台身份都能看到该 bot,但只能管理自己;allowedPlatformSessionIds 限定可以使用 bot 的身份,crossAccountAccess 允许这些身份管理其它身份。命令列表见 平台管理 bot 文档。
@mtproto-relay/telegram-bot-api 在 WebUI 所在的 HTTP 服务上提供兼容 Telegram Bot API 的接口(/bot<token>/<method>),并在客户端中提供 @BotFather 用于创建 bot。服务器不保存 bot token 原文,只保存以 verifierSecret 为密钥计算的 HMAC 校验值。verifierSecret 必须随机生成并长期保持不变,更换后所有已创建的 bot token 都会失效。
@mtproto-relay/qq-flash-transfer-bot 在 QQ 账号中提供“QQ 闪传”bot,把文件上传到 QQ 闪传并返回 QQ 的分享链接和文件集 ID。
- 转发给 bot 的 QQ 文件使用 QQ 服务器上已有文件的标识和哈希秒传,不读取或重新上传 QQNT 本地缓存。如果 QQ 服务器已不再保留该文件,那么秒传失败,bot 返回错误。
- 直接发送给 bot 的新文件以流式方式交给 QQ 闪传上传一次。随文件发送的纯文本说明用作文件集名称。
每次闪传最多包含 100 个文件,总大小不超过 100 GiB,maxFiles 和 maxTotalBytes 可以调低这两项限制。
@mtproto-relay/telegram-sticker-importer 使用一个官方 Telegram Bot Token,从 Telegram Bot API 读取公开贴纸包,插件默认停用。启用插件并设置 TELEGRAM_STICKER_IMPORTER_BOT_TOKEN 后,在客户端中向 Sticker Importer bot 发送 https://t.me/addstickers/<short_name> 或 /import <url>,贴纸包即导入并关联到当前平台身份。
贴纸文件在客户端请求时由服务器经 Bot API 下载,Bot Token 只在服务器端使用。Telegram 托管的 Bot API 限制单个文件不超过 20 MB;如需绕过该限制,可以把 apiBase 指向自建的 Local Bot API Server。每个平台身份默认最多导入 100 个贴纸包,两次导入至少间隔 3 秒,分别由 maxImportsPerSession 和 importCooldownMs 调整。
@mtproto-relay/satori-exporter 把一个已接入 bridge 的平台账号发布为 Satori Bot,供基于 Satori 的机器人框架使用。exporter 依赖 Satori core 和 @satorijs/plugin-server,应在 bridge 和目标平台之后加载:
- id: satori-core
name: '@satorijs/core'
- id: satori-server
name: '@satorijs/plugin-server'
config:
path: /satori
token: ${SATORI_TOKEN}
- id: qq-exporter
name: '@mtproto-relay/satori-exporter'
config:
platformId: qqnt
platform: qqplatformId 是目标平台适配器配置项的 id,platform 是 Satori 中使用的平台名,省略时取适配器的平台类型。一个 exporter 配置项只导出一个账号,导出多个账号需要多个配置项。
exporter 向 Satori 推送已成功写入数据库的新入站消息,以及消息删除和撤回事件;Satori 应用也可以通过该 Bot 发送、查询和删除消息,查询群组和成员。exporter 跟随 bridge 中平台账号的上线和下线注册或移除 Bot;Satori core 或目标平台重载时,exporter 移除旧 Bot,再按仍在线的账号重新注册。
@mtproto-relay/group-auto-kick 在指定的 QQ 群人数达到上限时,按最近发言时间移出最久未发言的成员,使人数回到目标值;群主和登录账号本身不会被移出,管理员是否受保护由配置决定。插件默认不在 app.yml 中,配置见 群人数自动清理文档。
yarn typecheck # 类型检查
yarn test:unit # 单元测试
yarn test:e2e # 端到端测试,会先执行 yarn build开发与排查问题可以使用以下工具:
- WebUI 的
/mtproto-debug页面按需记录 MTProto 请求与响应,/mtproto-statistics页面统计 RPC 耗时、流量和慢请求。 @mtproto-relay/debug-scripts把放入data/debug-scripts的 TypeScript 文件作为临时 cordis 插件加载,脚本发布的结果写入data/debug-results。yarn mtproto:e2e以 Telegram 客户端的身份连接 Crossgram 服务器并运行探测脚本。
cordis 的插件、服务与热重载机制见 cordis 开发说明。设计文档:
MIT
