Skip to content

Instantly share code, notes, and snippets.

@discountry
Created March 8, 2026 09:43
Show Gist options
  • Select an option

  • Save discountry/7d1d0497303d0c440fcfb172c3f2b428 to your computer and use it in GitHub Desktop.

Select an option

Save discountry/7d1d0497303d0c440fcfb172c3f2b428 to your computer and use it in GitHub Desktop.
#!/bin/bash
# ================= 1. 用户自定义配置 (请修改此处) =================
# [必填] 推特用户名 (不需要加 @,例如 elonmusk)
TWITTER_USER="discountifu"
# [必填] 币安广场 API Key
BINANCE_API_KEY="123123123"
# [可选] 币安广场 API URL (通常不需要修改)
BINANCE_API_URL="https://www.binance.com/bapi/composite/v1/public/pgc/openApi/content/add"
# [可选] 工作目录 (日志和临时文件存放位置)
WORK_DIR="$HOME/binance_sync"
# ================= 2. 高级系统配置 (通常不需要修改) =================
# Nitter 实例域名列表
# 脚本会自动将用户名拼接到这些域名后面
# 建议多保留几个,因为 Nitter 实例经常挂
NITTER_DOMAINS=(
"https://nitter.net"
"https://nitter.privacydev.net"
)
# 保留历史记录行数 (建议调大,防止 --full 模式下历史记录被过快滚动覆盖)
KEEP_HISTORY_COUNT=200
# 网络请求配置
RETRY_INTERVAL=3 # 重试间隔(秒)
MAX_RETRIES=3 # 单个源最大重试次数
TIMEOUT=15 # 连接超时时间(秒)
# =================================================================
# --- 参数解析 ---
# 默认模式: today (仅同步今日)
# 参数 --full: full (同步所有 RSS 内容)
SYNC_MODE="today"
if [[ "$1" == "--full" ]]; then
SYNC_MODE="full"
fi
# --- 基础环境准备 ---
HISTORY_FILE="$WORK_DIR/history.log"
TEMP_XML="$WORK_DIR/rss_feed.xml"
mkdir -p "$WORK_DIR"
touch "$HISTORY_FILE"
# 日志函数
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# --- 检查配置完整性 ---
if [ -z "$TWITTER_USER" ] || [ -z "$BINANCE_API_KEY" ]; then
log "错误: 请先在脚本顶部配置 TWITTER_USER 和 BINANCE_API_KEY"
exit 1
fi
# 获取今日日期 (UTC),RSS 通常使用 GMT/UTC
TODAY_DATE=$(date -u +%Y-%m-%d)
log "========================================"
log "任务开始: 同步 @$TWITTER_USER 的推文"
if [ "$SYNC_MODE" == "full" ]; then
log "运行模式: [全量同步] (同步所有未记录的推文)"
else
log "运行模式: [仅今日] (仅同步 UTC 日期为 $TODAY_DATE 的推文)"
fi
# 检查依赖
for cmd in xmlstarlet jq curl; do
if ! command -v $cmd &> /dev/null; then
log "错误: 未安装 $cmd,请运行 sudo apt-get install $cmd"
exit 1
fi
done
# ================= 下载模块 =================
DOWNLOAD_SUCCESS=false
# 遍历 Nitter 域名列表,自动拼接 RSS URL
for domain in "${NITTER_DOMAINS[@]}"; do
# 移除域名末尾可能存在的斜杠,构建最终 URL
domain=${domain%/}
url="$domain/$TWITTER_USER/rss"
log "尝试从源获取数据: $url"
for (( attempt=1; attempt<=MAX_RETRIES; attempt++ )); do
http_code=$(curl -s -L --connect-timeout "$TIMEOUT" \
-A "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" \
-w "%{http_code}" -o "$TEMP_XML" "$url")
# 检查 HTTP 200 且文件内容有效
if [ "$http_code" == "200" ] && [ -s "$TEMP_XML" ]; then
if grep -q "<?xml" "$TEMP_XML" || grep -q "<rss" "$TEMP_XML"; then
log "下载成功 (第 $attempt 次尝试)"
DOWNLOAD_SUCCESS=true
break 2 # 跳出两层循环
else
log "警告: 下载内容不是有效的 XML (第 $attempt 次尝试)"
fi
else
log "请求失败 (HTTP $http_code) - 第 $attempt/$MAX_RETRIES 次重试..."
fi
if [ "$attempt" -lt "$MAX_RETRIES" ]; then
sleep "$RETRY_INTERVAL"
fi
done
log "当前源不可用,切换下一个备用源..."
done
if [ "$DOWNLOAD_SUCCESS" = false ]; then
log "错误: 所有 RSS 源均无法访问,请检查网络或 Nitter 实例状态。"
exit 1
fi
# ================= 解析与同步模块 =================
# 统计条目数
item_count=$(xmlstarlet sel -t -v "count(//item)" "$TEMP_XML" 2>/dev/null)
if [[ -z "$item_count" || ! "$item_count" =~ ^[0-9]+$ ]]; then
item_count=0
fi
log "解析到 $item_count 条推文"
if [ "$item_count" -eq 0 ]; then
log "没有新内容。"
rm -f "$TEMP_XML"
exit 0
fi
# 倒序处理 (从旧到新同步)
for (( i=item_count; i>=1; i-- )); do
# 提取 GUID, Title, PubDate
guid=$(xmlstarlet sel -t -v "//item[$i]/guid" "$TEMP_XML" 2>/dev/null)
content=$(xmlstarlet sel -t -v "//item[$i]/title" "$TEMP_XML" 2>/dev/null)
pubDate=$(xmlstarlet sel -t -v "//item[$i]/pubDate" "$TEMP_XML" 2>/dev/null)
# 空值检查
if [ -z "$guid" ] || [ -z "$content" ]; then continue; fi
# 1. 查重 (历史记录中存在则跳过)
if grep -q "$guid" "$HISTORY_FILE"; then
continue
fi
# 2. 日期检查 (非 Full 模式下,非今日推文跳过)
if [ "$SYNC_MODE" != "full" ]; then
# 格式化 RSS 时间为 YYYY-MM-DD
item_date_str=$(date -d "$pubDate" -u +%Y-%m-%d 2>/dev/null)
if [ "$item_date_str" != "$TODAY_DATE" ]; then
# debug log: log "跳过非今日推文: $item_date_str"
continue
fi
fi
log "正在同步推文: $guid"
# 构建 JSON Payload
json_payload=$(jq -n --arg txt "$content" '{bodyTextOnly: $txt}')
# 调用币安 API
response=$(curl -s -X POST "$BINANCE_API_URL" \
-H "X-Square-OpenAPI-Key: $BINANCE_API_KEY" \
-H "Content-Type: application/json" \
-H "clienttype: binanceSkill" \
-d "$json_payload")
api_code=$(echo "$response" | jq -r '.code')
if [ "$api_code" == "000000" ]; then
log "同步成功! ID: $(echo "$response" | jq -r '.data.id')"
# 写入历史记录
echo "$guid | $(date '+%Y-%m-%d %H:%M:%S')" >> "$HISTORY_FILE"
# 滚动清理历史 (保持最后 N 行)
line_count=$(wc -l < "$HISTORY_FILE")
if [ "$line_count" -gt "$KEEP_HISTORY_COUNT" ]; then
tail -n "$KEEP_HISTORY_COUNT" "$HISTORY_FILE" > "$HISTORY_FILE.tmp" && mv "$HISTORY_FILE.tmp" "$HISTORY_FILE"
fi
# 避免 API 速率限制
sleep 5
else
log "同步失败. API 响应: $response"
fi
done
# 清理临时文件
rm -f "$TEMP_XML"
log "任务完成。"
@discountry
Copy link
Author

这是一个将上述脚本设置为 Ubuntu 定时任务(Crontab)的详细教程。

由于脚本已经设置了默认行为(只同步今日推文),非常适合设置为高频运行的定时任务。

步骤 1:准备工作

在设置定时任务之前,必须确保脚本有执行权限,并且你需要知道脚本的绝对路径

  1. 赋予执行权限
    假设你的脚本名为 binance_sync.sh
chmod +x binance_sync.sh
  1. 获取绝对路径
    运行以下命令并记下输出的路径:
realpath binance_sync.sh
# 输出示例: /home/ubuntu/binance_sync.sh

步骤 2:编辑 Crontab 配置

Ubuntu 使用 crontab 命令来管理定时任务。

  1. 打开编辑器
    在终端输入:
crontab -e

(如果是第一次运行,系统会让你选择编辑器,输入 1 选择 nano 即可,最简单易用)
2. 理解 Cron 语法
Cron 表达式由 5 个时间字段和一个命令组成。
格式如下:

m    h    dom   mon   dow   command
分   时    日    月    周    要执行的命令


步骤 3:添加定时任务

在打开的编辑器文件末尾,添加你需要的计划任务。由于 Nitter 实例有时不稳定,且为了及时获取推文,建议每 30分钟1小时 运行一次。

方案 A:每 30 分钟运行一次 (推荐)

/home/ubuntu/binance_sync.sh 替换为你步骤 1中获取的真实路径。

*/30 * * * * /home/ubuntu/binance_sync.sh >> /home/ubuntu/cron_run.log 2>&1

方案 B:每 1 小时运行一次 (整点)

0 * * * * /home/ubuntu/binance_sync.sh >> /home/ubuntu/cron_run.log 2>&1

关于命令末尾的解释:

  • >> /home/ubuntu/cron_run.log:这会将脚本的标准输出(例如脚本里的 echo)追加保存到一个日志文件中,方便你排查脚本是否运行了。
  • 2>&1:这会将错误信息(比如 curl 报错)也重定向到同一个日志文件中。

步骤 4:保存并退出

如果你使用的是 nano 编辑器:

  1. Ctrl + O (保存)。
  2. Enter (确认文件名)。
  3. Ctrl + X (退出编辑器)。

系统会提示 crontab: installing new crontab,表示设置成功。


步骤 5:验证与排查 (重要)

Cron 任务是在后台静默运行的,如果不检查,你可能不知道它是否在工作。

  1. 查看任务列表
    确保任务确实存在。
crontab -l
  1. 检查 Cron 服务日志
    查看系统是否触发了该任务。
grep CRON /var/log/syslog

如果你看到类似 (root) CMD (/home/ubuntu/binance_sync.sh ...) 的记录,说明系统尝试执行了。
3. 常见问题:找不到命令 (Command not found)
Cron 的环境变量(PATH)通常比用户登录时要少。如果脚本手动运行正常,但 Cron 运行报错说找不到 jqxmlstarlet,有两种解决方法:
方法一(推荐):在脚本内部指定绝对路径
或者在脚本顶部(#!/bin/bash 下方)添加 PATH 定义:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH

方法二:在 Crontab 文件顶部添加
再次运行 crontab -e,在文件最顶部添加:

SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# 下面才是你的任务
*/30 * * * * /home/ubuntu/binance_sync.sh ...

总结

设置完成后:

  • 日常运行:Cron 会每 30 分钟静默运行脚本,脚本内部逻辑会判断是否为“今日推文”并进行同步。
  • 手动补录:如果你发现漏掉了前几天的内容,可以随时手动登录服务器运行全量同步:
./binance_sync.sh --full

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment