218 lines
7.5 KiB
Python
Executable File
218 lines
7.5 KiB
Python
Executable File
import requests
|
||
import time
|
||
import json
|
||
import random
|
||
from pathlib import Path
|
||
|
||
# ================= 配置区域 =================
|
||
COOKIE_FILE = Path("./cookies.json")
|
||
TARGET_SEASON_ID = 7196643 # 目标合集 ID
|
||
ASCENDING_ORDER = True # True: 最早发布的在前面 (1, 2, 3...)
|
||
# ===========================================
|
||
|
||
def extract_cookie_from_list(cookie_list):
|
||
"""从列表结构中提取 SESSDATA 和 bili_jct"""
|
||
sessdata = ""
|
||
bili_jct = ""
|
||
for item in cookie_list:
|
||
if item.get("name") == "SESSDATA":
|
||
sessdata = item.get("value")
|
||
elif item.get("name") == "bili_jct":
|
||
bili_jct = item.get("value")
|
||
return sessdata, bili_jct
|
||
|
||
def load_cookies(file_path):
|
||
"""智能从 json 文件加载 cookies"""
|
||
if not file_path.exists():
|
||
print(f"[!] 错误: 找不到文件 {file_path}")
|
||
exit(1)
|
||
|
||
try:
|
||
with open(file_path, 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
|
||
sessdata = ""
|
||
bili_jct = ""
|
||
|
||
if isinstance(data, list):
|
||
sessdata, bili_jct = extract_cookie_from_list(data)
|
||
elif isinstance(data, dict):
|
||
if "cookie_info" in data and "cookies" in data["cookie_info"]:
|
||
sessdata, bili_jct = extract_cookie_from_list(data["cookie_info"]["cookies"])
|
||
elif "cookies" in data and isinstance(data["cookies"], list):
|
||
sessdata, bili_jct = extract_cookie_from_list(data["cookies"])
|
||
else:
|
||
sessdata = data.get("SESSDATA", "")
|
||
bili_jct = data.get("bili_jct", "")
|
||
|
||
if not sessdata or not bili_jct:
|
||
print("[!] 错误: cookies.json 中未找到 SESSDATA 或 bili_jct")
|
||
exit(1)
|
||
|
||
return sessdata, bili_jct
|
||
except Exception as e:
|
||
print(f"[!] 解析 cookies.json 失败: {e}")
|
||
exit(1)
|
||
|
||
# 初始化 Cookie
|
||
SESSDATA, BILI_JCT = load_cookies(COOKIE_FILE)
|
||
|
||
print(f"[*] SESSDATA 读取成功: {SESSDATA[:4]}...{SESSDATA[-4:]}")
|
||
print(f"[*] bili_jct 读取成功: {BILI_JCT[:4]}...{BILI_JCT[-4:]}")
|
||
|
||
HEADERS = {
|
||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||
"Cookie": f"SESSDATA={SESSDATA}; bili_jct={BILI_JCT}",
|
||
"Content-Type": "application/json"
|
||
}
|
||
|
||
def get_section_id_by_season(season_id):
|
||
"""查找合集对应的小节ID"""
|
||
print(f"[*] 正在查找合集 ID {season_id} 的小节信息...")
|
||
url = "https://member.bilibili.com/x2/creative/web/seasons"
|
||
|
||
page = 1
|
||
while True:
|
||
params = {"pn": page, "ps": 30, "order": "", "sort": ""}
|
||
resp = requests.get(url, params=params, headers=HEADERS)
|
||
data = resp.json()
|
||
|
||
if data["code"] != 0:
|
||
print(f"[!] 获取合集列表失败: {data['message']}")
|
||
if data["code"] == -101:
|
||
print("[!] 提示: 账号未登录,请检查 cookies.json")
|
||
return None
|
||
|
||
seasons_list = data["data"]["seasons"]
|
||
if not seasons_list:
|
||
break
|
||
|
||
for s_obj in seasons_list:
|
||
s_info = s_obj["season"]
|
||
if s_info["id"] == season_id:
|
||
title = s_info["title"]
|
||
print(f"[*] 找到合集: 《{title}》")
|
||
|
||
if "sections" in s_obj and "sections" in s_obj["sections"]:
|
||
first_section = s_obj["sections"]["sections"][0]
|
||
sec_id = first_section["id"]
|
||
sec_title = first_section["title"]
|
||
print(f"[*] 锁定小节: [{sec_title}] (Section ID: {sec_id})")
|
||
return sec_id
|
||
else:
|
||
print("[!] 该合集下没有发现小节结构。")
|
||
return None
|
||
|
||
page += 1
|
||
time.sleep(0.5)
|
||
|
||
print(f"[!] 未在您的账号中找到 Season ID: {season_id}")
|
||
return None
|
||
|
||
def get_video_publish_time(bvid):
|
||
"""
|
||
获取视频发布时间
|
||
[FIXED] 增加了 headers 参数,防止 B 站拦截请求
|
||
"""
|
||
url = "https://api.bilibili.com/x/web-interface/view"
|
||
params = {"bvid": bvid}
|
||
try:
|
||
# !!!关键修改:这里必须带上 headers !!!
|
||
resp = requests.get(url, params=params, headers=HEADERS)
|
||
data = resp.json()
|
||
|
||
if data["code"] == 0:
|
||
return data["data"]["pubdate"], data["data"]["title"]
|
||
else:
|
||
# 打印具体错误原因
|
||
print(f"\n[!] 获取视频 {bvid} 失败: code={data['code']}, msg={data['message']}")
|
||
return 0, "Unknown"
|
||
except Exception as e:
|
||
print(f"\n[!] 请求异常: {e}")
|
||
return 0, "Unknown"
|
||
|
||
def sort_videos(section_id):
|
||
# 1. 获取小节内视频
|
||
url_get = "https://member.bilibili.com/x2/creative/web/season/section"
|
||
resp = requests.get(url_get, params={"id": section_id}, headers=HEADERS)
|
||
res_json = resp.json()
|
||
|
||
if res_json["code"] != 0:
|
||
print(f"[!] API 错误: {res_json['message']}")
|
||
return
|
||
|
||
section_info = res_json["data"]["section"]
|
||
episodes = res_json["data"]["episodes"]
|
||
|
||
if not episodes:
|
||
print("[!] 合集内无视频。")
|
||
return
|
||
|
||
total = len(episodes)
|
||
print(f"[*] 获取到 {total} 个视频,开始查询发布时间...")
|
||
|
||
video_list = []
|
||
success_count = 0
|
||
|
||
for idx, ep in enumerate(episodes):
|
||
# 随机延迟 0.2 ~ 0.5 秒,比固定延迟更安全
|
||
time.sleep(random.uniform(0.2, 0.5))
|
||
|
||
bvid = ep["bvid"]
|
||
pubdate, title = get_video_publish_time(bvid)
|
||
|
||
# 简单的进度显示
|
||
date_str = "Fail/Unknown"
|
||
if pubdate != 0:
|
||
date_str = time.strftime('%Y-%m-%d', time.localtime(pubdate))
|
||
success_count += 1
|
||
|
||
print(f" [{idx+1}/{total}] {title[:15]:<15} -> {date_str}")
|
||
|
||
video_list.append({
|
||
"id": ep["id"],
|
||
"title": ep["title"] if title == "Unknown" else title, # 优先使用 API 查到的全名
|
||
"pubdate": pubdate
|
||
})
|
||
|
||
if success_count == 0:
|
||
print("[!] 错误: 所有视频时间查询均失败,终止排序以免数据混乱。")
|
||
return
|
||
|
||
# 2. 排序
|
||
print("[*] 正在计算排序顺序...")
|
||
video_list.sort(key=lambda x: x['pubdate'], reverse=not ASCENDING_ORDER)
|
||
|
||
# 3. 提交
|
||
print("[*] 正在提交新的排序列表...")
|
||
sorts_payload = [{"id": v["id"], "sort": i+1} for i, v in enumerate(video_list)]
|
||
|
||
payload = {
|
||
"section": {
|
||
"id": section_info["id"],
|
||
"seasonId": section_info["seasonId"],
|
||
"title": section_info["title"],
|
||
"type": section_info["type"]
|
||
},
|
||
"sorts": sorts_payload
|
||
}
|
||
|
||
url_edit = f"https://member.bilibili.com/x2/creative/web/season/section/edit?csrf={BILI_JCT}"
|
||
try:
|
||
resp_submit = requests.post(url_edit, json=payload, headers=HEADERS)
|
||
result = resp_submit.json()
|
||
|
||
if result["code"] == 0:
|
||
print(f"\n[SUCCESS] 合集《{section_info['title']}》排序更新成功!")
|
||
else:
|
||
print(f"\n[FAIL] 更新失败: {result['message']}")
|
||
except Exception as e:
|
||
print(f"\n[!] 提交时发生网络错误: {e}")
|
||
|
||
if __name__ == "__main__":
|
||
print("--- Bilibili 合集自动排序工具 (v2.0 fixed) ---")
|
||
|
||
target_section_id = get_section_id_by_season(TARGET_SEASON_ID)
|
||
|
||
if target_section_id:
|
||
sort_videos(target_section_id) |