极简 Node.js 推特监控 → Telegram 推送工具。基于 bird CLI,纯 Node.js 实现,带 Web 管理面板,网页与后台 worker 同进程运行,开箱即用。核心仅需一个 server.js 加 lib/ 下的几个小模块,使用 JSON 文件管理配置,运行依赖仅 express + bcryptjs。
💡 版本
3.2.0:监控台式面板(账号状态卡片 + 顶部指标 + 实时活动流,配置收进设置抽屉)、深浅双主题、bird 路径自动检测;单 Node 进程同时承载面板与后台监控 worker,用 SSE 实时推送状态与日志、用 systemd 常驻,无前端框架、无构建步骤。
- 📡 监控多个 Twitter/X 账号的新推文
- 📲 新推文实时推送到 Telegram(区分原创 / 转推)
- ⚙️ 网页面板与后台 worker 同进程,worker 随服务自动启动,无需 Cron、无需独立守护进程
- 🖥️ 监控台式面板:打开即见每账号状态卡片(状态灯 / 上次检查·推送 / 最近推文 / 推送量走势 / 下次检查倒计时)+ 顶部指标(账号数·今日推送·运行时长·下次检查)+ 实时活动流;配置收进右上角 ⚙ 设置抽屉
- 🌗 深色 / 浅色 / 跟随系统 主题一键切换(记忆选择)
- 🔍 bird CLI 路径自动检测(面板一键查找并填入)
- 🔒 密码保护 + 安全加固(bcrypt、无状态签名会话、CSRF、登录限流、防敏感字段回显、data 目录隔离)
- 🧠 智能去重(ID 集合,首次运行不推送旧推文)
- ♻️ 每账号独立设置拉取条数和检查频率,保存后下一轮自动热加载,无需重启
- 🔁 推送失败自动重试(最多 3 次,每次间隔 2 秒)
- ✂️ 超长推文自动截断(>4000 字符),避免 Telegram API 报错
- 📋 Web 端实时查看运行日志(SSE 推送,内存环形缓冲 500 条,同时打到 journald)
- 🚀 纯 Node,无前端框架、无构建步骤
Tweet-Watcher 面向以下场景:
- 想以极低依赖部署一个 Twitter/X → Telegram 监控工具
- 希望直接在服务器上用 Node.js + bird CLI 跑通,不引入复杂框架
- 需要一个简单可视化的 Web 面板管理 Cookie、Bot Token、监控账号和监控开关
- 接受当前项目以单机部署、轻量运维为主,而不是大规模分布式设计
如果你想要的是一个超轻量、可直接用 systemd 常驻 + Nginx 反代的监控工具,这个仓库就是为这种用途准备的。
├── server.js # 单 Node 进程:Web 面板 + 后台监控 worker
├── package.json # 依赖与启动脚本(version 3.0.0)
├── lib/
│ ├── config.js # config.json / secrets.json 读写与字段校验
│ ├── auth.js # bcrypt 密码、HMAC 签名会话、CSRF 令牌
│ ├── state.js # 运行时状态 + 日志环形缓冲 + 事件总线(SSE 源)
│ ├── store.js # 原子 JSON 读写(临时文件 + rename)
│ ├── bird.js # 调用 bird CLI 拉取推文并解析
│ ├── telegram.js # 消息格式化 + Telegram Bot API 发送
│ └── worker.js # 调度:按账号间隔拉取、去重、重试推送
├── public/ # 纯静态前端(无构建、无框架)
│ ├── index.html # 面板页面
│ ├── app.js # 前端逻辑
│ └── style.css # 样式
├── README.md # 项目说明
├── LICENSE # 开源许可证
└── data/ # 运行时数据(自动创建,位于站点根之外,永不经 Web 暴露)
├── config.json # 普通配置(账号、tg_chat_id、bird_path、paused)
├── secrets.json # 敏感凭据(auth_token / ct0 / tg_bot_token,明文)
├── password.json # 访问密码(bcrypt 哈希)
├── session_secret.json # 会话 HMAC 密钥 + epoch
└── sent_ids.json # 已推送推文 ID(去重用)
| 依赖 | 说明 |
|---|---|
| Node.js ≥ 20 | 运行环境(实测 v24.18.0);使用内置 fetch、execFile 等,无需额外扩展 |
| npm 依赖 | 仅 express + bcryptjs,npm install 自动安装 |
| bird CLI | npm install -g @steipete/bird,用于拉取推文 |
| Telegram Bot | Bot Token + Chat ID |
| Twitter Cookie | auth_token + ct0 |
💡 无需 PHP,也不依赖
pcntl/posix/proc_open之类的能力。监控 worker 在 Node 进程内直接作为定时循环运行,不 fork 独立守护进程。
⚠️ bird CLI 是外部命令,由 Node 通过execFile调用(带 30 秒超时)。请确保运行服务的用户对bird_path指向的可执行文件有执行权限。
npm install -g @steipete/bird验证安装:
bird --version
# 查看安装路径(也可在面板设置里点「🔍 自动检测」自动填入)
which bird💡 面板设置页的 bird CLI 路径支持 🔍 自动检测:会在常见位置(与运行 node 同目录、
which bird、/usr/local/bin、/usr/bin)自动找到 bird 并填入,无需手动which;找不到时会提示安装或手动填写。默认路径为/www/server/nodejs/v24.18.0/bin/bird。
git clone https://github.com/xxvcc/Tweet-Watcher.git
cd Tweet-Watcher
npm installnode server.js
# 或
npm start默认监听 127.0.0.1:8787,可用环境变量覆盖:
HOST=0.0.0.0 PORT=9000 node server.js服务启动后,网页面板与后台监控 worker 会在同一个进程内一起运行,日志同时打到终端(stdout)。
浏览器访问面板地址(本机为 http://127.0.0.1:8787),首次访问会提示设置访问密码(至少 8 位)。
登录后进入监控台(默认视图):账号状态卡片、顶部指标、右侧实时活动流一目了然。点右上角 ⚙ 设置 打开配置抽屉,填写:
- 监控账号列表:每个账号可单独设置拉取条数和检查频率
- Twitter 认证:
auth_token和ct0(已保存时显示一行圆点,留空即保持不变;点 👁 可明文核对) - Telegram 推送:Bot Token 和 Chat ID
- bird CLI 路径:可点 🔍 自动检测 自动填入
- 点击 💾 保存配置(几秒内热加载生效,无需重启)
- 点击各个 🧪 测试 按钮验证连接是否正常
右上角 🌙 可在 深色 / 浅色 / 跟随系统 间切换主题;⏸ 暂停 / ▶ 恢复 可随时停/启监控。
保存后 worker 会在下一轮 tick(几秒内)自动读取新配置,无需重启。
systemd 常驻 —— 新建 /etc/systemd/system/tweet-watcher.service:
[Unit]
Description=Tweet Watcher
After=network.target
[Service]
Type=simple
User=www
WorkingDirectory=/www/tweet-watcher
ExecStart=/usr/bin/node server.js
Environment=HOST=127.0.0.1
Environment=PORT=8787
Restart=always
RestartSec=3
[Install]
WantedBy=multi-user.target启用并查看日志:
systemctl daemon-reload
systemctl enable --now tweet-watcher
journalctl -u tweet-watcher -fNginx 反代 + HTTPS —— 面板只监听回环,由 Nginx 对外提供 HTTPS:
server {
listen 443 ssl;
server_name 你的域名;
ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8787;
proxy_set_header Host $host;
# 必须:注入真实客户端 IP,登录限流以此为准(客户端无法伪造)
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
# SSE 实时日志:关闭缓冲,保持长连接
proxy_buffering off;
proxy_read_timeout 3600s;
}
}
⚠️ X-Real-IP由 Nginx 无条件覆盖为真实连接 IP,登录限流依赖它;服务端只信任回环代理(trust proxy = loopback),因此请务必只从本机 Nginx 反代,不要让面板端口直接对公网开放。
💡 无需
.htaccess、也无需把data/放到 Web 根下再做防护:Node 只静态伺服public/目录,data/本就在站点根之外,永不经 Web 暴露。
所有配置均通过 Web 页面统一管理,无需手动编辑文件、也没有 .env。数据持久化到 data/ 目录下的几个 JSON 文件:
| 文件 | 内容 |
|---|---|
config.json |
账号列表、tg_chat_id、bird_path、paused(暂停开关) |
secrets.json |
auth_token、ct0、tg_bot_token(明文存储,目录权限保护) |
password.json |
访问密码的 bcrypt 哈希 |
session_secret.json |
会话 HMAC 密钥与 epoch |
sent_ids.json |
每账号已推送 ID(去重表) |
首次运行无需任何配置文件即可启动,config.js 内置默认值(当然需要在 Web 页面填写 Token 才能正常工作)。
| 配置项 | 说明 | 默认值 |
|---|---|---|
bird_path |
bird CLI 可执行文件路径 | /www/server/nodejs/v24.18.0/bin/bird |
auth_token |
Twitter Cookie 认证令牌 | - |
ct0 |
Twitter Cookie CSRF Token | - |
tg_bot_token |
Telegram Bot Token | - |
tg_chat_id |
Telegram Chat ID | - |
accounts |
监控的 Twitter 账号列表(对象数组) | [] |
每个账号对象支持以下字段:
| 字段 | 说明 | 默认值 |
|---|---|---|
username |
Twitter 用户名(不含 @,需匹配 ^[A-Za-z0-9_]{1,15}$) |
(必填) |
fetch_count |
每次拉取推文条数(1–50) | 10 |
check_interval |
检查频率(秒,30–3600) | 300 |
💡 修改任何配置后点击「保存配置」即可生效,worker 会在下一轮 tick(每 5 秒一轮)自动读取新值,无需重启。已保存的密文字段(
auth_token/ct0/tg_bot_token)在页面上不回显,留空即保持不变。
- 浏览器登录 x.com
- 按
F12打开开发者工具 - 切换到 Application(应用程序)标签页
- 左侧找到 Cookies →
https://x.com - 复制
auth_token和ct0的值
⚠️ Cookie 会过期。如果推送停止工作,请重新获取 Cookie 并在 Web 页面更新。
- 在 Telegram 搜索 @BotFather
- 发送
/newbot,按提示创建 - 获得格式如
123456789:ABCdefGHIjklMNOpqrsTUVwxyz的 Token
- 在 Telegram 搜索 @userinfobot
- 发送任意消息,获得你的 Chat ID
💡 推送到群组:把 Bot 拉入群组,使用群组 Chat ID(以
-开头)。
- 首次访问 Web 页面时需设置密码(至少 8 位)
- 密码以 bcrypt(cost 12)哈希存储在
data/password.json;兼容旧$2y$前缀(自动改写为$2b$) - 密码使用异步 bcrypt(不阻塞事件循环),错误时固定延迟 1 秒响应(防暴力破解)
- 登录限流(按
req.ip,即trust proxy=loopback下 Nginx 传入的真实客户端 IP,客户端无法伪造):连续失败 5 次锁 5 分钟、10 次锁 30 分钟、20 次锁 60 分钟;锁定期满即清零,且限流表有界(惰性回收 + 硬上限,防内存耗尽) - 首次设置令牌:无密码时服务端启动会在日志打印一次性
setup_token,/api/setup必须携带它才能设密,杜绝公网面板的无认证首次抢注(TOFU)。若password.json损坏,hasPassword判定为 fail-closed(视为已设置),不会重开无认证设置 - 会话:HMAC-SHA256 签名的无状态 Cookie(有效期 7 天),内含 epoch —— 登出或改密会
bumpepoch,使所有已签发会话立即失效;畸形 Cookie/会话一律返回 401(不再有 500 堆栈泄露) - CSRF 双提交令牌:所有改动型接口校验,
timingSafeEqual常量时间比较;/api/logout仅在持有效会话+CSRF 时才全局吊销,未认证请求无法借此制造登出 DoS - 安全响应头:CSP、
X-Content-Type-Options: nosniff、X-Frame-Options: DENY、Referrer-Policy,并关闭X-Powered-By - 凭据(
auth_token/ct0/tg_bot_token)在日志输出中自动 redact,替换为*** data/目录权限0700、文件写入0600,位于站点根之外且永不被静态伺服(Node 只express.static伺服public/)- 文件写入使用原子替换(临时文件 +
fsync+rename+ 目录fsync),防止崩溃/断电导致半写损坏;读取时区分「文件不存在」与「存在但损坏」,损坏不静默当作「未设置」 bird_path只接受绝对路径、限定字符集、无..、且文件名必须为bird——防止已认证用户将其改指向/bin/sh等宿主二进制- 子进程调用 bird 带 30 秒超时保护,防止挂起
- 忘记密码时,需停服 → 删除
password.json→ 重启,重启后从服务端日志读取新的一次性setup_token再走首次设置:
systemctl stop tweet-watcher
rm /www/tweet-watcher/data/password.json
systemctl start tweet-watcher
journalctl -u tweet-watcher -n 20 | grep 首次设置令牌 # 取出 setup_token重启后下次访问面板会回到「首次设置密码」流程,需在页面填入日志中的令牌。
原创推文:
🐦 新推文 @elonmusk
发布时间:2026/2/26 10:00:00
X链接:https://x.com/elonmusk/status/1234567890
内容:
推文内容在这里...
转推:
🔁 转推 @elonmusk
发布时间:2026/2/26 10:05:00
X链接:https://x.com/elonmusk/status/1234567890
内容:
转推内容在这里...
💡 时间由推文原始时间戳解析后转为北京时间(
Asia/Shanghai),以zh-CN、24 小时制显示;无法解析时原样保留。整条消息超过 4000 字符会自动截断并追加「…(内容已截断)」。
| 策略 | 说明 |
|---|---|
| ID 集合 | 每个账号维护已推送的推文 ID 列表,持久化到 sent_ids.json |
| 首次静默 | 账号首次运行只记录当前推文 ID,不推送旧推文 |
| 集合上限 | 每个账号最多保留 200 条 ID,超出后保留最新的部分 |
| 孤立清理 | 从配置中删除账号后,worker 自动清理其去重记录、计时与状态 |
| 转推识别 | 转推标记为 🔁,与原创推文(🐦)区分 |
| 即时落盘 | 每条推送成功后立即写盘 sent_ids.json,避免崩溃后重复推送 |
推送失败时最多重试 3 次、每次间隔 2 秒;全部失败则记录日志、保留该 ID 未推状态,下轮仍可再试。
Web 页面: 在「监控状态」板块点击「📋 实时日志」。日志通过 SSE(/api/stream)从服务端实时推送到面板,来源是内存里的环形缓冲(最多保留 500 条)。
命令行 / 服务器: 日志同时写到进程 stdout,交给 systemd/journald 留存与轮转:
# 查看最近日志
journalctl -u tweet-watcher -n 50
# 实时跟踪
journalctl -u tweet-watcher -f💡 本项目不再写
data/cron.log文件,也没有 2MB 上限那套自建日志轮转 —— 落盘与轮转交给 journald 处理。
迁移到新服务器:
- 拷贝整个项目目录(含
server.js、lib/、public/、package.json) - 新服务器安装 Node.js ≥ 20 与 bird CLI:
npm install -g @steipete/bird npm install安装运行依赖- (可选)拷贝
data/目录以保留配置、凭据、密码与去重记录;若只想保留去重记录,单独拷贝data/sent_ids.json - 配好 systemd / Nginx 后启动服务,访问面板确认状态
💡 拷贝
data/后请确认目录权限仍为0700、文件为0600,且属主是运行服务的用户。
- 查看日志:面板「实时日志」或
journalctl -u tweet-watcher -n 50 - 检查面板「监控状态」是否为运行中、是否被「暂停监控」
- 检查 Twitter Cookie 是否过期:
bird user-tweets elonmusk --json -n 1 --auth-token YOUR_TOKEN --ct0 YOUR_CT0 --no-color
- 重新获取 Cookie,在 Web 页面更新即可(下一轮 tick 自动生效)
- 确认服务在运行:
systemctl status tweet-watcher - 确认监听地址端口:默认
127.0.0.1:8787,可用HOST/PORT环境变量覆盖 - 若通过公网访问,确认 Nginx 已正确反代到
127.0.0.1:8787并配好 HTTPS
多半是 bird CLI 路径不对、Cookie 失效或未填。可在服务器终端手动排查:
# 检查安装与路径
bird --version
which bird
# 手动测试拉取(与服务端调用一致)
bird user-tweets elonmusk --json -n 1 --auth-token YOUR_TOKEN --ct0 YOUR_CT0 --no-color
⚠️ bird_path会经过格式校验:必须是绝对路径、限定字符集、不含..,且文件名必须为bird(防止改指向其它宿主二进制)。请填写 bird 可执行文件的真实绝对路径,如which bird的输出。
监控 worker 在 node server.js 进程内随服务自动启动,只要保证这个进程一直活着即可。除了 systemd,也可以用 pm2、supervisor 等进程管理器守护。注意本项目没有 start / stop 守护进程子命令,也没有 Cron 单次运行模式 —— 面板上的「暂停 / 恢复监控」控制的是同一个进程里的监控开关,而不是进程本身的启停。
- 创建 Telegram 群组,把 Bot 和所有人拉进去,使用群组 Chat ID(以
-开头) - 或复制项目到另一个目录、用不同的
PORT再跑一份,配置不同的 Chat ID
不需要。保存配置后 worker 会在下一轮 tick(每 5 秒一轮)读取最新的 config.json,账号增删、频率与条数调整都会自动生效。
欢迎提交 Issue、Pull Request 或部署经验反馈。请只依据代码中真实存在的能力提交问题与改进,附上脱敏后的日志(凭据在服务端日志中已自动 redact)与环境信息(Node 版本、bird 版本、部署方式)。
如果你在使用中遇到问题,建议按以下顺序排查:
- 阅读本 README 对应章节
- 查看面板「实时日志」或
journalctl -u tweet-watcher -f - 检查 bird CLI、Twitter Cookie 和 Telegram 配置(用面板的 🧪 测试按钮)
- 提交 Issue,并附上脱敏后的日志与环境信息
本项目基于 MIT License 开源,详见 LICENSE。
推特数据拉取由 bird CLI 提供支持。