""" Bilibili uploader using biliup library """ import json import asyncio from pathlib import Path from typing import Optional, List, Dict, Any from datetime import datetime from concurrent.futures import ThreadPoolExecutor try: from biliup.plugins.bili_webup import BiliBili, Data BILIUP_AVAILABLE = True except ImportError: BILIUP_AVAILABLE = False from loguru import logger from .base_uploader import BaseUploader # Thread pool for running sync biliup code _executor = ThreadPoolExecutor(max_workers=2) class BilibiliUploader(BaseUploader): """Bilibili video uploader using biliup library""" def __init__( self, title: str, file_path: str, tags: List[str], publish_date: Optional[datetime] = None, account_file: Optional[str] = None, description: str = "", tid: int = 122, # 分区ID: 122=国内原创 copyright: int = 1 # 1=原创, 2=转载 ): """ Initialize Bilibili uploader Args: tid: Bilibili category ID (default: 122 for 国内原创) copyright: 1 for original, 2 for repost """ super().__init__(title, file_path, tags, publish_date, account_file, description) self.tid = tid self.copyright = copyright if not BILIUP_AVAILABLE: raise ImportError( "biliup library not installed. Please run: pip install biliup" ) async def main(self) -> Dict[str, Any]: """ Upload video to Bilibili Returns: dict: Upload result """ # Run sync upload in thread pool to avoid asyncio.run() conflict loop = asyncio.get_event_loop() return await loop.run_in_executor(_executor, self._upload_sync) def _upload_sync(self) -> Dict[str, Any]: """Synchronous upload logic (runs in thread pool)""" try: # 1. Load cookie data if not self.account_file or not Path(self.account_file).exists(): logger.error(f"[B站] Cookie 文件不存在: {self.account_file}") return { "success": False, "message": "Cookie 文件不存在,请先登录", "url": None } with open(self.account_file, 'r', encoding='utf-8') as f: cookie_data = json.load(f) # Convert simple cookie format to biliup format if needed if 'cookie_info' not in cookie_data and 'SESSDATA' in cookie_data: # Transform to biliup expected format cookie_data = { 'cookie_info': { 'cookies': [ {'name': k, 'value': v} for k, v in cookie_data.items() ] }, 'token_info': { 'access_token': cookie_data.get('access_token', ''), 'refresh_token': cookie_data.get('refresh_token', '') } } logger.info("[B站] Cookie格式已转换") # 2. Prepare video data data = Data() data.copyright = self.copyright data.title = self.title data.desc = self.description or f"标签: {', '.join(self.tags)}" data.tid = self.tid data.set_tag(self.tags) data.dtime = self._get_timestamp(self.publish_date) logger.info(f"[B站] 开始上传: {self.file_path.name}") logger.info(f"[B站] 标题: {self.title}") logger.info(f"[B站] 定时发布: {'是' if data.dtime > 0 else '否'}") # 3. Upload video with BiliBili(data) as bili: # Login with cookies bili.login_by_cookies(cookie_data) bili.access_token = cookie_data.get('access_token', '') # Upload file (3 threads, auto line selection) video_part = bili.upload_file( str(self.file_path), lines='AUTO', tasks=3 ) video_part['title'] = self.title data.append(video_part) # Submit ret = bili.submit() # Debug: log full response logger.debug(f"[B站] API响应: {ret}") if ret.get('code') == 0: # Try multiple keys for bvid (API may vary) bvid = ret.get('data', {}).get('bvid') or ret.get('bvid', '') aid = ret.get('data', {}).get('aid') or ret.get('aid', '') if bvid: logger.success(f"[B站] 上传成功: {bvid}") return { "success": True, "message": "发布成功,待审核" if data.dtime == 0 else "已设置定时发布", "url": f"https://www.bilibili.com/video/{bvid}" } elif aid: logger.success(f"[B站] 上传成功: av{aid}") return { "success": True, "message": "发布成功,待审核" if data.dtime == 0 else "已设置定时发布", "url": f"https://www.bilibili.com/video/av{aid}" } else: # No bvid/aid but code=0, still consider success logger.warning(f"[B站] 上传返回code=0但无bvid/aid: {ret}") return { "success": True, "message": "发布成功,待审核", "url": None } else: error_msg = ret.get('message', '未知错误') logger.error(f"[B站] 上传失败: {error_msg} (完整响应: {ret})") return { "success": False, "message": f"上传失败: {error_msg}", "url": None } except Exception as e: logger.exception(f"[B站] 上传异常: {e}") return { "success": False, "message": f"上传异常: {str(e)}", "url": None }