Initial commit: sanitize repository for remote push
This commit is contained in:
172
add_to_collection.py
Executable file
172
add_to_collection.py
Executable 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()
|
||||
Reference in New Issue
Block a user