netease-music-cli/scripts/refresh-urls.sh
2026-10-02 07:26:46 +08:00

100 lines
4.6 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# refresh-urls.sh — 队列地址保活
#
# 网易云的播放直链是限时签名 URL:实测取出来约 5 分钟就 403
# (路径里的时间戳 + vuutv/authSecret 签名,服务端一核,过期就拒绝)。
# 长队列预取一批地址,放到后半段会集体失效,听感是「歌还在放、就是没声音」。
#
# 本脚本由入口的 keepalive_start 拉起(一次只跑一个),每 90 秒看一次:
# 这批地址取出来多久了(urls_at)× mpv 播到第几首(playlist-pos)
# 一到 4 分钟,就把「当前这首之后还没播的」批量重取,原地换掉 m3u 里对应的行。
# mpv 是按需读取下一首的,所以换 URL 不影响正在放的那首。
# 队列被换掉 / 停止 / mpv 退出,脚本自己收工。
set -u
SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
LOCAL_DIR="${NETEASE_MUSIC_STATE_DIR:-${PI_AGENT_DIR:-$HOME/.pi/agent}/local/skills/netease-music}"
QUEUE="$LOCAL_DIR/queue.json"
M3U="$LOCAL_DIR/queue.m3u8"
URLS_AT="$LOCAL_DIR/urls_at"
SOCK="$LOCAL_DIR/mpv.sock"
API="$SKILL_DIR/scripts/netease-api.mjs"
QUALITY="$(python3 -c 'import json,sys
try: print(json.load(open(sys.argv[1])).get("quality") or "lossless")
except Exception: print("lossless")' "$LOCAL_DIR/config.json" 2>/dev/null)"
QUALITY="${NETEASE_MUSIC_QUALITY:-${PI_SKILL_NETEASE_MUSIC_QUALITY:-$QUALITY}}"
COOKIE="${NETEASE_MUSIC_COOKIE:-${PI_SKILL_NETEASE_MUSIC_COOKIE:-$(python3 -c 'import json,sys
try: print(json.load(open(sys.argv[1])).get("cookie") or "")
except Exception: print("")' "$LOCAL_DIR/config.json" 2>/dev/null)}}"
log() { printf '%s %s\n' "$(date '+%H:%M:%S')" "$*"; }
mpv_query() { # mpv_query <属性>:拿不到就打印空
python3 - "$SOCK" "$1" <<'PY' 2>/dev/null
import json, socket, sys
try:
s = socket.socket(socket.AF_UNIX); s.settimeout(3); s.connect(sys.argv[1])
s.sendall(json.dumps({"command": ["get_property", sys.argv[2]], "request_id": 1}).encode() + b"\n")
r = json.loads(s.recv(65536).decode())
print(r.get("data") if r.get("error") == "success" else "")
except Exception:
print("")
PY
}
refetch_from() { # refetch_from <起始序号 0-based>:重取这一首之后的地址,原地换 m3u
local from="$1" tmp_ids tmp_urls n
tmp_ids="$(mktemp)"; tmp_urls="$(mktemp)"
python3 - "$QUEUE" "$from" > "$tmp_ids" <<'PY'
import json, sys
q = json.load(open(sys.argv[1]))
for i, s in enumerate(q["songs"]):
if i >= int(sys.argv[2]):
print(s["id"])
PY
[ -s "$tmp_ids" ] || { rm -f "$tmp_ids" "$tmp_urls"; return 1; }
n="$(wc -l < "$tmp_ids" | tr -d ' ')"
log "重取地址:从第 $((from + 1)) 首起共 $n 首"
while read -r chunk; do
NETEASE_COOKIE="$COOKIE" node "$API" url $chunk --level "$QUALITY" 2>/dev/null \
| python3 -c 'import json,sys
for it in (json.load(sys.stdin).get("items") or []):
print("%s\t%s" % (it.get("id"), it.get("url") or ""))' >> "$tmp_urls"
done < <(python3 -c 'import sys
ids = [l.strip() for l in sys.stdin if l.strip()]
for i in range(0, len(ids), 50): print(" ".join(ids[i:i+50]))' < "$tmp_ids")
python3 - "$M3U" "$QUEUE" "$tmp_urls" "$from" <<'PY'
import json, sys
mpath, qpath, upath, start = sys.argv[1], sys.argv[2], sys.argv[3], int(sys.argv[4])
urls = {}
for line in open(upath):
sid, _, url = line.rstrip("\n").partition("\t")
if url:
urls[sid] = url
order = [str(s["id"]) for s in json.load(open(qpath))["songs"]]
lines = open(mpath).read().split("\n")
head = [i for i, l in enumerate(lines) if l.startswith("#EXTINF")]
changed = 0
for pos, i in enumerate(head):
if pos < start or pos >= len(order) or i + 1 >= len(lines):
continue
u = urls.get(order[pos])
if u:
lines[i + 1] = u
changed += 1
open(mpath, "w").write("\n".join(lines))
print("换成新地址的条数:%d/%d" % (changed, len(head) - start))
PY
log "重取完成(从第 $((from + 1)) 首起)"
rm -f "$tmp_ids" "$tmp_urls"
}
log "地址保活启动"
while :; do
sleep 90
[ -n "$(mpv_query idle-active)" ] || { log "mpv 没了,收工"; exit 0; }
[ -s "$QUEUE" ] || { log "队列没了,收工"; exit 0; }
age=$(( $(date +%s) - $(cat "$URLS_AT" 2>/dev/null || echo 0) ))
[ "$age" -lt 240 ] && continue
# 「当前放到第几首」按歌认:mpv 开了 shuffle 时 playlist-pos 不是 queue.json 的序号
title="$(mpv_query media-title)"
curl="$(mpv_query path)"
pos="$(python3 "$SKILL_DIR/scripts/lib/mpv-map.py" index "$QUEUE" "$LOCAL_DIR/urls.txt" "$title" "$curl" 2>/dev/null)"
case "$pos" in ''|*[!0-9]*) pos=0 ;; esac
if refetch_from "$pos"; then date +%s > "$URLS_AT"; else log "重取失败,90 秒后再试"; fi
done