172 lines
6.4 KiB
Python
Executable File
172 lines
6.4 KiB
Python
Executable File
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() |