Initial commit: sanitize repository for remote push

This commit is contained in:
theshy
2026-03-21 01:36:28 +08:00
commit 3925cb508f
21 changed files with 3357 additions and 0 deletions

172
add_to_collection.py Executable file
View File

@ -0,0 +1,172 @@
import json
import time
import requests
import re
import shutil
import subprocess
import random
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from logger import get_system_logger, log_exception
# ================= 配置区域 =================
SESSION_DIR = Path("./session")
COOKIE_FILE = Path("./cookies.json")
CHECK_INTERVAL = 5
# 合集 ID 配置
SEASON_ID_A = 7196643 # 合集 A (同名视频)
SEASON_ID_B = 7196624 # 合集 B (Upload切片)
# 自动寻找 biliup
BILIUP_PATH = shutil.which("biliup") or "biliup"
# 初始化日志
logger = get_system_logger("add_to_collection.py")
# ===========================================
class BiliCollectionClient:
def __init__(self):
self.load_cookies()
self.session = requests.Session()
self.session.headers.update({
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"Referer": "https://member.bilibili.com/platform/upload-manager/distribution"
})
def load_cookies(self):
if not COOKIE_FILE.exists():
raise FileNotFoundError(f"Cookies 文件不存在: {COOKIE_FILE}")
with open(COOKIE_FILE, "r", encoding="utf-8") as f:
data = json.load(f)
self.cookies = {c["name"]: c["value"] for c in data.get("cookie_info", {}).get("cookies", [])} if "cookie_info" in data else data
self.csrf = self.cookies.get("bili_jct")
def get_video_info(self, bvid):
url = "https://api.bilibili.com/x/web-interface/view"
try:
self.session.cookies.update(self.cookies)
res = self.session.get(url, params={"bvid": bvid}, timeout=10).json()
if res["code"] == 0:
d = res["data"]
return {"aid": d["aid"], "cid": d["cid"], "title": d["title"], "charging_pay": 0}
except Exception as e:
logger.error(f"获取视频信息失败: {e}")
return None
def resolve_section_id(self, sid):
url = "https://member.bilibili.com/x2/creative/web/seasons"
try:
self.session.cookies.update(self.cookies)
res = self.session.get(url, params={"pn": 1, "ps": 50}).json()
for s in res.get("data", {}).get("seasons", []):
if s.get("season", {}).get("id") == sid:
return s.get("sections", {}).get("sections", [])[0]["id"]
except: pass
return None
def add_videos_batch(self, section_id, episodes):
if not episodes: return True
# 频率控制
wait = random.uniform(5.0, 10.0)
logger.info(f"☕ 模拟人工操作,等待 {wait:.2f}s 后提交到合集...")
time.sleep(wait)
url = "https://member.bilibili.com/x2/creative/web/season/section/episodes/add"
params = {"csrf": self.csrf}
try:
res = self.session.post(url, params=params, json={"sectionId": section_id, "episodes": episodes}).json()
return res["code"] == 0
except Exception as e:
log_exception(logger, e, "批量添加合集异常")
return False
class CollectionHandler(FileSystemEventHandler):
def __init__(self, client, sid_a, sid_b):
self.client = client
self.sid_a = sid_a
self.sid_b = sid_b
self.ansi_escape = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
def on_created(self, event):
# 监听文件夹创建或 bvid.txt 创建
if event.is_directory or event.src_path.endswith("bvid.txt"):
self.process_all()
def process_all(self):
recent = self.fetch_biliup_list()
pending_a, pending_b = [], []
for folder in SESSION_DIR.iterdir():
if not folder.is_dir(): continue
# 任务 A: 同名视频 -> 合集 A
flag_a = folder / "collection_a_done.flag"
if self.sid_a and not flag_a.exists():
bvid = self.match_bvid(folder.name, recent)
if bvid:
info = self.client.get_video_info(bvid)
if info: pending_a.append((folder, info))
# 任务 B: 切片视频 -> 合集 B
flag_b = folder / "collection_b_done.flag"
txt = folder / "bvid.txt"
if self.sid_b and not flag_b.exists() and txt.exists():
try:
bvid = txt.read_text(encoding='utf-8').strip()
if bvid.startswith("BV"):
info = self.client.get_video_info(bvid)
if info: pending_b.append((folder, info))
except: pass
# 批量执行提交
if pending_a:
if self.client.add_videos_batch(self.sid_a, [i[1] for i in pending_a]):
for f, _ in pending_a: (f / "collection_a_done.flag").touch()
logger.info(f"合集 A 更新完成: {len(pending_a)}个任务")
if pending_b:
if self.client.add_videos_batch(self.sid_b, [i[1] for i in pending_b]):
for f, _ in pending_b: (f / "collection_b_done.flag").touch()
logger.info(f"合集 B 更新完成: {len(pending_b)}个任务")
def fetch_biliup_list(self):
try:
res = subprocess.run([BILIUP_PATH, "list"], capture_output=True, text=True, encoding='utf-8')
clean_out = self.ansi_escape.sub("", res.stdout)
return [{"bvid": l.split()[0], "title": "".join(l.split()[1:])} for l in clean_out.splitlines() if l.startswith("BV")]
except: return []
def match_bvid(self, name, vlist):
n = lambda x: re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', '', x).lower()
target = n(name)
for v in vlist:
vn = n(v['title'])
if target in vn or vn in target: return v['bvid']
return None
def main():
logger.info("="*50)
logger.info("合集监控模块启动")
logger.info("="*50)
client = BiliCollectionClient()
sid_a = client.resolve_section_id(SEASON_ID_A) if SEASON_ID_A > 0 else None
sid_b = client.resolve_section_id(SEASON_ID_B) if SEASON_ID_B > 0 else None
handler = CollectionHandler(client, sid_a, sid_b)
handler.process_all() # 初始扫描
observer = Observer()
observer.schedule(handler, str(SESSION_DIR), recursive=False)
observer.start()
try:
while True:
time.sleep(CHECK_INTERVAL)
except KeyboardInterrupt:
observer.stop()
observer.join()
if __name__ == "__main__":
main()