name: Update Trackers # 说明:之前因为我的服务器掉链子导致自动化更新中断,现已迁移至 GitHub Actions 稳定运行。为顺利迁移暂时精简了筛选过滤流程(以及部分处理起来麻烦的 Tracker 来源),后续会逐步恢复接近原来的效果! # Note: Previously, my automated updates were interrupted due to server issues, but they have now been migrated to GitHub Actions and are running stably. To ensure a smooth migration, the filtering process has been temporarily streamlined, and I will gradually restore it to its original effect in the future! on: # 定时执行。这里使用 UTC 时间。 schedule: # 每天 UTC 00:00 运行一次,对应 UTC+8 的 8:00 - cron: "0 0 * * *" # 允许在 GitHub Actions 页面手动触发,便于测试。 workflow_dispatch: permissions: # 允许工作流把生成结果提交回当前仓库。 contents: write concurrency: # 避免同一工作流并发执行,导致互相覆盖提交。 group: update-trackers-optimized # false 表示不取消已在运行的任务,新任务排队等待。 cancel-in-progress: false jobs: update: # 使用 GitHub 提供的 Ubuntu 运行环境。 runs-on: ubuntu-latest # 给整个任务一个总超时,防止外部源异常时无限挂住。 timeout-minutes: 45 env: UA: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 TRACKERSLIST_ALL_URL: | https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_all.txt http://github.itzmx.com/1265578519/OpenTracker/master/tracker.txt https://newtrackon.com/api/live https://raw.githubusercontent.com/DeSireFire/animeTrackerList/master/AT_best.txt TRACKERSLIST_BEST_URL: | https://raw.githubusercontent.com/ngosang/trackerslist/master/trackers_best.txt https://newtrackon.com/api/stable https://raw.githubusercontent.com/DeSireFire/animeTrackerList/master/AT_best.txt steps: - name: Checkout # 检出仓库内容。后续所有文件读写都基于这里的工作目录。 uses: actions/checkout@v4 with: # 拉取完整历史,方便后面 git commit / git push 更稳定。 fetch-depth: 0 - name: Generate tracker files # 生成最终 Tracker 列表文件(拉取 -> 清洗 -> 黑名单过滤 -> 解析检查 -> TCP 检查 -> 输出文件) shell: bash run: | # 严格模式: # -e 任意命令失败立即退出 # -u 使用未定义变量时报错 # -o pipefail 管道任一环节失败都视为失败 set -Eeuo pipefail # 所有输入输出文件名都集中定义,便于后续维护。 FILE_ALL="all.txt" FILE_BEST="best.txt" FILE_HTTP="http.txt" FILE_NOHTTP="nohttp.txt" FILE_ALL_ARIA2="all_aria2.txt" FILE_BEST_ARIA2="best_aria2.txt" FILE_HTTP_ARIA2="http_aria2.txt" FILE_NOHTTP_ARIA2="nohttp_aria2.txt" FILE_OTHER="other.txt" FILE_BLACKLIST="blacklist.txt" # 基础文件检查:这些文件缺失时,直接终止工作流并报错。 [[ -f "${FILE_OTHER}" ]] || { echo "${FILE_OTHER} not found" >&2; exit 1; } [[ -f "${FILE_BLACKLIST}" ]] || { echo "${FILE_BLACKLIST} not found" >&2; exit 1; } # 拉取远程源。这里用 curl,不依赖额外安装。 # 参数说明: # -4 只走 IPv4,避免某些源在 Actions 环境下 IPv6 异常 # -f 服务器返回 4xx/5xx 时直接失败 # -sS 静默但保留错误 # -L 自动跟随重定向 fetch_url() { local url="$1" curl -4fsSL --max-redirs 5 --retry 2 --retry-delay 1 --connect-timeout 4 --max-time 10 \ -A "${UA}" "${url}" || true } # TCP 连通性检查。 # 使用 Python 标准库 socket 做 TCP 连接测试,原因是: # 1. GitHub Actions 自带 python3 # 2. 可同时支持 IPv4 / 域名 / IPv6 # 3. 不需要额外安装 nc、telnet 等工具 tcping() { local host="$1" local port="$2" python3 -c 'import socket, sys; socket.create_connection((sys.argv[1], int(sys.argv[2])), timeout=1.5).close()' "$host" "$port" 2>/dev/null } # 规范化并过滤 Tracker: # 1. 清理空白与注释 # 2. 保留包含 announce 的合法 URL # 3. 过滤掉部分 Cloudflare CDN 的网段(CDN 直接用 IP 是不行的) # 4. 给未显式带端口的 http/https 自动补 80/443 # 5. 只保留最终带端口的记录 normalize_and_filter() { awk ' function trim(s){ sub(/^[[:space:]]+/,"",s); sub(/[[:space:]]+$/,"",s); return s } { gsub(/\r/, "", $0) line = trim($0) if (line == "" || line ~ /^#/) next if (line !~ /:\/\//) next if (line !~ /\/announce/) next if (line ~ /:\/\/104\./) next if (line ~ /:\/\/172\./) next # 在清洗阶段统一补齐(http/https 缺省端口的处理) if (line ~ /^http:\/\//) { if (line !~ /^http:\/\/.*:[0-9]+\/announce/) { sub(/\/announce/, ":80/announce", line) } } else if (line ~ /^https:\/\//) { if (line !~ /^https:\/\/.*:[0-9]+\/announce/) { sub(/\/announce/, ":443/announce", line) } } if (line ~ /:[0-9]+/) print line } ' | sort -u } apply_blacklist() { local trackers="$1" local output="${trackers}" local line while IFS= read -r line; do [[ -z "${line}" ]] && continue [[ "${line}" =~ ^# ]] && continue output=$(printf '%s\n' "${output}" | grep -Fvx "${line}" || true) done < "${FILE_BLACKLIST}" printf '%s\n' "${output}" | sed '/^\s*$/d' | sort -u } # 从 tracker URL 中提取主机名或 IP。 # 支持以下形式: # - 域名: tracker.example.com:443 # - IPv4: 1.2.3.4:6969 # - IPv6: [2001:db8::1]:443 host_from_tracker() { local tracker="$1" local host_port host_port=$(printf '%s\n' "${tracker}" \ | sed -E 's#^[a-z]+://##' \ | sed -E 's#/.*$##') # 带括号的 IPv6,例如 [2001:db8::1]:443 if [[ "${host_port}" =~ ^\[.*\](:[0-9]+)?$ ]]; then printf '%s\n' "${host_port}" | sed -E 's#^\[([^]]+)\](:[0-9]+)?$#\1#' return 0 fi # 普通 host:port 场景,去掉尾部端口即可。 printf '%s\n' "${host_port}" | sed -E 's#:[0-9]+$##' } # 域名解析检查: # - IPv4 字面量和 IPv6 字面量直接保留 # - 域名使用 getent ahosts 检查是否能解析到有效地址 # getent 是 Ubuntu runner 自带工具 check_host_resolution() { local trackers="$1" local keep="" local invalid=0 local t host while IFS= read -r t; do [[ -z "${t}" ]] && continue host=$(host_from_tracker "${t}") # IPv4 和 IPv6 字面量直接放行,不做额外 DNS 解析。 if [[ "${host}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]] || [[ "${host}" == *:* ]]; then keep+=$'\n'"${t}" continue fi if getent ahosts "${host}" \ | awk '{print $1}' \ | grep -Ev '^(127\.|0\.0\.0\.0$|10\.|::1$|::$)' >/dev/null; then keep+=$'\n'"${t}" else invalid=$((invalid + 1)) fi done <<< "${trackers}" echo "Host invalid count: ${invalid}" >&2 printf '%s\n' "${keep}" | sed '/^\s*$/d' | sort -u } # 从 tracker URL 中提取 host 和 port。 # 输出格式固定为:hostport split_host_port() { local tracker="$1" local host_port host port host_port=$(printf '%s\n' "${tracker}" \ | sed -E 's#^[a-z]+://##' \ | sed -E 's#/.*$##') # IPv6 形式:[2001:db8::1]:443 if [[ "${host_port}" =~ ^\[([^]]+)\]:([0-9]+)$ ]]; then host=$(printf '%s\n' "${host_port}" | sed -E 's#^\[([^]]+)\]:([0-9]+)$#\1#') port=$(printf '%s\n' "${host_port}" | sed -E 's#^\[([^]]+)\]:([0-9]+)$#\2#') printf '%s\t%s\n' "${host}" "${port}" return 0 fi # 普通 host:port 形式 host=$(printf '%s\n' "${host_port}" | sed -E 's#:[0-9]+$##') port=$(printf '%s\n' "${host_port}" | grep -Eo ':[0-9]+$' | tr -d ':' || true) printf '%s\t%s\n' "${host}" "${port}" } # TCP 端口检查: # - 只跳过 UDP # 这一步会去掉 DNS 可解析但端口不可达的 tracker check_port_tcp() { local trackers="$1" local keep="" local invalid=0 local t host port split while IFS= read -r t; do [[ -z "${t}" ]] && continue # UDP 不做 TCP 检查,直接保留。 if [[ "${t}" =~ ^udp:// ]]; then keep+=$'\n'"${t}" continue fi split=$(split_host_port "${t}") host=$(printf '%s\n' "${split}" | cut -f1) port=$(printf '%s\n' "${split}" | cut -f2) if [[ -z "${host}" || -z "${port}" ]]; then invalid=$((invalid + 1)) continue fi if tcping "${host}" "${port}"; then keep+=$'\n'"${t}" else invalid=$((invalid + 1)) fi done <<< "${trackers}" echo "TCP invalid count: ${invalid}" >&2 printf '%s\n' "${keep}" | sed '/^\s*$/d' | sort -u } # 生成两种输出: # - 普通文本格式:每个 tracker 后面空一行 # - aria2 格式:单行逗号分隔 write_bt_and_aria2() { local trackers="$1" local out_bt="$2" local out_aria2="$3" printf '%s\n' "${trackers}" | sed '/^\s*$/d' | sed 's/$/\n/g' > "${out_bt}" grep -v '^\s*$' "${out_bt}" | paste -sd, - > "${out_aria2}" } # 拉取外部 tracker 源,并与 other.txt 中的内容合并。 collect_trackers() { local urls="$1" local combined local url body combined=$(cat "${FILE_OTHER}") while IFS= read -r url; do [[ -z "${url}" ]] && continue body=$(fetch_url "${url}") combined+=$'\n'"${body}" done <<< "${urls}" printf '%s\n' "${combined}" } # 生成 ALL 或 BEST 的统一流程: # 拉取 -> 清洗 -> 黑名单过滤 -> 解析检查 -> TCP 检查 -> 输出文件 build_set() { local mode="$1" local urls out_bt out_aria2 local trackers if [[ "${mode}" == "ALL" ]]; then echo "ALL:" urls="${TRACKERSLIST_ALL_URL}" out_bt="${FILE_ALL}" out_aria2="${FILE_ALL_ARIA2}" else echo "BEST:" urls="${TRACKERSLIST_BEST_URL}" out_bt="${FILE_BEST}" out_aria2="${FILE_BEST_ARIA2}" fi trackers=$(collect_trackers "${urls}" | normalize_and_filter) trackers=$(apply_blacklist "${trackers}") trackers=$(check_host_resolution "${trackers}") trackers=$(check_port_tcp "${trackers}") write_bt_and_aria2 "${trackers}" "${out_bt}" "${out_aria2}" } # 先生成全量和精选集合。 build_set ALL build_set BEST # 从 ALL 中再拆分出 HTTP/HTTPS 集合。 grep -v '^\s*$' "${FILE_ALL}" \ | sed -e 's/^[ ]*//g' -e 's/[ ]*$//g' -e 's/\r//g' \ | grep -E '^http(s)?:' \ | sed 's/$/\n/g' > "${FILE_HTTP}" # 从 ALL 中拆分出非 HTTP/HTTPS、非 WS/WSS 的集合。 grep -v '^\s*$' "${FILE_ALL}" \ | sed -e 's/^[ ]*//g' -e 's/[ ]*$//g' -e 's/\r//g' \ | grep -vE '^http(s)?:' \ | grep -vE '^ws(s)?:' \ | sed 's/$/\n/g' > "${FILE_NOHTTP}" # 同步生成 aria2 版本文件。 grep -v '^\s*$' "${FILE_HTTP}" | paste -sd, - > "${FILE_HTTP_ARIA2}" grep -v '^\s*$' "${FILE_NOHTTP}" | paste -sd, - > "${FILE_NOHTTP_ARIA2}" - name: Update README and export stats # 更新 README 文件并统计数据 shell: bash run: | set -Eeuo pipefail # 统一使用北京时间作为 README 中展示的日期。 UPDATE=$(TZ='Asia/Shanghai' date '+%Y-%m-%d') # 统计各输出文件中的有效 tracker 数量。 ALL_NUM=$(grep -v '^\s*$' all.txt | wc -l) BEST_NUM=$(grep -v '^\s*$' best.txt | wc -l) HTTP_NUM=$(grep -v '^\s*$' http.txt | wc -l) NOHTTP_NUM=$(grep -v '^\s*$' nohttp.txt | wc -l) # 更新英文 README。 sed -i -E "s|^### Updated: .*|### Updated: ${UPDATE}|" README.md sed -i -E "s|^- \*\*BEST Tracker list:\*\* \([0-9]+ trackers\).*$|- **BEST Tracker list:** (${BEST_NUM} trackers) |" README.md sed -i -E "s|^- \*\*ALL Tracker list:\*\* \([0-9]+ trackers\).*$|- **ALL Tracker list:** (${ALL_NUM} trackers) |" README.md sed -i -E "s|^- \*\*HTTP\(S\) Tracker list:\*\* \([0-9]+ trackers\).*$|- **HTTP(S) Tracker list:** (${HTTP_NUM} trackers) |" README.md sed -i -E "s|^- \*\*No HTTP Tracker list:\*\* \([0-9]+ trackers\).*$|- **No HTTP Tracker list:** (${NOHTTP_NUM} trackers) |" README.md # 更新中文 README。 sed -i -E "s|^### 更新时间: .*|### 更新时间: ${UPDATE}|" README-ZH.md sed -i -E "s|^- \*\*精选列表:\*\*\([0-9]+ 个\).*$|- **精选列表:**(${BEST_NUM} 个) |" README-ZH.md sed -i -E "s|^- \*\*完整列表:\*\*\([0-9]+ 个\).*$|- **完整列表:**(${ALL_NUM} 个) |" README-ZH.md sed -i -E "s|^- \*\*HTTP\(S\) 列表:\*\*\([0-9]+ 个\).*$|- **HTTP(S) 列表:**(${HTTP_NUM} 个) |" README-ZH.md sed -i -E "s|^- \*\*无 HTTP 列表:\*\*\([0-9]+ 个\).*$|- **无 HTTP 列表:**(${NOHTTP_NUM} 个) |" README-ZH.md echo "[$(TZ='Asia/Shanghai' date '+%Y/%m/%d %H:%M:%S')] ALL数量:${ALL_NUM} 个,BEST数量:${BEST_NUM} 个,HTTP(S)数量:${HTTP_NUM} 个,NoHTTP数量:${NOHTTP_NUM} 个。" - name: Commit and push when changed # 如有修改则提交推送 shell: bash env: BITBUCKET_TOKEN: ${{ secrets.BITBUCKET_TOKEN }} run: | set -Eeuo pipefail # 设置提交身份为 GitHub Actions 的机器人账号。 git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" # 只有存在文件变更时才提交,避免空提交失败。 git add -A if git diff --cached --quiet; then echo "No changes to commit." exit 0 fi # 提交并推送到当前触发运行的分支。 UPDATE=$(TZ='Asia/Shanghai' date '+%Y-%m-%d') git commit -m "${UPDATE}" # 1) 推送到 GitHub(origin) git push origin "HEAD:${GITHUB_REF_NAME}" # 2) 推送到 Bitbucket(镜像) git remote add bitbucket "https://x-bitbucket-api-token-auth:${BITBUCKET_TOKEN}@bitbucket.org/xiu2/trackerslistcollection.git" git push bitbucket "HEAD:${GITHUB_REF_NAME}"