import json import os import subprocess import re import requests import time import random import base64 import shutil import hashlib import logging from typing import Dict from tqdm import tqdm # 配置日志 logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('bilibili_tool.log', encoding='utf-8'), logging.StreamHandler() ] ) logger = logging.getLogger(__name__) def convert_browser_cookies(browser_cookies: list) -> Dict: """转换浏览器导出的cookies格式为简单的键值对""" cookies = {} for cookie in browser_cookies: if isinstance(cookie, dict) and 'name' in cookie and 'value' in cookie: cookies[cookie['name']] = cookie['value'] return cookies def load_cookies_from_file() -> Dict: """从文件加载cookies""" cookie_files = ['cookies.txt', 'bilibili_cookies.json'] # 直接从当前目录读取 for cookie_file in cookie_files: if os.path.exists(cookie_file): try: with open(cookie_file, 'r', encoding='utf-8') as f: content = f.read().strip() cookies = {} # 尝试作为JSON解析 try: json_data = json.loads(content) if isinstance(json_data, list): cookies = convert_browser_cookies(json_data) elif isinstance(json_data, dict): cookies = json_data except json.JSONDecodeError: # 如果不是JSON,尝试作为Netscape格式或普通cookie字符串解析 if content.startswith('# Netscape HTTP Cookie File'): # Netscape格式 for line in content.split('\n'): line = line.strip() if not line or line.startswith('#'): continue try: fields = line.split('\t') if len(fields) >= 7: name, value = fields[5:7] cookies[name] = value except: continue else: # 尝试作为普通cookie字符串解析 cookies = parse_cookie_string(content) if cookies: # 检查必要的cookie是否存在 required_cookies = ['SESSDATA', 'bili_jct', 'DedeUserID'] missing_cookies = [cookie for cookie in required_cookies if cookie not in cookies] if missing_cookies: print(f"警告: 缺少必要的cookies: {', '.join(missing_cookies)}") print("这些cookie是必需的,否则某些功能可能无法正常工作") print("请确保你的cookies.txt中包含这些必要的cookie") return {} print(f"已从 {cookie_file} 加载cookies:") for name, value in cookies.items(): print(f"找到cookie: {name}") return cookies else: print(f"警告: 无法从 {cookie_file} 加载有效的cookies") except Exception as e: print(f"警告: 读取 {cookie_file} 时出错: {str(e)}") return {} def parse_cookie_string(cookie_str: str) -> Dict: """解析浏览器直接复制的cookie字符串""" cookies = {} try: # 处理URL编码 def decode_value(value): # 处理特殊的URL编码字符 replacements = { '%2C': ',', '%2F': '/', '%3A': ':', '%2B': '+', '%3D': '=', '%3B': ';' } for encoded, decoded in replacements.items(): value = value.replace(encoded, decoded) return value # 分割cookie字符串 cookie_pairs = cookie_str.split(';') for pair in cookie_pairs: if '=' in pair: name, value = pair.strip().split('=', 1) name = name.strip() value = decode_value(value.strip()) cookies[name] = value # 如果cookies非空,打印找到的cookie数量 if cookies: print(f"字符串中解析出 {len(cookies)} 个cookies") except Exception as e: print(f"解析cookie字符串时出错: {str(e)}") return {} return cookies def get_cookies_from_user() -> Dict: """获取用户输入的cookies""" print("请输入B站cookies (支持JSON格式、浏览器导出格式或直接复制的cookie字符串):") cookies_str = input().strip() # 首先尝试解析JSON try: cookies_data = json.loads(cookies_str) if isinstance(cookies_data, list): cookies = convert_browser_cookies(cookies_data) else: cookies = cookies_data except json.JSONDecodeError: # 如果不是JSON,尝试解析为cookie字符串 cookies = parse_cookie_string(cookies_str) # 检查是否获取到cookies if cookies: print("成功获取到以下cookies:") for name in cookies.keys(): print(f"- {name}") return cookies else: print("未能获取到有效的cookies") return {} def save_cookies(cookies: Dict): """保存cookies为Netscape格式""" with open('cookies.txt', 'w', encoding='utf-8') as f: # 直接保存到当前目录 f.write("# Netscape HTTP Cookie File\n") f.write("# https://curl.haxx.se/rfc/cookie_spec.html\n") f.write("# This is a generated file! Do not edit.\n\n") # 为所有cookie写入Netscape格式的行 for name, value in cookies.items(): # 某些特殊cookie需要特殊处理 if name in ['SESSDATA']: f.write(f"#HttpOnly_.bilibili.com\tTRUE\t/\tTRUE\t1735689600\t{name}\t{value}\n") else: f.write(f".bilibili.com\tTRUE\t/\tFALSE\t1735689600\t{name}\t{value}\n") print(f"cookies已保存到cookies.txt,包含以下字段:{', '.join(cookies.keys())}") def find_ffmpeg() -> str: """查找ffmpeg可执行文件的路径""" try: # 直接检查系统是否能执行ffmpeg命令 result = subprocess.run(['ffmpeg', '-version'], capture_output=True, text=True) if result.returncode == 0: print("找到系统ffmpeg") return 'ffmpeg' # 直接返回命令名称 except Exception: pass # 如果直接执行失败,尝试完整路径 ffmpeg_path = r'C:\Program Files\ffmpeg\bin\ffmpeg.exe' if os.path.isfile(ffmpeg_path): print(f"找到ffmpeg: {ffmpeg_path}") return ffmpeg_path print("警告:未找到ffmpeg,请确保ffmpeg已正确安装添加到系统PATH中") return '' def convert_to_mp3(input_file: str) -> bool: """将音频文件转换为MP3格式""" if not os.path.exists(input_file): print(f"错误:找不到文件 {input_file}") return False output_file = os.path.splitext(input_file)[0] + '.mp3' try: # 构建ffmpeg命令 cmd = [ 'ffmpeg', '-i', input_file, '-acodec', 'libmp3lame', '-q:a', '0', # 最高质量 '-threads', '4', # 使用4个线程 '-progress', 'pipe:1', # 输出进度信息到stdout '-nostats', # 不输出额外统计信息 output_file ] print(f"正在转换为MP3: {os.path.basename(input_file)}") # 创建进度条 with tqdm(total=100, desc="转换进度", ncols=80, bar_format='{desc}: {percentage:3.1f}%|{bar}| [{elapsed}<{remaining}]') as pbar: process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True ) # 读取并更新进度 last_percent = 0 while True: line = process.stdout.readline() if not line and process.poll() is not None: break # 从输出中提取时间信息 if line.startswith('out_time='): time_str = line.split('=')[1].strip() try: # 解析时间格式 HH:MM:SS.mmm time_parts = time_str.split(':') if len(time_parts) == 3: hours = float(time_parts[0]) minutes = float(time_parts[1]) seconds = float(time_parts[2]) current_time = hours * 3600 + minutes * 60 + seconds # 这里需要知道总时长才能计算准确进度 # 暂时使用一个估算值,实际应该先获取文件时长 estimated_duration = 180 # 假设3分钟,实际应该动态获取 percent = min(100, max(0, int((current_time / estimated_duration) * 100))) # 更新进度条 if percent > last_percent: pbar.update(percent - last_percent) last_percent = percent except: pass # 确保进度条到达100% if last_percent < 100: pbar.update(100 - last_percent) # 检查转换结果 if process.returncode == 0: print(f"\n转换完成:{os.path.basename(output_file)}") # 询问是否删除原始文件 choice = input("是否删除原始音频文件?(y/n) [y]: ").lower() if not choice or choice == 'y': # 直接回车或输入y都删除 os.remove(input_file) print("原始文件已删除") return True else: error = process.stderr.read() print(f"转换失败!错误信息:\n{error}") return False except Exception as e: print(f"转换过程中发生错误: {str(e)}") return False def download_audio(url_or_bvid: str, cookies: Dict): """下载音频""" save_cookies(cookies) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } try: # 获取视频BV号并显示信息 bvid = extract_bvid(url_or_bvid) if bvid: pages = get_video_info(bvid, headers, show_info=False) # 添加参数控制是否显示信息 if len(pages) > 1: choice = input("\n是否显示分P列表?(y/n) [n]: ").lower() if choice == 'y': print("\n分P列表:") # 显示视频基本信息 get_video_info(bvid, headers, show_info=True) # 此时显示完整信息 # 显示分P信息 for page in pages: duration_min = page['duration'] // 60 duration_sec = page['duration'] % 60 print(f"{page['page']}. {page['part']} ({duration_min}分{duration_sec}秒)") print("\n请输入要下载的分P编号(用空格分隔,例如:1 3 5)") print("支持范围选择(例如:1-5 7 9-11)") choice = input("请选择 [1]: ").strip() # 如果直接回车,默认下载第一P if not choice: choice = "1" # 解析选择的分P selected_pages = set() for part in choice.split(): if '-' in part: start, end = map(int, part.split('-')) selected_pages.update(range(start, end + 1)) else: selected_pages.add(int(part)) # 过滤无效的分P编号 valid_pages = sorted([p for p in selected_pages if 1 <= p <= len(pages)]) if valid_pages: print(f"\n将下载以下分P: {', '.join(map(str, valid_pages))}") for page_num in valid_pages: page = pages[page_num - 1] page_url = f"https://www.bilibili.com/video/{bvid}?p={page_num}" print(f"\n开始下载P{page_num}: {page['part']}") download_single_audio(page_url, cookies) else: print("未选择有效的分P编号,将下载P1") download_single_audio(url_or_bvid, cookies) else: # 单P视频直接下载 download_single_audio(url_or_bvid, cookies) else: print("请输入有效的B站视频链接或BV号!") except Exception as e: print(f"下载过程出错: {str(e)}") def get_user_info(cookies: Dict) -> None: """获取并显示用户信息""" print("\n" + "="*50) print("# 用户信息") print("="*50) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } try: # 获取用户导航信息 nav_url = 'https://api.bilibili.com/x/web-interface/nav' nav_resp = requests.get(nav_url, cookies=cookies, headers=headers) nav_data = nav_resp.json() if nav_data['code'] == 0: data = nav_data['data'] print(f"用户名: {data['uname']}") print(f"用户ID: {data['mid']}") print(f"邮箱验证: {'已验证' if data['email_verified'] else '未验证'}") print(f"手机验证: {'已验证' if data['mobile_verified'] else '未验证'}") print(f"硬币数: {data['money']}") print(f"B币余额: {data['wallet']['bcoin_balance']}") print(f"道德值: {data['moral']}") # 等级信息 level_info = data['level_info'] print(f"\n等级信息:") print(f"当前等级: {level_info['current_level']}") print(f"当前经验: {level_info['current_exp']}") print(f"下一级所需经验: {level_info['next_exp']}") # 会员信息 vip = data['vip'] print(f"\n会员信息:") vip_type_map = {0: "无", 1: "月度大会员", 2: "年度大会员"} print(f"会员类型: {vip_type_map.get(vip['type'], '未知')}") if vip['status'] == 1: due_date = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(vip['due_date']/1000)) print(f"到期时间: {due_date}") # 认证信息 if data['official']['type'] != -1: print(f"\n认证信息:") print(f"认证类型: {'个人认证' if data['official']['type'] == 0 else '机构认证'}") if data['official']['title']: print(f"认证信息: {data['official']['title']}") if data['official']['desc']: print(f"认证描述: {data['official']['desc']}") # 获取关注和粉丝数 stat_url = f'https://api.bilibili.com/x/relation/stat?vmid={data["mid"]}' stat_resp = requests.get(stat_url, headers=headers) stat_data = stat_resp.json() if stat_data['code'] == 0: stat_info = stat_data['data'] print(f"\n社交信息:") print(f"关注数: {stat_info['following']}") print(f"粉丝数: {stat_info['follower']}") # 获取今日投币经验 coin_exp_url = 'https://api.bilibili.com/x/web-interface/coin/today/exp' coin_exp_resp = requests.get(coin_exp_url, cookies=cookies, headers=headers) coin_exp_data = coin_exp_resp.json() if coin_exp_data['code'] == 0: today_exp = coin_exp_data['data'] max_coin_exp = 50 print(f"\n投币信息:") print(f"今日投币经验: {today_exp}/{max_coin_exp}") remaining_coins = (max_coin_exp - today_exp) // 10 if remaining_coins > 0: print(f"今日还可投币: {remaining_coins}个") else: print(f"获取用户信息失败:{nav_data['message']}") except Exception as e: print(f"获取用户信���时出错: {str(e)}") print("="*50 + "\n") def extract_bvid_from_file(filename: str) -> list: """从文件中取URL并提取BV号""" bvids = [] try: with open(filename, 'r', encoding='utf-8') as f: for line in f: url = line.strip() if url: # 使用正则表达式匹配BV号 match = re.search(r'BV[a-zA-Z0-9]{10}', url) if match: bvid = match.group() bvids.append(bvid) except Exception as e: print(f"读取文件出错: {str(e)}") return bvids def batch_coin(cookies: Dict) -> None: """批量投币功能""" print("\n" + "="*50) print("# 批量投币") print("="*50) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } try: # 先检查今日投币情况 coin_exp_url = 'https://api.bilibili.com/x/web-interface/coin/today/exp' coin_exp_resp = requests.get(coin_exp_url, cookies=cookies, headers=headers) coin_exp_data = coin_exp_resp.json() if coin_exp_data['code'] == 0: today_exp = coin_exp_data['data'] max_coin_exp = 50 remaining_coins = (max_coin_exp - today_exp) // 10 if remaining_coins <= 0: print("今日已完成投币任务!") return print(f"今日还可投币: {remaining_coins}个") # 检查bvid.txt文件是否存在 if not os.path.exists('bvid.txt'): print("错误:找不到bvid.txt文件!") print("请创建bvid.txt文件并在其中每行写入一个视频链接") return # 从文件中读取��取BV号 bvids = extract_bvid_from_file('bvid.txt') if not bvids: print("错误:无法从文件中提取有效的BV号!") return print(f"从文件中提取到 {len(bvids)} 个BV号:") # 获取每个视频的信息 for bvid in bvids: get_video_info(bvid, headers) # 询问是否继续投币 choice = input("\n是否开始批量投币?(y/n) [y]: ").lower() if not choice or choice == 'y': # 直接回车或输入y都继续 # 获取csrf令牌 csrf = cookies.get('bili_jct') if not csrf: print("错误:无法获取csrf令牌!") return # 投API coin_url = 'https://api.bilibili.com/x/web-interface/coin/add' # 创建进度条 with tqdm(total=min(remaining_coins, len(bvids)), desc="投币进度") as pbar: for bvid in bvids: if remaining_coins <= 0: print("\n今日投币任务完成!") break # 构造请求数据 data = { 'bvid': bvid, 'multiply': 1, # 投1个币 'select_like': 1, # 同时点赞 'csrf': csrf } try: # 发送投币请求 response = requests.post(coin_url, data=data, cookies=cookies, headers=headers) result = response.json() if result['code'] == 0: print(f"\n成功给视频 {bvid} 投币") remaining_coins -= 1 pbar.update(1) else: print(f"\n给视频 {bvid} 投币失败:{result['message']}") print("停止投币操作") break # 投币失败就停止 # 添加延时,避免请求过快 time.sleep(2) except Exception as e: print(f"\n投币过程出错: {str(e)}") print("停止投币操作") break # 出错也停止 print("\n批量投币完成!") # 获取当前硬币数 nav_url = 'https://api.bilibili.com/x/web-interface/nav' nav_resp = requests.get(nav_url, cookies=cookies, headers=headers) nav_data = nav_resp.json() if nav_data['code'] == 0: current_coins = nav_data['data']['money'] print(f"当前剩余硬币数: {current_coins}") except Exception as e: print(f"批量投币时出错: {str(e)}") print("="*50 + "\n") def show_menu() -> str: """显示功能菜单""" print("\n" + "="*50) print("B站工具箱") print("="*50) print("1. 举报麦片 (默认)") # 移到第一位并标记为默认 print("2. 批量点赞视频") print("3. 批量投币") print("4. 下载视频音频") print("5. 获取视频BV号") print("6. 查看用户信息") print("7. bv转av") print("8. 获取视频弹幕") print("9. 获取视频热评") print("10. 查看IP地理位置") print("11. 直播") print("12. 弹幕点赞") print("13. 老粉计划") print("14. 举报视频") print("15. 搜索视频") print("16. 退出程序") print("="*50) choice = input("请选择功能 (1-16) [1]: ").strip() if not choice: return '1' # 直接回车默认选择1号功能(举报麦片) if choice in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16']: return choice print("无效的选择,使用默认选项1") return '1' def show_live_menu() -> str: """显示直播功能子菜单""" print("\n" + "="*50) print("直播功能") print("="*50) print("1. 获取直�������������������������������������间信息") print("2. 获取用户�����������播状态") print("3. 获取主播信息") print("4. 获取多个直播间信息") print("5. 批量查询直播状态") print("6. 获取直播间历史弹��") print("7. 获取直播间播放信息") print("8. 获取直播间主播详情") print("9. ���回主菜单") print("="*50) choice = input("请选择功能 (1-9) [1]: ").strip() if not choice: return '1' if choice in ['1', '2', '3', '4', '5', '6', '7', '8', '9']: return choice print("无效的选择,使用默认选项1") return '1' def extract_bvid(url_or_bvid: str) -> str: """从URL或直接输入的BV��中提取BV""" # 如果直接输入的是BV号 if url_or_bvid.startswith('BV'): return url_or_bvid # 否则从URL中提取 bv_pattern = r'BV[a-zA-Z0-9]{10}' match = re.search(bv_pattern, url_or_bvid) if match: return match.group() return '' def get_video_bvids() -> None: """获取视频BV号并保存到文件""" bvids = [] headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } print("请输入B站视频URL�����BV号,每行一���,输入q��束:") while True: input_text = input().strip() if input_text.lower() == 'q': break # 如果输入的是BV号 if input_text.startswith('BV'): bvid = input_text bvids.append(bvid) get_video_info(bvid, headers) continue # 如果输入的是URL if not input_text.startswith(('https://www.bilibili.com', 'https://b23.tv')): print("请输入有效的B站视频链或BV号!") continue bvid = extract_bvid(input_text) if bvid: bvids.append(bvid) get_video_info(bvid, headers) else: print("无法从URL中提���BV号!") if bvids: with open('bvid.txt', 'w', encoding='utf-8') as f: f.write('\n'.join(bvids)) print(f"已保存 {len(bvids)} 个BV号到 bvid.txt") def get_video_info(bvid: str, headers: dict, show_info: bool = True) -> list: """获取并显示视频信息""" try: video_url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}' response = requests.get(video_url, headers=headers) data = response.json() if data['code'] == 0: info = data['data'] if show_info: # 只在需要时显示信息 print(f"\n视频信息 - {bvid}:") print(f"标题: {info['title']}") print(f"UP主: {info['owner']['name']}") print(f"播放量: {info['stat']['view']}") print(f"点赞数: {info['stat']['like']}") print(f"投币数: {info['stat']['coin']}") print(f"收藏数: {info['stat']['favorite']}") print(f"分享数: {info['stat']['share']}") comment_count = get_comment_count(bvid) print(f"评论数: {comment_count}") print(f"发布时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(info['pubdate']))}") # 修复括号 print(f"视频简介: {info['desc'][:100]}..." if len(info['desc']) > 100 else f"视频简介: {info['desc']}") print("-" * 50) return info.get('pages', []) except Exception as e: print(f"获取视频 {bvid} 信息失败: {str(e)}") return [] def batch_like(cookies: Dict) -> None: """批量点赞功能""" print("\n" + "="*50) print("# 批量点赞") print("="*50) headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } try: # 检查bvid.txt文件是��存在 if not os.path.exists('bvid.txt'): print("错误:找不到bvid.txt文件!") print("请创建bvid.txt文件并在其中每行写入一个视频链接") return # 从文件中读取并提取BV号 bvids = extract_bvid_from_file('bvid.txt') if not bvids: print("错误:无法从文件中提取有效的BV号!") return print(f"从文件中提取到 {len(bvids)} 个BV号:") # 获取每个视频的信息 for bvid in bvids: get_video_info(bvid, headers) # 询问是否继续点赞 choice = input("\n是否开���批量点赞?(y/n) [y]: ").lower() if not choice or choice == 'y': # 获csrf令牌 csrf = cookies.get('bili_jct') if not csrf: print("错误:无法���取csrf令牌!") return # 点赞API like_url = 'https://api.bilibili.com/x/web-interface/archive/like' # 创建进度条 with tqdm(total=len(bvids), desc="点赞进度") as pbar: for bvid in bvids: # 构造请求数据 data = { 'bvid': bvid, 'like': 1, # 1表示点赞,2表示取消��赞 'csrf': csrf } try: # 发送点赞请求 response = requests.post(like_url, data=data, cookies=cookies, headers=headers) result = response.json() if result['code'] == 0: print(f"\n成功给视频 {bvid} 点赞") pbar.update(1) else: print(f"\n给频 {bvid} 点赞失败{result['message']}") print("��止点赞操作") break # 点������������������失败就停�� # 添加延时,避免请求过��� time.sleep(2) except Exception as e: print(f"\n点赞过程出错: {str(e)}") print("停止点赞操作") break # 出错也停止 print("\n批量点赞完成!") except Exception as e: print(f"批量点赞时出错: {str(e)}") print("="*50 + "\n") def download_single_audio(url: str, cookies: Dict): """下载单个音频""" max_retries = 3 retry_count = 0 while retry_count < max_retries: try: cmd = [ 'yt-dlp', '--cookies', 'cookies.txt', '-f', 'ba[ext=m4a]/ba', # 优先选择m4a格式 '--no-playlist', '--no-check-certificates', '--progress', '--newline', '--socket-timeout', '30', # 添加超时设置 '--retries', '3', # 添加重试设置 '-o', os.path.join("音频", "%(title)s.%(ext)s"), url ] print("开始下载音频...") process = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, encoding='utf-8', errors='replace', bufsize=1, universal_newlines=True ) downloaded_file = None pbar = None total_size = None # 用于解析下载进度的正则表达式 progress_pattern = re.compile(r'\[download\]\s+(\d+(?:\.\d+)?)%\s+of\s+~?\s*(\d+(?:\.\d+)?)([KMG]?)iB.*?ETA\s+([\d:]+|Unknown)') while True: output = process.stdout.readline() if output == '' and process.poll() is not None: break if output: output = output.strip() # 获取文件名 if '[download] Destination:' in output: downloaded_file = output.split('[download] Destination:', 1)[1].strip() elif 'has already been downloaded' in output: downloaded_file = output.split('[download] ', 1)[1].split(' has already', 1)[0].strip() # 解析进度信息 match = progress_pattern.search(output) if match: percentage, size, unit, eta = match.groups() # 计算总大小(转换为字节) if total_size is None: multiplier = {'K': 1024, 'M': 1024**2, 'G': 1024**3} total_size = int(float(size) * multiplier[unit]) # 创建进度条 pbar = tqdm( total=total_size, unit='B', unit_scale=True, desc="下载进度", ncols=80, bar_format='{desc}: {percentage:3.1f}%|{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}]' ) # 更新进度条 if pbar: current = int(total_size * float(percentage) / 100) pbar.n = current pbar.refresh() # 其他输出信息 elif not output.startswith('[download]'): print(output) # 关闭进度条 if pbar: pbar.close() # 查下载结果 if process.returncode == 0: print("\n下载完成!") if downloaded_file and os.path.exists(downloaded_file): print(f"找到音频文件: {downloaded_file}") if input("是否转换为MP3格式?(y/n) [y]: ").lower() in ['', 'y']: if find_ffmpeg(): convert_to_mp3(downloaded_file) else: print("未找到ffmpeg,无法转换为MP3格式") else: print(f"警告:无法找到下载的文件") m4a_files = [f for f in os.listdir('.') if f.endswith('.m4a')] if m4a_files: print(f"在当前目录找到��下.m4a文件:") for i, file in enumerate(m4a_files, 1): print(f"{i}. {file}") choice = input("请选择要转换的文件编号(输入q取消)[1]: ") if not choice: choice = '1' if choice.isdigit() and 1 <= int(choice) <= len(m4a_files): if find_ffmpeg(): convert_to_mp3(m4a_files[int(choice)-1]) else: print("未找到ffmpeg,无法转换为MP3格式") return True # 下载成功 else: error = process.stderr.read() print(f"下载失败!错误信息:\n{error}") retry_count += 1 if retry_count < max_retries: print(f"正在进行第 {retry_count + 1} 次重试...") time.sleep(2) # 等待2秒后重试 else: print("已达到最大重试次数,下载失败") return False except FileNotFoundError: print("错误:请先安装 yt-dlp") print("可以使用以下命令安装:") print("pip install yt-dlp") return False except Exception as e: print(f"发生错误: {str(e)}") retry_count += 1 if retry_count < max_retries: print(f"正在进行第 {retry_count + 1} 次重试...") time.sleep(2) else: print("已达到最大重试次数,下载失败") return False def create_required_directories(): """创建程序所需的文件夹""" required_dirs = [ "弹幕", # 存放视频弹幕文件 os.path.join("弹幕", "直播弹幕"), # 存放直播弹幕文件 "评论", # 存放评论文件 "音频", # 存放下载的音频文件 "举报", # 存放举报相关文件 os.path.join("举报", "图片"), # 存放举报附件图片 os.path.join("举报", "记录"), # 存放举报记录 ] for directory in required_dirs: if not os.path.exists(directory): try: os.makedirs(directory) print(f"创建文件夹: {directory}") except Exception as e: print(f"创建文件夹 {directory} 时出错: {str(e)}") def main(): # 创建必要的文件夹 create_required_directories() # 检查cookies.txt是否存在 if not os.path.exists('cookies.txt'): # 直接���查当前目录 print("错误:找���到cookies.txt��件!") print("请确保cookies.txt文件存在且包含有效的cookies信息") return # 加载cookies cookies = load_cookies_from_file() if not cookies: print("��误:无法从cookies.txt加载有效的cookies") return # 获取并显示用户信息 get_user_info(cookies) # 创建LiveRoom实例 live_room = LiveRoom() while True: choice = show_menu() if choice == '1': # 举报麦片 show_maipi_menu(cookies) elif choice == '2': # ���量点赞 batch_like(cookies) elif choice == '3': # 批量投币 batch_coin(cookies) elif choice == '4': # 下载视频音频 while True: print("\n请输入B站视频URL或BV号 (输入q返回主菜单):") input_text = input().strip() if input_text.lower() == 'q': break # 如果输入的是BV号 if input_text.startswith('BV'): download_audio(input_text, cookies) continue # 如果输���的是URL if not input_text.startswith(('https://www.bilibili.com', 'https://b23.tv')): print("请输入有效的B站视频链接或BV号!") continue download_audio(input_text, cookies) elif choice == '5': # 获取视频BV号 get_video_bvids() elif choice == '6': # 查看用户信息 get_user_info(cookies) elif choice == '7': # 新增的功能:bv转av bvid = input("请输入BV号: ").strip() if bvid.startswith('BV'): av_number = bv_to_av(bvid) print(f"对应AV号是: {av_number}") else: print("请输入有效的BV号!") elif choice == '8': # 获取视频弹幕 input_text = input("请输入视频URL或BV号: ").strip() # 如果输入的��BV号 if input_text.startswith('BV'): bvid = input_text # 如果输入的是URL elif input_text.startswith(('https://www.bilibili.com', 'https://b23.tv')): bvid = extract_bvid(input_text) else: print("请输���有效的B站视频链接或BV号!") continue if bvid: try: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } video_url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}' response = requests.get(video_url, headers=headers) data = response.json() if data['code'] == 0: cid = data['data']['cid'] segment = input("请输入要获取第几个6分钟段的弹幕 (1-n) [1]: ").strip() if not segment: segment = "1" if segment.isdigit(): get_danmaku(cid, bvid, int(segment), cookies) # 传入cookies参数 else: print("请输入有效的数字!") else: print(f"获取视频信息失败:{data['message']}") except Exception as e: print(f"获取弹幕时出错: {str(e)}") else: print("无法��别BV号") elif choice == '9': # 获取视频热评 input_text = input("请输入视频URL或BV号: ").strip() # 如果输入的BV号 if input_text.startswith('BV'): bvid = input_text # 如果输入的是URL elif input_text.startswith(('https://www.bilibili.com', 'https://b23.tv')): bvid = extract_bvid(input_text) else: print("请输入有效的B站视频链接或BV号!") continue if bvid: # 获取每页评论数 ps = input("请输入每页显示的评论数 (1-49) [20]: ").strip() if not ps: ps = "20" if ps.isdigit() and 1 <= int(ps) <= 49: get_hot_comments(bvid, int(ps)) else: print("请输入有效的数字!") else: print("无法识别BV号!") elif choice == '10': # 查看IP地理位置 get_ip_location() elif choice == '11': # 进入直播子菜单 while True: live_choice = show_live_menu() if live_choice == '1': room_input = input("请输入直播间号或直播间链接: ").strip() room_id = extract_room_id(room_input) if 'live.bilibili.com' in room_input else room_input if room_id.isdigit(): live_room.get_room_info(room_id) else: print("请输入有效的直播间号或链接!") input("\n按回车继续...") # 添加这一行 continue # 继续显示直播菜单 elif live_choice == '2': uid_input = input("请输入用户UID或个人空间链接: ").strip() if 'space.bilibili.com' in uid_input: uid = extract_uid(uid_input) else: uid = uid_input if uid.isdigit(): live_room.get_user_live_status(uid) else: print("请输入有效的用户UID或个人空间链接!") input("\n按回车继续...") # 添加这一行 continue # 继续显示直播菜单 elif live_choice == '3': uid_input = input("请输入主播UID或直播间链接: ").strip() if 'live.bilibili.com' in uid_input: uid = extract_uid(uid_input) else: uid = uid_input if uid.isdigit(): live_room.get_anchor_info(uid) else: print("请输入有效的主播UID或直播间链接!") input("\n按回车继续...") # 添加这一行 continue # 继���显示直播菜单 elif live_choice == '4': room_input = input("请输入直播间号或直播间链接: ").strip() live_room.get_room_base_info([room_input]) input("\n按回车继续...") # 添加这一行 continue # 继续显示直播菜单 elif live_choice == '5': live_room.get_batch_live_status([room_input]) input("\n按回车继续...") # 添加���一行 continue # 继续��示直播菜单 elif live_choice == '6': room_input = input("请输入直播间号或直播间链接: ").strip() live_room.get_history_danmaku(room_input) input("\n按回车继续...") # 添加这一行 continue # 继续显示直播菜单 elif live_choice == '7': room_input = input("请输入直播间号或直播间链接: ").strip() live_room.get_play_info(room_input) input("\n按回车继续...") # 添加这一行 continue # 继续显示直播菜单 elif live_choice == '8': room_input = input("请输入直播间号或直播间链接: ").strip() live_room.get_room_anchor_info(room_input) input("\n按回车继续...") # 添加这��行 continue # 继续显示直播菜单 elif live_choice == '9': break # 返回主菜单 elif choice == '12': # 弹幕点赞功能 input_text = input("请输入视频URL或BV号: ").strip() # 如果输入的是BV号 if input_text.startswith('BV'): bvid = input_text # �������输入����URL elif input_text.startswith(('https://www.bilibili.com', 'https://b23.tv')): bvid = extract_bvid(input_text) else: print("请输入有效的B���视频链接或BV号���") continue if bvid: try: headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } video_url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}' response = requests.get(video_url, headers=headers) data = response.json() if data['code'] == 0: cid = data['data']['cid'] segment = input("请输入要获取第几个6分钟段的弹幕 (1-n) [1]: ").strip() if not segment: segment = "1" if segment.isdigit(): get_danmaku(cid, bvid, int(segment), cookies) # 传入cookies参数 else: print("请输入有效的数字!") else: print(f"获��视��信息失败:{data['message']}") except Exception as e: print(f"获取���幕时出错: {str(e)}") else: print("无法识别BV号") elif choice == '13': # 原来退出选项改为12 print("\n=== 老粉计划 ===") print("1. 加老粉") print("2. 发送老粉留言") print("3. 返回主菜单") fan_choice = input("\n请选择功能 (1-3) [1]: ").strip() if not fan_choice: fan_choice = "1" if fan_choice == "1": up_input = input("\n请输入UP主UID或空间链接: ").strip() if 'space.bilibili.com' in up_input: up_mid = extract_uid(up_input) else: up_mid = up_input if up_mid.isdigit(): join_old_fan_plan(up_mid, cookies) else: print("请输入有效的UP主UID或空间链接!") elif fan_choice == "2": up_input = input("\n请输入UP主UID或空间链接: ").strip() if 'space.bilibili.com' in up_input: up_mid = extract_uid(up_input) else: up_mid = up_input if up_mid.isdigit(): message = input("请输入要发送的留言: ").strip() if message: send_old_fan_message(up_mid, message, cookies) else: print("留言内容不能为空!") else: print("请输���有效的UP主UID或空间链接!") input("\n按回车继续...") # 添加这行防止直接退出 continue # 继续显示主菜单 elif choice == '14': # 举报视频 input_text = input("请输入要举报的视频URL或BV号: ").strip() # 如果输入的是BV号 if input_text.startswith('BV'): bvid = input_text # 如果输入的是URL elif input_text.startswith(('https://www.bilibili.com', 'https://b23.tv')): bvid = extract_bvid(input_text) else: print("请输入有效的B站视频链接或BV号!") continue if bvid: show_report_menu(bvid, cookies) else: print("无法识别BV号!") elif choice == '15': show_search_menu(cookies) # 传入cookies elif choice == '16': print("\n感谢使用!再见!") break def bv_to_av(bvid: str) -> str: """将BV号转换为AV号""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } try: # 使用B站API获取视信息 video_url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}' response = requests.get(video_url, headers=headers) data = response.json() if data['code'] == 0: # 从返回数据中获���aid(AV号) av_number = data['data']['aid'] return str(av_number) # 直接返回数字部分 else: print(f"获取AV号失败:{data['message']}") return "" except Exception as e: print(f"转换过程出错: {str(e)}") return "" def get_color_name(hex_color: str) -> str: """将十六进制颜色代码转换为��色名称""" # 常见B站弹幕颜色映射 color_map = { '#ffffff': '白色', '#000000': '黑色', '#ff0000': '红色', '#00ff00': '绿色', '#0000ff': '蓝色', '#ffff00': '黄色', '#ff7600': '橙色', '#ff00ff': '粉色', '#00ffff': '青色', '#64c8ff': '浅��', '#ff8080': '��红', '#66ccff': '天蓝', '#ff69b4': '粉红', '#ff69b4': '热粉红', '#98fb98': '浅绿', '#ffa500': '橙色', '#f0e68c': '卡其色', '#87ceeb': '天蓝色', '#dda0dd': '梅红色', '#ff7f50': '珊瑚色', } return color_map.get(hex_color.lower(), hex_color) def like_danmaku(dmid: str, oid: str, cookies: Dict) -> bool: """点赞弹幕 Args: dmid (str): 弹幕ID oid (str): 视频cid cookies (Dict): cookies信息 Returns: bool: 是否点赞成功 """ headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } try: # 获取csrf令牌 csrf = cookies.get('bili_jct') if not csrf: print("错误:无法获取csrf令牌!") return False # 构造请求数据 data = { 'dmid': dmid, 'oid': oid, 'op': 1, # 1表示点赞,2表示取消点赞 'platform': 'web_player', 'csrf': csrf } # 发送点赞请求 url = 'https://api.bilibili.com/x/v2/dm/thumbup/add' response = requests.post(url, data=data, cookies=cookies, headers=headers) result = response.json() if result['code'] == 0: print(f"��功给弹幕 {dmid} 点赞") return True else: error_messages = { -101: "账号未登录", -111: "csrf校验失败", -400: "请求错误", 36106: "���弹幕已被删除", 36805: "该视频禁止点赞弹幕", 65004: "取消赞失败,未点赞过", 65006: "已赞过该弹幕" } error_msg = error_messages.get(result['code'], result['message']) print(f"点赞弹幕失败:{error_msg}") return False except Exception as e: print(f"点赞弹幕时出错: {str(e)}") return False def get_danmaku(cid: str, bvid: str, segment_index: int = 1, cookies: Dict = None) -> None: """获取视频弹幕""" try: import google.protobuf.text_format as text_format from bilibili.community.service.dm.v1 import dm_pb2 as Danmaku except ImportError: print("错误:缺少必要的库,请先安装:") print("pip install protobuf") print("同时需要编译proto文件生成dm_pb2.py") return url = 'https://api.bilibili.com/x/v2/dm/web/seg.so' params = { 'type': 1, 'oid': cid, 'segment_index': segment_index } headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com' } try: resp = requests.get(url, params=params, headers=headers) if resp.status_code == 200: danmaku_seg = Danmaku.DmSegMobileReply() danmaku_seg.ParseFromString(resp.content) danmaku_list = [] print(f"\n获取到 {len(danmaku_seg.elems)} 条弹�����:") print("-" * 50) for elem in danmaku_seg.elems: seconds = elem.progress / 1000 minutes = int(seconds / 60) seconds = int(seconds % 60) mode_types = { 1: "普通弹幕", 2: "普通弹幕", 3: "普通弹幕", 4: "底部弹幕", 5: "顶部弹幕", 6: "逆向弹幕", 7: "高级弹幕", 8: "代码弹幕", 9: "BAS弹幕" } mode_type = mode_types.get(elem.mode, "未知类型") hex_color = f"#{elem.color:06x}" color_name = get_color_name(hex_color) danmaku_info = ( f"时间: {minutes:02d}:{seconds:02d}\n" f"内容: {elem.content}\n" f"类型: {mode_type}\n" f"颜色: {color_name} ({hex_color})\n" f"字号: {elem.fontsize}\n" f"发送时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(elem.ctime))}\n" f"弹幕ID: {elem.id}\n" # 添加弹幕ID显示 f"{'-' * 50}" ) # 只打印一次弹幕信息 print(danmaku_info) # 询问是否点赞 choice = input("是否点赞该弹幕?(y/n) [n]: ").lower() if choice == 'y': like_danmaku(str(elem.id), cid, cookies) danmaku_list.append(danmaku_info) # 询问是否保存到文件 if danmaku_list: choice = input("\n是否保存弹幕到文本文件?(y/n) [y]: ").lower() if not choice or choice == 'y': danmaku_dir = "弹幕" if not os.path.exists(danmaku_dir): os.makedirs(danmaku_dir) filename = f"{bvid} {time.strftime('%Y-%m-%d %H-%M-%S', time.localtime())} 弹��.txt" filepath = os.path.join(danmaku_dir, filename) try: with open(filepath, 'w', encoding='utf-8') as f: f.write('\n'.join(danmaku_list)) print(f"弹幕已保存到文件: {filepath}") except Exception as e: print(f"保存文件时出错: {str(e)}") else: print(f"获取弹幕失败: HTTP {resp.status_code}") except Exception as e: print(f"获取弹幕时出错: {str(e)}") print("���误详细信息:") import traceback traceback.print_exc() def get_comment_count(bvid: str) -> int: """获取视频评论总数""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.bilibili.com' } try: # 获取视频aid video_url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}' response = requests.get(video_url, headers=headers) data = response.json() if data['code'] == 0: aid = data['data']['aid'] # 获取评论总数 count_url = 'https://api.bilibili.com/x/v2/reply/count' params = {'type': 1, 'oid': aid} count_response = requests.get(count_url, params=params, headers=headers) count_data = count_response.json() return count_data['data']['count'] if count_data['code'] == 0 else 0 return 0 except: return 0 def get_hot_comments(bvid: str, ps: int = 20, pn: int = 1) -> None: """���取视频热门���论""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } try: # 首先取视频aid video_url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}' response = requests.get(video_url, headers=headers) data = response.json() if data['code'] == 0: aid = data['data']['aid'] # 获取热评 hot_url = 'https://api.bilibili.com/x/v2/reply/hot' params = { 'type': 1, # 视频评论区 'oid': aid, # 视频aid 'ps': ps, # 每页评论数 'pn': pn # 页码 } hot_response = requests.get(hot_url, params=params, headers=headers) hot_data = hot_response.json() if hot_data['code'] == 0: # 查data字段是否存在 if 'data' not in hot_data or hot_data['data'] is None: print("该视频暂无热评") return # 检查replies字段是否存在 if 'replies' not in hot_data['data'] or not hot_data['data']['replies']: print("该视频暂无热") return page_info = hot_data['data'].get('page', {'acount': 0}) replies = hot_data['data']['replies'] print(f"\n获取到 {len(replies)} 条热门评论(总评论数:{page_info.get('acount', 0)}):") print("="*50) for reply in replies: # 获取评论者信息 member = reply['member'] username = member['uname'] # 用户名 user_level = member['level_info']['current_level'] # 用户等级 user_id = member['mid'] # 用户ID # 获取评论内容 content = reply['content']['message'] # 获取点赞数 like_count = reply['like'] # 获取复数 reply_count = reply['rcount'] # 获取发布时间 ctime = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(reply['ctime'])) print(f"评论者: {username} (UID: {user_id})") print(f"用���等级: {user_level}") print(f"点赞数: {like_count}") print(f"回复数: {reply_count}") print(f"发布时间: {ctime}") print(f"评论内容: {content}") print("-"*50) # 询问是否保存到文 choice = input("\n是否保热评到文本件?(y/n) [y]: ").lower() if not choice or choice == 'y': # 创建评论文件夹(如果不存在) comment_dir = "评论" if not os.path.exists(comment_dir): os.makedirs(comment_dir) # 生成文件名(BV号 + 当前间 + 评论后) filename = f"{bvid} {time.strftime('%Y-%m-%d %H-%M-%S', time.localtime())} 评论.txt" # 完整文件路径 filepath = os.path.join(comment_dir, filename) try: with open(filepath, 'w', encoding='utf-8') as f: f.write(f"视频热门评论(总评论数:{page_info.get('acount', 0)})\n") f.write("="*50 + "\n") for reply in replies: member = reply['member'] f.write(f"��论者: {member['uname']} (UID: {member['mid']})\n") f.write(f"��户等级: {member['level_info']['current_level']}\n") f.write(f"点赞数: {reply['like']}\n") f.write(f"回复数: {reply['rcount']}\n") f.write(f"发布时���: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(reply['ctime']))}\n") f.write(f"评论内容: {reply['content']['message']}\n") f.write("-"*50 + "\n") print(f"热评已保存到文件: {filepath}") except Exception as e: print(f"保存文件出: {str(e)}") else: print(f"获取热评失败:{hot_data['message']}") else: print(f"获取视频信息失败:{data['message']}") except Exception as e: print(f"获取热评时出错: {str(e)}") print("请确保视频存在且有评论权限") def get_ip_location(): """获取IP地理位置信息""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } try: while True: print("\n请选择查询方式:") print("1. 查询当前IP") print("2. 查询指定IP") print("3. 返回主菜单") choice = input("请选择 [1]: ").strip() if not choice: choice = "1" if choice == "1": # 查询当前IP url = 'https://api.bilibili.com/x/web-interface/zone' response = requests.get(url, headers=headers) data = response.json() elif choice == "2": # 查询指定IP ip = input("请输入要查询的IP地址: ").strip() url = 'https://api.live.bilibili.com/ip_service/v1/ip_service/get_ip_addr' params = {'ip': ip} response = requests.get(url, params=params, headers=headers) data = response.json() elif choice == "3": break else: print("无效的选择!") continue if data['code'] == 0: ip_info = data['data'] print("\nIP地理位置信息:") print("="*50) print(f"IP地址: {ip_info['addr']}") print(f"国家/地区: {ip_info['country']}") if 'province' in ip_info and ip_info['province']: print(f"省/州: {ip_info['province']}") if 'city' in ip_info and ip_info['city']: print(f"城市: {ip_info['city']}") print(f"运营商: {ip_info['isp']}") if 'longitude' in ip_info and ip_info['longitude']: print(f"经度: {ip_info['longitude']}") if 'latitude' in ip_info and ip_info['latitude']: print(f"纬度: {ip_info['latitude']}") print("="*50) else: print(f"获取IP信息失败:{data.get('message', '') or data.get('msg', '')}") input("\n按回车继续...") except Exception as e: print(f"获取IP信息时出错: {str(e)}") def get_live_room_info(room_id: str) -> None: """获取直播间信息""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } try: url = 'https://api.live.bilibili.com/room/v1/Room/get_info' params = {'room_id': room_id} response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: room_info = data['data'] print("\n直播间信息:") print("="*50) print(f"主UID: {room_info['uid']}") print(f"直播间号: {room_info['room_id']}") if room_info['short_id'] != 0: print(f"直播间短号: {room_info['short_id']}") print(f"关注数: {room_info['attention']}") print(f"观看人数: {room_info['online']}") print(f"标题: {room_info['title']}") print(f"分区: {room_info['parent_area_name']} - {room_info['area_name']}") # 显示直播状态 status_map = { 0: "未开播", 1: "直播中", 2: "��播中" } status = status_map.get(room_info['live_status'], "未知状态") print(f"直播状态: {status}") if room_info['live_status'] == 1: print(f"开播时间: {room_info['live_time']}") if room_info['description']: print(f"\n房间简介: {room_info['description']}") # 显示认证信息 if room_info['new_pendants']['badge']: badge = room_info['new_pendants']['badge'] badge_type = { 'v_person': '个人认证', 'v_company': '企业认证' } print(f"\n认证类型: {badge_type.get(badge['name'], '未知认证')}") if badge['desc']: print(f"认证说明: {badge['desc']}") # 示热词 if room_info['hot_words']: print("\n房间热词:") for word in room_info['hot_words']: print(f"- {word}") print("="*50) else: print(f"获取直播间信息失败:{data['message']}") except Exception as e: print(f"获取直播间信息时出错: {str(e)}") def get_user_live_status(mid: str) -> None: """获取用户直播间状态""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } try: url = 'https://api.live.bilibili.com/room/v1/Room/getRoomInfoOld' params = {'mid': mid} response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: live_info = data['data'] print("\n用户直播间状态:") print("="*50) # 显示房间状态 room_status = "有直播间" if live_info['roomStatus'] == 1 else "无直播间" print(f"房间状态: {room_status}") if live_info['roomStatus'] == 1: # 显示直播状态 live_status_map = { 0: "未开播", 1: "直播中" } live_status = live_status_map.get(live_info['live_status'], "未知状态") print(f"直播状态: {live_status}") # 显示轮播状态 round_status = "轮播中" if live_info['roundStatus'] == 1 else "未轮播" print(f"轮播状态: {round_status}") print(f"直播间ID: {live_info['roomid']}") print(f"直播间标题: {live_info['title']}") print(f"观看人数: {live_info['online']}") print(f"直播间地址: {live_info['url']}") if live_info['cover']: print(f"封面地址: {live_info['cover']}") print("="*50) else: print(f"获取直播间状态失败:{data['message']}") except Exception as e: print(f"获取直播间状态时出错: {str(e)}") def get_room_init_info(room_id: str) -> None: """获取直��间初始化信息""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } try: url = 'https://api.live.bilibili.com/room/v1/Room/room_init' params = {'id': room_id} response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: room_info = data['data'] print("\n直播间初始化信息:") print("="*50) print(f"真实房间号: {room_info['room_id']}") print(f"短号: {room_info['short_id']}") print(f"主播UID: {room_info['uid']}") # 显示直播状态 status_map = { 0: "未开播", 1: "直播中", 2: "轮播中" } status = status_map.get(room_info['live_status'], "未知状态") print(f"直播状态: {status}") # 显示开播时间 if room_info['live_time'] > 0: live_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(room_info['live_time'])) print(f"开播时间: {live_time}") # 显示房间类型 special_type_map = { 0: "普通直播间", 1: "付费直播间", 2: "拜年祭直播间" } special_type = special_type_map.get(room_info['special_type'], "未知类型") print(f"房间类型: {special_type}") # 显示其他状态 print(f"是否隐藏: {'是' if room_info['is_hidden'] else '否'}") print(f"是否锁: {'是' if room_info['is_locked'] else '否'}") print(f"是否竖屏: {'是' if room_info['is_portrait'] else '否'}") print(f"是否加密: {'是' if room_info['encrypted'] else '否'}") if room_info['encrypted']: print(f"密码验证: {'已通过' if room_info['pwd_verified'] else '未通过'}") print("="*50) else: print(f"获取直播间信息失败:{data['message']}") except Exception as e: print(f"获取直播间信息时出错: {str(e)}") def get_anchor_info(uid: str) -> None: """获取主播信息""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } try: url = 'https://api.live.bilibili.com/live_user/v1/Master/info' params = {'uid': uid} response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: anchor_data = data['data'] info = anchor_data['info'] exp = anchor_data['exp'] print("\n主播信息:") print("="*50) print(f"UID: {info['uid']}") print(f"用户名: {info['uname']}") print(f"头像: {info['face']}") # 显示性别 gender_map = { -1: "保密", 0: "女", 1: "男" } gender = gender_map.get(info['gender'], "未知") print(f"性别: {gender}") # 显示认证信息 if info['official_verify']['type'] != -1: verify_type = "个人认证" if info['official_verify']['type'] == 0 else "机构认证" print(f"认证类型: {verify_type}") print(f"认证说明: {info['official_verify']['desc']}") # 显示等级信��� level_info = exp['master_level'] print(f"\n主播等级: {level_info['level']}") print(f"当前经验: {level_info['current'][1]}/{level_info['next'][1]}") print(f"升级还需: {level_info['next'][0] - level_info['current'][0]}") # 显示其他信息 print(f"\n粉丝数: {anchor_data['follower_num']}") print(f"直播间ID: {anchor_data['room_id']}") if anchor_data['medal_name']: print(f"粉丝勋章: {anchor_data['medal_name']}") if anchor_data['glory_count'] > 0: print(f"主播荣誉数: {anchor_data['glory_count']}") if anchor_data['pendant']: print(f"头像框: {anchor_data['pendant']}") # 显示公告 if anchor_data['room_news']['content']: print("\n主播公告:") print(f"内容: {anchor_data['room_news']['content']}") print(f"时间: {anchor_data['room_news']['ctime_text']}") print("="*50) else: print(f"获取主播信息失败:{data['message']}") except Exception as e: print(f"��������主播信息时出错: {str(e)}") def get_room_base_info(room_ids: list) -> None: """获取多个直播间基本信息""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } try: url = 'https://api.live.bilibili.com/xlive/web-room/v1/index/getRoomBaseInfo' params = { 'req_biz': 'web_room_componet', } # 添加多个房间号 for room_id in room_ids: params[f'room_ids'] = room_id response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: rooms_info = data['data']['by_room_ids'] for room_id, room_info in rooms_info.items(): print(f"\n直播间 {room_id} 基本信息:") print("="*50) print(f"主播: {room_info['uname']} (UID: {room_info['uid']}") print(f"真实房间号: {room_info['room_id']}") if room_info['short_id'] != 0: print(f"短号: {room_info['short_id']}") print(f"标题: {room_info['title']}") print(f"分区: {room_info['parent_area_name']} - {room_info['area_name']}") # 显示直播状态 status_map = { 0: "未开播", 1: "直播中", 2: "轮播中" } status = status_map.get(room_info['live_status'], "未知状态") print(f"直播状态: {status}") print(f"关注数: {room_info['attention']}") print(f"在线人数: {room_info['online']}") if room_info['live_time'] != "0000-00-00 00:00:00": print(f"开播时间: {room_info['live_time']}") if room_info['description']: print(f"\n房间简介: {room_info['description']}") if room_info['tags']: print(f"标签: {room_info['tags']}") if room_info['cover']: print(f"封面: {room_info['cover']}") if room_info['background']: print(f"背景: {room_info['background']}") print(f"直播间地址: {room_info['live_url']}") print("="*50) else: print(f"获取直播间信息失败:{data['message']}") except Exception as e: print(f"获取直播间信息时出错: {str(e)}") def get_batch_live_status(uids: list) -> None: """批量查询直播间状态""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } try: url = 'https://api.live.bilibili.com/room/v1/Room/get_status_info_by_uids' # 构造请求参数 params = {} for i, uid in enumerate(uids): params[f'uids[]'] = uid response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: rooms_info = data['data'] for uid, room_info in rooms_info.items(): print(f"\n主播 {room_info['uname']} (UID: {uid}) 的直播状态:") print("="*50) print(f"直播间标题: {room_info['title']}") print(f"房间号: {room_info['room_id']}") if room_info['short_id'] != 0: print(f"短号: {room_info['short_id']}") # 显示直播状态 status_map = { 0: "未开播", 1: "正在直播", 2: "轮播中" } status = status_map.get(room_info['live_status'], "未知状态") print(f"直播状态: {status}") print(f"在线人数: {room_info['online']}") if room_info['live_time'] > 0: live_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(room_info['live_time'])) print(f"开播时间: {live_time}") print(f"分区: {room_info['area_v2_parent_name']} - {room_info['area_v2_name']}") if room_info['tag_name']: print(f"标签: {room_info['tag_name']}") if room_info['tags']: print(f"定义标签: {room_info['tags']}") if room_info['cover_from_user']: print(f"封面: {room_info['cover_from_user']}") # 显示直播类型 broadcast_type = "手机直播" if room_info['broadcast_type'] == 1 else "普通直播" print(f"直播类型: {broadcast_type}") print("="*50) else: print(f"获取直播间状态失败:{data['message']}") except Exception as e: print(f"获取直播间状态时出错: {str(e)}") def get_live_history_danmaku(room_id: str) -> None: """获取直播间最近历史弹幕""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://live.bilibili.com' } seen_msgs = set() # 添加这行来定义 seen_msgs try: url = 'https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory' params = {'roomid': room_id} response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: admin_msgs = data['data']['admin'] # 管理员的弹幕 room_msgs = data['data']['room'] # 普通用户的弹幕 print("\n直播间历史弹幕:") print("="*50) if admin_msgs: print("\n管理员弹幕:") print("-"*30) for msg in admin_msgs: print(f"时间: {msg['timeline']}") print(f"用户: {msg['nickname']} (UID: {msg['uid']})") if msg['medal']: # 如果有粉丝勋章 print(f"勋章: {msg['medal'][1]} {msg['medal'][0]}级") print(f"内容: {msg['text']}") print("-"*30) if room_msgs: print("\n普通用户弹幕:") print("-"*30) for msg in room_msgs: print(f"时间: {msg['timeline']}") print(f"用户: {msg['nickname']} (UID: {msg['uid']})") if msg['medal']: # 如果有粉丝勋章 print(f"勋章: {msg['medal'][1]} {msg['medal'][0]}级") print(f"内容: {msg['text']}") print("-"*30) # 询问是否保存到文件 choice = input("\n是否保存历史弹幕到文本文件?(y/n) [y]: ").lower() if not choice or choice == 'y': # 创建直弹幕文件夹(如果不存在) danmaku_dir = os.path.join("弹幕", "直播弹幕") if not os.path.exists(danmaku_dir): os.makedirs(danmaku_dir) # 生成���件���(房间号 + 当前时间 + 历史弹幕后缀) filename = f"{room_id} {time.strftime('%Y-%m-%d %H-%M-%S', time.localtime())} 历史弹幕.txt" filepath = os.path.join(danmaku_dir, filename) try: with open(filepath, 'w', encoding='utf-8') as f: if admin_msgs: f.write("管理员弹幕:\n") f.write("-"*30 + "\n") for msg in admin_msgs: f.write(f"时间: {msg['timeline']}\n") f.write(f"用户: {msg['nickname']} (UID: {msg['uid']})\n") if msg['medal']: f.write(f"勋章: {msg['medal'][1]} {msg['medal'][0]}级\n") f.write(f"内容: {msg['text']}\n") f.write("-"*30 + "\n") f.flush() # 立即写入文件 time.sleep(10) print(f"\n监完成,共收集到 {len(seen_msgs)} 条弹幕") print(f"弹幕已保存到文件: {filepath}") except KeyboardInterrupt: print("\n用户停止监听") print(f"已收集到 {len(seen_msgs)} 条弹幕") print(f"弹幕已保存到文件: {filepath}") else: print(f"获取历史弹幕失败:{data['message']}") except Exception as e: print(f"获取历史弹幕时出错: {str(e)}") def get_room_play_info(room_id: str) -> None: """获取直播间播放信息""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } # 清晰度代码映射 quality_map = { 30000: "杜比", 20000: "4K", 10000: "原画", 400: "蓝光", 250: "超清", 150: "高清", 80: "流畅" } try: url = 'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo' params = { 'room_id': room_id, 'protocol': '0,1', # http_stream,http_hls 'format': '0,1,2', # flv,ts,fmp4 'codec': '0,1', # AVC,HEVC 'qn': '10000', # 原画质量 'platform': 'web', 'ptype': '8', 'dolby': '5', 'panorama': '1' } response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: room_info = data['data'] print("\n直播间播放信息:") print("="*50) print(f"房间号: {room_info['room_id']}") if room_info['short_id'] != 0: print(f"短号: {room_info['short_id']}") print(f"主UID: {room_info['uid']}") # 显示直播状态 status_map = { 0: "未开播", 1: "直播中", 2: "轮播中" } status = status_map.get(room_info['live_status'], "未知状态") print(f"直播状态: {status}") if room_info['live_status'] == 1: live_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(room_info['live_time'])) print(f"开播时间: {live_time}") # 显示房间状态 print(f"是否隐藏: {'是' if room_info['is_hidden'] else '否'}") print(f"是否锁定: {'是' if room_info['is_locked'] else '否'}") print(f"是否竖屏: {'是' if room_info['is_portrait'] else '否'}") print(f"是否加密: {'是' if room_info['encrypted'] else '否'}") if room_info['encrypted']: print(f"密码验证: {'已通过' if room_info['pwd_verified'] else '未通过'}") # 显示清晰度信息 if 'playurl_info' in room_info and 'playurl' in room_info['playurl_info']: playurl = room_info['playurl_info']['playurl'] if 'g_qn_desc' in playurl: print("\n可用清晰度:") for qn in playurl['g_qn_desc']: quality_name = quality_map.get(qn['qn'], qn['desc']) desc = f"{quality_name}" if qn['hdr_desc']: desc += f" ({qn['hdr_desc']})" print(f"- {desc} (qn: {qn['qn']})") # 显示直播流信息 if 'stream' in playurl: print("\n直播流信息:") for stream in playurl['stream']: print(f"\n协议: {stream['protocol_name']}") for format_info in stream['format']: print(f"格式: {format_info['format_name']}") for codec in format_info['codec']: print(f"编码: {codec['codec_name']}") current_quality = quality_map.get(codec['current_qn'], f"未知({codec['current_qn']})") print(f"当前清晰度: {current_quality}") accept_qualities = [quality_map.get(qn, f"未知({qn})") for qn in codec['accept_qn']] print(f"可用清晰度: {', '.join(accept_qualities)}") if codec['url_info']: print(f"播放地址: {codec['url_info'][0]['host']}{codec['base_url']}") print("="*50) else: print(f"获取直播间播放信息失败:{data['message']}") except Exception as e: print(f"获取直播间播放信息时出错: {str(e)}") def get_room_anchor_info(room_id: str) -> None: """获取直播间主播详细信息""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } try: url = 'https://api.live.bilibili.com/live_user/v1/UserInfo/get_anchor_in_room' params = {'roomid': room_id} response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: if 'data' in data and data['data']: anchor_data = data['data'] info = anchor_data['info'] level = anchor_data['level'] print("\n主播详细信息:") print("="*50) # 基本信息 print("基本信息:") print(f"UID: {info['uid']}") print(f"用户名: {info['uname']}") print(f"头像: {info['face']}") print(f"排名: {info['rank']}") print(f"平台用户等级: {info['platform_user_level']}") # 认证状态 print(f"\n认证状态:") print(f"手机认证: {'已认证' if info['mobile_verify'] == 1 else '未认证'}") print(f"身份认证: {'已认证' if info['identification'] == 1 else '未认证'}") # 官方认证信息 if info['official_verify']['type'] != -1: verify_type = "认证" if info['official_verify']['type'] == 0 else "机构认证" print(f"认证类型: {verify_type}") print(f"认证说明: {info['official_verify']['desc']}") # 性别 gender_map = { -1: "保密", 0: "女", 1: "男" } print(f"性别: {gender_map.get(info['gender'], '未知')}") # 等级信息 print("\n等级信息:") master_level = level['master_level'] print(f"主播等级: {master_level['level']}") print(f"用户等级: {level['user_level']}") print(f"主播积分: {master_level['anchor_score']}") if master_level['upgrade_score'] > 0: print(f"升级还需: {master_level['upgrade_score']}") # 消费信息 print("\n消费信息:") print(f"消费金额: {level['cost']}") print(f"充值金额: {level['rcost']}") # VIP信息 print("\nVIP状态:") if level['vip'] == 1: print(f"VIP到期时间: {level['vip_time']}") else: print("不是VIP") if level['svip'] == 1: print(f"SVIP到期时间: {level['svip_time']}") else: print("不是SVIP") # SAN值 print(f"\nSAN值: {anchor_data['san']}/12") print("="*50) else: print("未找到主播信息") else: print(f"获取主播信息失败:{data['message']}") except Exception as e: print(f"获取主播信息时出错: {str(e)}") class LiveRoom: """B站直播间相关功能类""" def __init__(self): self.headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } # 清晰度代码映射 self.quality_map = { 30000: "杜比", 20000: "4K", 10000: "原画", 400: "蓝光", 250: "超清", 150: "高清", 80: "流畅" } def _process_room_id(self, room_input: str) -> str: """处理直播���号输入,支���直接输入房间号或链接""" if 'live.bilibili.com' in room_input: return extract_room_id(room_input) return room_input def get_room_info(self, room_input: str) -> None: """获取直播间信息""" room_id = self._process_room_id(room_input) if not room_id.isdigit(): print("请输入有效的直播间号或链接!") return try: url = 'https://api.live.bilibili.com/room/v1/Room/get_info' params = {'room_id': room_id} response = requests.get(url, params=params, headers=self.headers) data = response.json() if data['code'] == 0: room_info = data['data'] print("\n直播间信息:") print("="*50) print(f"主播UID: {room_info['uid']}") print(f"直播间号: {room_info['room_id']}") if room_info['short_id'] != 0: print(f"直播间短号: {room_info['short_id']}") print(f"关注数: {room_info['attention']}") print(f"观看人数: {room_info['online']}") print(f"标题: {room_info['title']}") print(f"分区: {room_info['parent_area_name']} - {room_info['area_name']}") # 显示直播状态 status_map = { 0: "未开播", 1: "直播中", 2: "轮播中" } status = status_map.get(room_info['live_status'], "未知状态") print(f"直播状态: {status}") if room_info['live_status'] == 1: print(f"开播时间: {room_info['live_time']}") if room_info['description']: print(f"\n房间简介: {room_info['description']}") # 显示认证信息 if room_info['new_pendants']['badge']: badge = room_info['new_pendants']['badge'] badge_type = { 'v_person': '个人认证', 'v_company': '企业认证' } print(f"\n认证类型: {badge_type.get(badge['name'], '未知认证')}") if badge['desc']: print(f"认证说明: {badge['desc']}") # 显示热词 if room_info['hot_words']: print("\n房间热词:") for word in room_info['hot_words']: print(f"- {word}") print("="*50) else: print(f"获取直播间信息失败:{data['message']}") except Exception as e: print(f"获取直播间信息时出错: {str(e)}") def get_user_live_status(self, mid: str) -> None: """获取用户直播间状态""" # 原get_user_live_status函数的内容 def get_anchor_info(self, uid: str) -> None: """获取主播信息""" try: url = 'https://api.live.bilibili.com/live_user/v1/Master/info' params = {'uid': uid} response = requests.get(url, params=params, headers=self.headers) data = response.json() if data['code'] == 0: anchor_data = data['data'] info = anchor_data['info'] exp = anchor_data['exp'] print("\n主播信息:") print("="*50) print(f"UID: {info['uid']}") print(f"用户名: {info['uname']}") print(f"头像: {info['face']}") # 显示性别 gender_map = { -1: "保密", 0: "女", 1: "男" } gender = gender_map.get(info['gender'], "未知") print(f"性别: {gender}") # 显示认证信息 if info['official_verify']['type'] != -1: verify_type = "个人认证" if info['official_verify']['type'] == 0 else "机构认证" print(f"认证类型: {verify_type}") print(f"认证说明: {info['official_verify']['desc']}") # 显示等级信息 level_info = exp['master_level'] print(f"\n主播等级: {level_info['level']}") print(f"当前经验: {level_info['current'][1]}/{level_info['next'][1]}") print(f"升级还需: {level_info['next'][0] - level_info['current'][0]}") # 显示其他信息 print(f"\n粉丝数: {anchor_data['follower_num']}") print(f"直播间ID: {anchor_data['room_id']}") if anchor_data['medal_name']: print(f"粉丝勋章: {anchor_data['medal_name']}") if anchor_data['glory_count'] > 0: print(f"主播荣誉数: {anchor_data['glory_count']}") if anchor_data['pendant']: print(f"头像框: {anchor_data['pendant']}") # 显示公告 if anchor_data['room_news']['content']: print("\n主播公告:") print(f"内容: {anchor_data['room_news']['content']}") print(f"时间: {anchor_data['room_news']['ctime_text']}") print("="*50) else: print(f"获取主播信息失败:{data['message']}") except Exception as e: print(f"获取主播信息时出错: {str(e)}") def get_room_base_info(self, room_ids: list) -> None: """获取多个直播间基本信息""" # 原get_room_base_info函数的内容 def get_batch_live_status(self, uids: list) -> None: """批量查询直播间状态""" # 原get_batch_live_status函数的容 def get_history_danmaku(self, room_input: str) -> None: """获取直播间历史弹幕""" room_id = self._process_room_id(room_input) if not room_id.isdigit(): print("请输入有效的直播间号或链接!") return try: print("\n请选择获取方式:") print("1. 获取最近弹幕") print("2. 持续监听弹幕") choice = input("请选择 [1]: ").strip() if not choice: choice = "1" if choice == "1": self._get_recent_danmaku(room_id) elif choice == "2": self._monitor_danmaku(room_id) except Exception as e: print(f"获取历史弹幕时出错: {str(e)}") def _get_recent_danmaku(self, room_id: str) -> None: """获取最近的弹幕""" try: url = 'https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory' params = {'roomid': room_id} response = requests.get(url, params=params, headers=self.headers) data = response.json() if data['code'] == 0: self._save_danmaku_to_file(room_id, data['data']) else: print(f"获取历史弹幕失败:{data['message']}") except Exception as e: print(f"获取最近弹幕时出错: {str(e)}") def _monitor_danmaku(self, room_id: str) -> None: """持续监听直播间弹幕""" try: duration = input("请输��监听时长(分钟)[30]: ").strip() duration = int(duration) if duration.isdigit() else 30 interval = input("请输入获取间隔(秒)[10]: ").strip() interval = int(interval) if interval.isdigit() else 10 print(f"\n开始监听直播间 {room_id} 的弹幕") print(f"将持续监听 {duration} 分钟,每 {interval} 秒获取一次") print("按Ctrl+C可以随时停止") # 创建文件 danmaku_dir = os.path.join("弹幕", "直播弹幕") if not os.path.exists(danmaku_dir): os.makedirs(danmaku_dir) filename = f"{room_id} {time.strftime('%Y-%m-%d %H-%M-%S', time.localtime())} 监听弹幕.txt" filepath = os.path.join(danmaku_dir, filename) seen_msgs = set() # 用于去重 start_time = time.time() with open(filepath, 'w', encoding='utf-8') as f: try: while time.time() - start_time < duration * 60: new_msgs = self._fetch_danmaku(room_id) for msg in new_msgs: msg_id = f"{msg['timeline']}_{msg['nickname']}_{msg['text']}" if msg_id not in seen_msgs: seen_msgs.add(msg_id) # 写入文件 f.write(f"时间: {msg['timeline']}\n") f.write(f"用户: {msg['nickname']} (UID: {msg['uid']})\n") if msg['medal']: f.write(f"勋章: {msg['medal'][1]} {msg['medal'][0]}级\n") f.write(f"内容: {msg['text']}\n") f.write("-"*30 + "\n") f.flush() # 立即写入文件 time.sleep(interval) print(f"\n监完成,共收集到 {len(seen_msgs)} 条弹幕") print(f"弹幕已保存到文件: {filepath}") except KeyboardInterrupt: print("\n用户停止监听") print(f"已收集到 {len(seen_msgs)} 条弹幕") print(f"弹幕已保存到文件: {filepath}") except Exception as e: print(f"监听弹幕时出错: {str(e)}") def _save_danmaku_to_file(self, room_id: str, data: dict) -> None: """保存弹幕到文件""" admin_msgs = data['admin'] room_msgs = data['room'] danmaku_dir = os.path.join("弹幕", "直播弹幕") if not os.path.exists(danmaku_dir): os.makedirs(danmaku_dir) filename = f"{room_id} {time.strftime('%Y-%m-%d %H-%M-%S', time.localtime())} 历史弹幕.txt" filepath = os.path.join(danmaku_dir, filename) with open(filepath, 'w', encoding='utf-8') as f: if admin_msgs: f.write("管理员弹幕:\n") f.write("-"*30 + "\n") for msg in admin_msgs: f.write(f"时间: {msg['timeline']}\n") f.write(f"用户: {msg['nickname']} (UID: {msg['uid']})\n") if msg['medal']: f.write(f"勋章: {msg['medal'][1]} {msg['medal'][0]}级\n") f.write(f"内容: {msg['text']}\n") f.write("-"*30 + "\n") if room_msgs: f.write("\n普通用户弹幕:\n") f.write("-"*30 + "\n") for msg in room_msgs: f.write(f"时间: {msg['timeline']}\n") f.write(f"用户: {msg['nickname']} (UID: {msg['uid']})\n") if msg['medal']: f.write(f"勋章: {msg['medal'][1]} {msg['medal'][0]}级\n") f.write(f"内容: {msg['text']}\n") f.write("-"*30 + "\n") total_msgs = len(admin_msgs) + len(room_msgs) print(f"\n共获取到 {total_msgs} 条弹幕") print(f"弹幕已保存到文件: {filepath}") def _fetch_danmaku(self, room_id: str) -> list: """获取一次弹幕数据""" url = 'https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory' params = {'roomid': room_id} response = requests.get(url, params=params, headers=self.headers) data = response.json() if data['code'] == 0: msgs = [] if 'admin' in data['data']: msgs.extend(data['data']['admin']) if 'room' in data['data']: msgs.extend(data['data']['room']) return msgs return [] def get_play_info(self, room_input: str) -> None: """获取直播间播放信息""" room_id = self._process_room_id(room_input) if not room_id.isdigit(): print("请输入有效的直���间号或链接���") return headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } # 清晰度代码映射 quality_map = { 30000: "杜比", 20000: "4K", 10000: "原画", 400: "蓝光", 250: "超清", 150: "高清", 80: "流畅" } try: url = 'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo' params = { 'room_id': room_id, 'protocol': '0,1', # http_stream,http_hls 'format': '0,1,2', # flv,ts,fmp4 'codec': '0,1', # AVC,HEVC 'qn': '10000', # 原画质量 'platform': 'web', 'ptype': '8', 'dolby': '5', 'panorama': '1' } response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: room_info = data['data'] print("\n直播间播放信息:") print("="*50) print(f"房间号: {room_info['room_id']}") if room_info['short_id'] !=0: print(f"短号: {room_info['short_id']}") print(f"主UID: {room_info['uid']}") # 显示直播状态 status_map = { 0: "未开播", 1: "直播中", 2: "轮播中" } status = status_map.get(room_info['live_status'], "未知状态") print(f"直播状态: {status}") if room_info['live_status'] == 1: live_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(room_info['live_time'])) print(f"开播时间: {live_time}") # 显示房间状态 print(f"是否隐藏: {'是' if room_info['is_hidden'] else '否'}") print(f"是否锁定: {'是' if room_info['is_locked'] else '否'}") print(f"是否竖屏: {'是' if room_info['is_portrait'] else '否'}") print(f"是否加密: {'是' if room_info['encrypted'] else '否'}") if room_info['encrypted']: print(f"密码验证: {'已通过' if room_info['pwd_verified'] else '未通过'}") # 显示清晰度信息 if 'playurl_info' in room_info and 'playurl' in room_info['playurl_info']: playurl = room_info['playurl_info']['playurl'] if 'g_qn_desc' in playurl: print("\n可用清晰度:") for qn in playurl['g_qn_desc']: quality_name = quality_map.get(qn['qn'], qn['desc']) desc = f"{quality_name}" if qn['hdr_desc']: desc += f" ({qn['hdr_desc']})" print(f"- {desc} (qn: {qn['qn']})") # 显示直播流信息 if 'stream' in playurl: print("\n直播流信息:") for stream in playurl['stream']: print(f"\n协议: {stream['protocol_name']}") for format_info in stream['format']: print(f"格式: {format_info['format_name']}") for codec in format_info['codec']: print(f"编码: {codec['codec_name']}") current_quality = quality_map.get(codec['current_qn'], f"未知({codec['current_qn']})") print(f"当前清晰度: {current_quality}") accept_qualities = [quality_map.get(qn, f"未知({qn})") for qn in codec['accept_qn']] print(f"可用清晰度: {', '.join(accept_qualities)}") if codec['url_info']: print(f"播放地址: {codec['url_info'][0]['host']}{codec['base_url']}") print("="*50) else: print(f"获取直播间播放信息失败:{data['message']}") except Exception as e: print(f"获取直播间播放信息时出错: {str(e)}") def get_room_anchor_info(self, room_input: str) -> None: """获取直播间主播详细信息""" room_id = self._process_room_id(room_input) if not room_id.isdigit(): print("请输入有效的直播间号或链接!") return headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://live.bilibili.com', } try: url = 'https://api.live.bilibili.com/live_user/v1/UserInfo/get_anchor_in_room' params = {'roomid': room_id} response = requests.get(url, params=params, headers=headers) data = response.json() if data['code'] == 0: if 'data' in data and data['data']: anchor_data = data['data'] info = anchor_data['info'] level = anchor_data['level'] print("\n主播详细信息:") print("="*50) # 基本信息 print("基本信息:") print(f"UID: {info['uid']}") print(f"用户名: {info['uname']}") print(f"头像: {info['face']}") print(f"排����: {info['rank']}") print(f"平台用户等级: {info['platform_user_level']}") # 认证状态 print(f"\n认证状态:") print(f"手机认证: {'已认证' if info['mobile_verify'] == 1 else '未认证'}") print(f"身份认证: {'已认证' if info['identification'] == 1 else '未认证'}") # 官方认证信息 if info['official_verify']['type'] != -1: verify_type = "认证" if info['official_verify']['type'] == 0 else "机构认证" print(f"认证类型: {verify_type}") print(f"认证说明: {info['official_verify']['desc']}") # 性别 gender_map = { -1: "保密", 0: "女", 1: "男" } print(f"性别: {gender_map.get(info['gender'], '未知')}") # 等级信息 print("\n等级信息:") master_level = level['master_level'] print(f"主播等级: {master_level['level']}") print(f"用户等级: {level['user_level']}") print(f"主播积分: {master_level['anchor_score']}") if master_level['upgrade_score'] > 0: print(f"升级���需: {master_level['upgrade_score']}") # 消费���息 print("\n消费信息:") print(f"消费金额: {level['cost']}") print(f"充值金额: {level['rcost']}") # VIP信息 print("\nVIP状态:") if level['vip'] == 1: print(f"VIP到期时间: {level['vip_time']}") else: print("不是VIP") if level['svip'] == 1: print(f"SVIP到期时间: {level['svip_time']}") else: print("不是SVIP") # SAN值 print(f"\nSAN值: {anchor_data['san']}/12") print("="*50) else: print("未找到主播信息") else: print(f"获取主播信息失败:{data['message']}") except Exception as e: print(f"获取主播信息时出错: {str(e)}") def extract_room_id(url: str) -> str: """从直播间URL中提取房间号""" # 匹配live.bilibili.com/后面的数字 pattern = r'live\.bilibili\.com/(\d+)' match = re.search(pattern, url) if match: return match.group(1) return '' def extract_uid(url: str) -> str: """从B���个人空间URL中提取UID""" # 匹配space.bilibili.com/后面的数字 pattern = r'space\.bilibili\.com/(\d+)' match = re.search(pattern, url) if match: return match.group(1) return '' def send_old_fan_message(up_mid: str, content: str, cookies: Dict) -> bool: """发老粉计划留言 Args: up_mid (str): UP主的UID content (str): 留言内容 cookies (Dict): cookies信息 Returns: bool: 是否发送成功 """ headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://space.bilibili.com', 'Content-Type': 'application/x-www-form-urlencoded', } try: # 获取csrf令牌 csrf = cookies.get('bili_jct') if not csrf: print("错误:无法获取csrf令牌!") return False # 构造请求数据 data = { 'aid': '', 'up_mid': up_mid, 'source': '4', 'scene': '105', 'content': content, 'csrf': csrf } # 发送请求 url = 'https://api.bilibili.com/x/v1/contract/add_message' response = requests.post(url, data=data, cookies=cookies, headers=headers) result = response.json() if result['code'] == 0: print(f"\n{result['data']['success_toast']}") return True else: error_messages = { -101: "账号未登录", -111: "csrf校验失", -400: "请求错误", 158005: "您跟up主还不是契约关系,请先加入老粉计划" } error_msg = error_messages.get(result['code'], result['message']) print(f"\n发送留言失败:{error_msg}") return False except Exception as e: print(f"\n发送留言时出错: {str(e)}") return False def join_old_fan_plan(up_mid: str, cookies: Dict) -> bool: """加入UP主的老粉计划""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://space.bilibili.com', 'Content-Type': 'application/x-www-form-urlencoded', } try: csrf = cookies.get('bili_jct') if not csrf: print("错误:无法获取csrf令牌!") return False data = { 'aid': '', 'up_mid': up_mid, 'source': '4', 'scene': '105', 'platform': 'web', 'mobi_app': 'pc', 'csrf': csrf } url = 'https://api.bilibili.com/x/v1/contract/add_contract' response = requests.post(url, data=data, cookies=cookies, headers=headers) result = response.json() if result['code'] == 0: print("\n成功加入老粉���划!") if result['data']['allow_message']: print(f"\n{result['data']['input_title']}") choice = input("\n是否发送留言?(y/n) [n]: ").lower() if choice == 'y': message = input(f"请输入要发送的留言 [默认: {result['data']['input_text']}]: ").strip() if not message: message = result['data']['input_text'] send_old_fan_message(up_mid, message, cookies) return True else: error_messages = { -101: "账号未登录", -111: "csrf校验失败", -400: "���输入错误", 158001: "不满足加入条件", 158002: "已经是老粉了", 158003: "UP主未开通老粉计划", 158004: "UP主暂未达到开通条件" } error_msg = error_messages.get(result['code'], result['message']) print(f"\n加入老粉计划失败:{error_msg}") return False except Exception as e: print(f"\n加入老粉计划时出错: {str(e)}") return False def check_follow_status(up_mid: str, cookies: Dict) -> bool: """检查是否关注了UP主 Args: up_mid (str): UP主的UID cookies (Dict): cookies信息 Returns: bool: 是否已关注 """ headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://space.bilibili.com', } try: # 获取关注状态 url = f'https://api.bilibili.com/x/relation/stat?vmid={up_mid}' response = requests.get(url, cookies=cookies, headers=headers) data = response.json() if data['code'] == 0: # 获取UP主信息 info_url = f'https://api.bilibili.com/x/space/acc/info?mid={up_mid}' info_response = requests.get(info_url, headers=headers) info_data = info_response.json() if info_data['code'] == 0: up_info = info_data['data'] print(f"\nUP主信息:") print(f"名称: {up_info['name']}") print(f"等级: LV{up_info['level']}") if up_info['official']['title']: print(f"认证信息: {up_info['official']['title']}") # 检查关注状态 relation_url = f'https://api.bilibili.com/x/relation?fid={up_mid}' relation_response = requests.get(relation_url, cookies=cookies, headers=headers) relation_data = relation_response.json() if relation_data['code'] == 0: is_followed = relation_data['data']['attribute'] == 1 print(f"关注状态: {'已关注' if is_followed else '未关注'}") if not is_followed: print("\n提示: 需要先关注UP主才能加入老粉计划") choice = input("是否现在关注?(y/n) [y]: ").lower() if not choice or choice == 'y': return follow_up(up_mid, cookies) return is_followed print(f"获取关注状态失败:{data['message']}") return False except Exception as e: print(f"检查关注状态时出错: {str(e)}") return False def follow_up(up_mid: str, cookies: Dict) -> bool: """关注UP主 Args: up_mid (str): UP主的UID cookies (Dict): cookies信息 Returns: bool: 是否关注成��� """ headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://space.bilibili.com', 'Content-Type': 'application/x-www-form-urlencoded', } try: csrf = cookies.get('bili_jct') if not csrf: print("错误:无法获取csrf令牌!") return False data = { 'fid': up_mid, 'act': 1, # 1:关注 2:取消关注 're_src': 11, 'csrf': csrf } url = 'https://api.bilibili.com/x/relation/modify' response = requests.post(url, data=data, cookies=cookies, headers=headers) result = response.json() if result['code'] == 0: print("\n成功关注UP主!") return True else: print(f"\n关注UP主失败:{result['message']}") return False except Exception as e: print(f"关注UP主时出错: {str(e)}") return False def report_video(aid: str, tid: str, desc: str, attach: str, cookies: Dict, video_info: dict = None) -> bool: """无图片举报视频""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', 'buid': str(random.randint(100000, 999999)) # 随机生成风控代码 } try: csrf = cookies.get('bili_jct') if not csrf: print("错误:无法获取csrf令牌!") return False data = { 'csrf': csrf, 'aid': aid, 'tid': tid, 'desc': desc, } if attach: data['attach'] = attach cookies['Buid'] = headers['buid'] url = 'https://api.bilibili.com/x/web-interface/appeal/v2/submit' response = requests.post(url, data=data, cookies=cookies, headers=headers) result = response.json() if result['code'] == 0: print("\n举报成功!") # 保存举报记录 if video_info: report_info = { 'reason': report_types.get(tid, {}).get('name', '未知��由'), 'desc': desc, 'attach': attach, 'is_image': False } save_report_record(video_info, report_info) return True else: error_messages = { -101: "账号未登录", -111: "csrf校验失败", -400: "请求错误", -404: "视频不存在", 78001: "举报理由不合法", 78002: "举报描述过短", 78003: "举报描述过长", 78004: "附件格式不支持", 78005: "附件数量超限" } error_msg = error_messages.get(result['code'], result['message']) print(f"\n举报失败:{error_msg}") return False except Exception as e: print(f"\n举报时出错: {str(e)}") return False def report_video_with_image(aid: str, tid: str, desc: str, image_url: str, cookies: Dict, video_info: dict = None) -> bool: """带图片举报视频""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', 'buid': str(random.randint(100000, 999999)) # 随机生成风控代码 } try: csrf = cookies.get('bili_jct') if not csrf: print("错误:无法获取csrf令牌!") return False data = { 'csrf': csrf, 'aid': aid, 'tid': tid, 'desc': desc, 'attach': image_url } cookies['Buid'] = headers['buid'] url = 'https://api.bilibili.com/x/web-interface/appeal/v2/submit' response = requests.post(url, data=data, cookies=cookies, headers=headers) result = response.json() if result['code'] == 0: print("\n举报成功!") # 保存举报记录 if video_info: report_info = { 'reason': report_types.get(tid, {}).get('name', '未知理由'), 'desc': desc, 'attach': image_url, 'is_image': True } save_report_record(video_info, report_info) return True else: error_messages = { -101: "账号未登录", -111: "csrf校验失败", -400: "请求错误", -404: "视频不存在", 78001: "举报理由不合", 78002: "举报描述过短", 78003: "举报描述过长", 78004: "附件格式不支持", 78005: "附件数量超限" } error_msg = error_messages.get(result['code'], result['message']) print(f"\n举报失败:{error_msg}") return False except Exception as e: print(f"\n举报时出错: {str(e)}") return False def get_report_types() -> dict: """获取视频举报类型列表""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://www.bilibili.com' } try: url = 'https://api.bilibili.com/x/web-interface/archive/appeal/tags' response = requests.get(url, headers=headers) data = response.json() if data['code'] == 0: report_types = {} for item in data['data']: report_types[str(item['tid'])] = { 'name': item['name'], 'remark': item['remark'], 'controls': item['controls'] } return report_types else: print(f"获取举报类型失败:{data['message']}") return {} except Exception as e: print(f"获取举报类型时出错: {str(e)}") return {} def show_report_menu(bvid: str, cookies: Dict) -> None: """显示举报菜单""" try: # 获取视频aid headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } video_url = f'https://api.bilibili.com/x/web-interface/view?bvid={bvid}' response = requests.get(video_url, headers=headers) data = response.json() if data['code'] == 0: aid = str(data['data']['aid']) # 获取举报类型 report_types = get_report_types() if not report_types: print("\n获���������举报类型失败,使用默认类型") # 使用默认的举报类型 report_types = { "2": {"name": "违法违禁", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, "3": {"name": "色情低俗", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, "5": {"name": "赌博诈骗", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, "6": {"name": "血腥暴力", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, "7": {"name": "人身攻击", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, } print("\n请选择举报理由:") for tid, info in report_types.items(): print(f"{tid}. {info['name']}") tid = input("\n请选择举报理由编号: ").strip() if tid in report_types: print(f"\n��示: {report_types[tid]['remark']}") while True: # 添加循环让用户重新输入 desc = input("\n请输入详细描述(至少10个字):").strip() if len(desc) < 10: print("\n描述太短,至少需要10个字!请重新输入") continue # 继续循环让用户重新输入 break # 描述长度符合要求时跳出循环 attach = "" # 如果需要额外信息(如撞车举报) if report_types[tid]['controls']: for control in report_types[tid]['controls']: if control['required']: value = input(f"\n请输入{control['title']} ({control['placeholder']}): ").strip() if not value: print(f"\n{control['title']}为必填项!") return attach = value else: # 普通举报,询问是否需要上传图片 print("\n是否上传举报附件图片?") print("1. 上传本地图片") print("2. 直接输入图片URL") print("3. 跳过") upload_choice = input("\n请选择 [3]: ").strip() if not upload_choice: upload_choice = "3" if upload_choice == "1": image_path = input("\n请输入图片文件路径: ").strip() if os.path.exists(image_path): image_url = upload_report_image(image_path, cookies) if image_url: attach = image_url else: print("\n图片上传失败!") else: print("\n找不到图片文件!") elif upload_choice == "2": attach = input("\n请输入图片URL(多个用逗号分隔):").strip() # 确认举报 print("\n举报信息确认:") print(f"视频:{bvid}") print(f"理由:{report_types[tid]['name']}") print(f"描述:{desc}") if attach: if report_types[tid]['controls']: print(f"{report_types[tid]['controls'][0]['title']}: {attach}") else: print(f"附件:{attach}") confirm = input("\n确认提交举报?(y/n) [y]: ").lower() # 修改默认提示为[y] if not confirm or confirm == 'y': # 如果直接回车或输入y都确认举报 if report_types[tid]['controls']: # 特殊举报(如撞车)不需要上传图片 report_video(aid, tid, desc, attach, cookies) else: # 普通举报 report_video_with_image(aid, tid, desc, attach, cookies) else: print("\n无效的举报理由编号!") else: print(f"\n获取视频信息失败:{data['message']}") except Exception as e: print(f"\n显示举报菜单时出错: {str(e)}") def upload_report_image(image_path: str, cookies: Dict) -> str: """上传举报附件图片""" try: # 复制图片到举报图片文件夹 image_dir = os.path.join("举报", "图片") image_name = f"{time.strftime('%Y-%m-%d %H-%M-%S')}_{os.path.basename(image_path)}" new_image_path = os.path.join(image_dir, image_name) import shutil shutil.copy2(image_path, new_image_path) print(f"\n图片已保存到: {new_image_path}") # 上传图片到B站 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } csrf = cookies.get('bili_jct') if not csrf: print("错误:无法获取csrf令牌!") return "" with open(new_image_path, 'rb') as f: image_data = f.read() base64_data = f"data:image/jpeg;base64,{base64.b64encode(image_data).decode()}" data = { 'csrf': csrf, 'cover': base64_data } url = 'https://member.bilibili.com/x/vu/web/cover/up' params = {'ts': int(time.time() * 1000)} response = requests.post(url, params=params, data=data, cookies=cookies, headers=headers) result = response.json() if result['code'] == 0: return result['data']['url'] else: error_messages = { -101: "账号未登录", -111: "csrf校验失败", -400: "请求错误" } error_msg = error_messages.get(result['code'], result['message']) print(f"上传图片失败:{error_msg}") return "" except Exception as e: print(f"上传图片时出错: {str(e)}") return "" def search_videos_request(keyword: str, cookies: Dict, page: int = 1, order: str = 'totalrank', ps: int = 50) -> list: """搜索请求API""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://search.bilibili.com' } try: params = { 'keyword': keyword, 'page': page, 'order': order, 'search_type': 'video', 'tids': 0, 'duration': 0, 'ps': ps } # 使用新的搜索API url = 'https://api.bilibili.com/x/web-interface/wbi/search/all/v2' response = requests.get(url, params=params, cookies=cookies, headers=headers) data = response.json() if data['code'] == 0: results = [] # 从返回的数据中提取视频信息 for result in data['data']['result']: if result['result_type'] == 'video': for video in result['data']: video_info = { 'title': re.sub(r'<.*?>', '', video['title']), 'bvid': video['bvid'], 'aid': video['aid'], 'author': video['author'], 'mid': video['mid'], 'play': video['play'], 'favorites': video.get('favorites', 0), 'duration': video['duration'], 'description': video['description'], 'pubdate': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(video['pubdate'])), 'tag': video.get('tag', '') } results.append(video_info) return results else: print(f"搜索失败:{data['message']}") return [] except Exception as e: print(f"搜索时出错: {str(e)}") return [] def search_videos_result(keyword: str, cookies: Dict, page: int = 1, order: str = 'totalrank', ps: int = 50) -> list: """搜索结果API (原来的搜索函数)""" # 保持原来的 search_videos 函数代码不变,只是改名 # ... 原有代码 ... def show_search_menu(cookies: Dict) -> None: """显示搜索菜单""" print("\n=== 视频搜索 ===") print("排序方式:") print("1. 综合排序") print("2. 最多点击") print("3. 最新发布") print("4. 最多弹幕") print("5. 最多收藏") order_map = { '1': 'totalrank', '2': 'click', '3': 'pubdate', '4': 'dm', '5': 'stow' } keyword = input("\n请输入搜索关键词: ").strip() if not keyword: # 如果直接回车 keyword = "圣体" # 默认关键词 order = '3' # 默认使用最新发布排序 else: order = input("请选择排序方式 [1]: ").strip() if not order: order = '1' if order not in order_map: print("无效的排序方式,使用默认排序") order = '1' page = input("请输入页码 [1]: ").strip() if not page or not page.isdigit(): page = 1 else: page = int(page) results = search_videos(keyword, cookies, page, order_map[order]) if results: print(f"\n找到 {len(results)} 个视频:") print("="*50) for i, video in enumerate(results, 1): print(f"{i}. {video['title']}") print(f" UP主: {video['author']}") print(f" 播放: {video['play']} | 收藏: {video['favorites']} | 时长: {video['duration']}") print(f" 发布时间: {video['pubdate']}") print(f" BV号: {video['bvid']}") print(f" 视频链接: https://www.bilibili.com/video/{video['bvid']}") # 添加视频链接 print(f" 标签: {video['tag']}") print(f" 简介: {video['description'][:100]}..." if len(video['description']) > 100 else f" 简介: {video['description']}") print("-"*50) while True: choice = input("\n请选择要操作的视频序号 (输入q返回): ").strip().lower() if choice == 'q': break if choice.isdigit() and 1 <= int(choice) <= len(results): video = results[int(choice)-1] print("\n请选择操作:") print("1. 下载音频") print("2. 获取弹幕") print("3. 获取评论") print("4. 举报视频") print("5. 返回") op = input("\n请选择 [1]: ").strip() if not op: op = "1" if op == "1": download_audio(video['bvid'], cookies) elif op == "2": # 获取视频cid headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } video_url = f'https://api.bilibili.com/x/web-interface/view?bvid={video["bvid"]}' response = requests.get(video_url, headers=headers) data = response.json() if data['code'] == 0: cid = data['data']['cid'] get_danmaku(cid, video['bvid'], 1, cookies) elif op == "3": get_hot_comments(video['bvid']) elif op == "4": show_report_menu(video['bvid'], cookies) else: print("无效的选择!") input("\n按回车继续...") def batch_report_videos(cookies: Dict) -> None: """批量举报视频""" print("\n=== 批量举报视频 ===") # 1. 先搜索视频 print("排序方式:") print("1. 综合排序") print("2. 最多点击") print("3. 最新发布") print("4. 最多弹幕") print("5. 最多收藏") order_map = { '1': 'totalrank', '2': 'click', '3': 'pubdate', '4': 'dm', '5': 'stow' } keyword = input("\n请输入搜索关键词: ").strip() if not keyword: print("搜关键词不能为!") return order = input("请选择排序方式 [1]: ").strip() if not order: order = '1' if order not in order_map: print("无效的排序方式,使用默认排序") order = '1' page = input("请输入页码 [1]: ").strip() if not page or not page.isdigit(): page = 1 else: page = int(page) results = search_videos_request(keyword, page, order_map[order]) if not results: return # 2. 显示搜索结果并让用户选择要举报的视频 print(f"\n找到 {len(results)} 个视频:") print("="*50) for i, video in enumerate(results, 1): print(f"{i}. {video['title']}") print(f" UP主: {video['author']}") print(f" 播放: {video['play']} | 收藏: {video['favorites']} | 时长: {video['duration']}") print(f" 发布时间: {video['pubdate']}") print(f" BV号: {video['bvid']}") print(f" 标签: {video['tag']}") print(f" 简介: {video['description'][:100]}..." if len(video['description']) > 100 else f" 简介: {video['description']}") print("-"*50) print("\n请选择要举报的视频序号(用空格分隔,例如:1 3 5)") print("支持范围选择(例如:1-5 7 9-11)") choice = input("请选择: ").strip() selected_videos = set() for part in choice.split(): if '-' in part: start, end = map(int, part.split('-')) selected_videos.update(range(start, end +1)) else: if part.isdigit(): selected_videos.add(int(part)) # 过滤无效的序号 valid_videos = sorted([i for i in selected_videos if 1 <= i <= len(results)]) if not valid_videos: print("未选择有效视频!") return # 3. 获取举报类型 report_types = get_report_types() if not report_types: print("\n获取举报类型失败,使用默认类型") report_types = { "2": {"name": "违法违禁", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, "3": {"name": "色情低俗", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, "5": {"name": "赌博诈骗", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, "6": {"name": "血腥暴力", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, "7": {"name": "人身攻击", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None}, } print("\n请选择举报理由:") for tid, info in report_types.items(): print(f"{tid}. {info['name']}") tid = input("\n请选择举报理由编号: ").strip() if tid not in report_types: print("\n无效的举报理由编号!") return print(f"\n提示: {report_types[tid]['remark']}") desc = input("\n请输入详细描述(至少10个字):").strip() if len(desc) < 10: print("\n描述太短,至少需要10个字����") return attach = "" # 如果需要额外信息(如撞车举报) if report_types[tid]['controls']: for control in report_types[tid]['controls']: if control['required']: value = input(f"\n请输入{control['title']} ({control['placeholder']}): ").strip() if not value: print(f"\n{control['title']}为必填项!") return attach = value else: # 普通举报,询问是否需要上传图片 print("\n是否上传举报附件图片?") print("1. 上传本地图片") print("2. 直接输入图片URL") print("3. 跳过") upload_choice = input("\n请选择 [3]: ").strip() if not upload_choice: upload_choice = "3" if upload_choice == "1": image_path = input("\n请输入图片文件路径: ").strip() if os.path.exists(image_path): image_url = upload_report_image(image_path, cookies) if image_url: attach = image_url else: print("\n图��上传失败!") else: print("\n找不到图片文件!") elif upload_choice == "2": attach = input("\n请输入图片URL(多个用逗号分隔):").strip() # 4. 确认并执行批量举报 print("\n以下视频将被举报:") for i in valid_videos: video = results[i-1] print(f"- {video['title']} (BV号: {video['bvid']}") print(f"\n举报理由: {report_types[tid]['name']}") print(f"描述: {desc}") if attach: if report_types[tid]['controls']: print(f"{report_types[tid]['controls'][0]['title']}: {attach}") else: print(f"附件: {attach}") confirm = input("\n确认提交举报?(y/n) [n]: ").lower() if confirm == 'y': success_count = 0 fail_count = 0 # 创建进度条 with tqdm(total=len(valid_videos), desc="举报进度") as pbar: for i in valid_videos: video = results[i-1] try: if report_types[tid]['controls']: # 特殊举报(如撞车)不需要上传图片 if report_video(str(video['aid']), tid, desc, attach, cookies): success_count += 1 else: fail_count += 1 else: # 普通举报 if report_video_with_image(str(video['aid']), tid, desc, attach, cookies): success_count += 1 else: fail_count += 1 except Exception as e: print(f"\n举报视频 {video['bvid']} 时出错: {str(e)}") fail_count += 1 pbar.update(1) # 添���延时,避免���求过快 time.sleep(2) print(f"\n批量举报完成!成功: {success_count}, 失败: {fail_count}") def save_report_record(video_info: dict, report_info: dict) -> None: """保存举报记录 Args: video_info (dict): 视频信息 report_info (dict): 举报信息 """ try: record_dir = os.path.join("举报", "记录") if not os.path.exists(record_dir): os.makedirs(record_dir) # 使用日期作为子文件夹 date_dir = os.path.join(record_dir, time.strftime('%Y-%m-%d')) if not os.path.exists(date_dir): os.makedirs(date_dir) # 文件名格式: 时间_BV号.txt filename = f"{time.strftime('%H-%M-%S')}_{video_info['bvid']}.txt" filepath = os.path.join(date_dir, filename) with open(filepath, 'w', encoding='utf-8') as f: f.write("="*50 + "\n") f.write("视频信息:\n") f.write("-"*30 + "\n") f.write(f"标题: {video_info['title']}\n") f.write(f"BV号: {video_info['bvid']}\n") f.write(f"AV号: {video_info['aid']}\n") f.write(f"UP主: {video_info['author']}\n") f.write(f"发布时间: {video_info['pubdate']}\n") f.write(f"视频链接: https://www.bilibili.com/video/{video_info['bvid']}\n") f.write("\n举报信息:\n") f.write("-"*30 + "\n") f.write(f"举报理由: {report_info['reason']}\n") f.write(f"详细描述: {report_info['desc']}\n") if report_info.get('attach'): if report_info.get('is_image'): f.write(f"附件图片: {report_info['attach']}\n") else: f.write(f"附加信息: {report_info['attach']}\n") f.write(f"举报时间: {time.strftime('%Y-%m-%d %H:%M:%S')}\n") f.write("="*50 + "\n") print(f"\n举报记录已保存: {filepath}") except Exception as e: print(f"保存举报记录��出错: {str(e)}") def get_wbi_keys(cookies: Dict = None) -> tuple: """获取最新的 img_key 和 sub_key,带缓存机制 Args: cookies (Dict): cookies信息 Returns: tuple: (img_key, sub_key) """ cache_file = os.path.join("举报", "wbi_cache.json") try: # 检查缓存是否存在且未过期 if os.path.exists(cache_file): with open(cache_file, 'r', encoding='utf-8') as f: cache = json.load(f) # 检查缓存是否在24小时内 if time.time() - cache['timestamp'] < 24 * 3600: return cache['img_key'], cache['sub_key'] # 缓存不存在或已过期,重新获取 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Referer': 'https://www.bilibili.com', } url = 'https://api.bilibili.com/x/web-interface/nav' # 添加cookies参数 response = requests.get(url, cookies=cookies, headers=headers) data = response.json() if data['code'] == 0: img_url = data['data']['wbi_img']['img_url'] sub_url = data['data']['wbi_img']['sub_url'] img_key = os.path.splitext(os.path.basename(img_url))[0] sub_key = os.path.splitext(os.path.basename(sub_url))[0] # 保存到缓存 cache_data = { 'img_key': img_key, 'sub_key': sub_key, 'timestamp': time.time() } # 确保缓存目录存在 os.makedirs(os.path.dirname(cache_file), exist_ok=True) with open(cache_file, 'w', encoding='utf-8') as f: json.dump(cache_data, f, indent=2) return img_key, sub_key else: print(f"获取WBI keys失败: {data['message']}") return None, None except Exception as e: print(f"获取WBI keys时出错: {str(e)}") return None, None def get_mixin_key(raw_key: str) -> str: """成 mixin_key Args: raw_key (str): img_key + sub_key 拼接的字符串 Returns: str: mixin_key """ MIXIN_KEY_ENC_TAB = [ 46, 47, 18, 2, 53, 8, 23, 32, 15, 50, 10, 31, 58, 3, 45, 35, 27, 43, 5, 49, 33, 9, 42, 19, 29, 28, 14, 39, 12, 38, 41, 13, 37, 48, 7, 16, 24, 55, 40, 61, 26, 17, 0, 1, 60, 51, 30, 4, 22, 25, 54, 21, 56, 59, 6, 63, 57, 62, 11, 36, 20, 34, 44, 52 ] # 对raw_key按照MIXIN_KEY_ENC_TAB的索引进行重排 mixin_key = '' for i in MIXIN_KEY_ENC_TAB: if i < len(raw_key): mixin_key += raw_key[i] # 截取前32 return mixin_key[:32] def encode_wbi(params: dict, cookies: Dict = None) -> dict: """为请求参数进行WBI签名 Args: params (dict): 原始请求参数 cookies (Dict): cookies信息 Returns: dict: 加入w_rid和wts的新参数 """ # 获取img_key和sub_key,传入cookies img_key, sub_key = get_wbi_keys(cookies) # 确保传入cookies if not img_key or not sub_key: return params # 生成mixin_key mixin_key = get_mixin_key(img_key + sub_key) # 添加wts参数 wts = str(int(time.time())) params['wts'] = wts # 按键名升序排序并拼接参数 sorted_params = dict(sorted(params.items())) query = [] for k, v in sorted_params.items(): # URL编码,需要大写 query.append(f"{k}={requests.utils.quote(str(v), safe='').upper()}") query = '&'.join(query) # 计算w_rid w_rid = hashlib.md5((query + mixin_key).encode()).hexdigest() # 将w_rid和wts添加到原��参数中 params['w_rid'] = w_rid return params def save_reported_bvids(bvids: list) -> None: """保存已举报的BV号列表""" report_dir = os.path.join("举报", "记录") if not os.path.exists(report_dir): os.makedirs(report_dir) reported_file = os.path.join(report_dir, "reported_videos.txt") try: with open(reported_file, 'a', encoding='utf-8') as f: for bvid in bvids: f.write(f"{bvid}\n") except Exception as e: print(f"保存已举报BV号时出错: {str(e)}") def get_reported_bvids() -> set: """获取已举报的BV号集合""" reported_file = os.path.join("举报", "记录", "reported_videos.txt") reported_bvids = set() if os.path.exists(reported_file): try: with open(reported_file, 'r', encoding='utf-8') as f: for line in f: bvid = line.strip() if bvid: reported_bvids.add(bvid) except Exception as e: print(f"读取已举报BV号时出错: {str(e)}") return reported_bvids def show_maipi_menu(cookies: Dict) -> None: """显示麦片举报菜单""" print("\n=== 麦片举报 ===") print("请选择搜索关键词:") print("1. 圣体") print("2. 大雷") choice = input("\n请选择 [1]: ").strip() if not choice: choice = "1" keywords = { "1": "圣体", "2": "大雷" } # 定义关键词列表 blacklist_keywords = ["健身", "肌肉", "训练"] whitelist_keywords = ["变速卡点", "美女", "诱惑", "小姐姐", "大长腿"] title_keywords = { "1": ["圣体", "麦片", "黄片", "色情", "福利"], "2": ["大雷", "麦片", "黄片", "色情", "福利"] } if choice in keywords: keyword = keywords[choice] page = 1 reported_bvids = get_reported_bvids() while True: results = search_videos(keyword, cookies, page=page, order='pubdate', ps=50) if not results: print("没有更多视频了") break # 过滤和排序视频 filtered_results = [] for video in results: if video['bvid'] not in reported_bvids: title = video['title'].lower() # 跳过黑名单关键词 if any(kw.lower() in title for kw in blacklist_keywords): continue # 必须包含必要关键词中的任意一个 if any(kw.lower() in title for kw in title_keywords[choice]): filtered_results.append(video) results = filtered_results if not results: print("当前页没有符合条件的视频,正在获取下一页...") page += 1 continue print(f"\n找到 {len(results)} 个未举报的视频:") print("="*50) for i, video in enumerate(results, 1): print(f"{i}. {video['title']}") print(f" UP主: {video['author']}") print(f" 播放: {video['play']} | 收藏: {video['favorites']} | 时长: {video['duration']}") print(f" 发布时间: {video['pubdate']}") print(f" BV号: {video['bvid']}") print(f" 视频链接: https://www.bilibili.com/video/{video['bvid']}") print(f" 标签: {video['tag']}") print(f" 简介: {video['description'][:100]}..." if len(video['description']) > 100 else f" 简介: {video['description']}") print("-"*50) print("\n请选择要举报的视频序号(用空格分隔,例如:1 3 5)") print("支持范围选择(例如:1-5 7 9-11)") choice = input("请选择: ").strip() selected_videos = set() for part in choice.split(): if '-' in part: start, end = map(int, part.split('-')) selected_videos.update(range(start, end + 1)) else: if part.isdigit(): selected_videos.add(int(part)) # 过滤无效的序号 valid_videos = sorted([i for i in selected_videos if 1 <= i <= len(results)]) if valid_videos: print("\n以下视频将被举报:") for i in valid_videos: video = results[i-1] print(f"- {video['title']} (BV号: {video['bvid']}") # 在使用 report_types 前先获取 report_types = get_report_types() if not report_types: report_types = { "3": { "name": "色情低俗", "remark": "为帮助审核人员更快处理,补充违规内容出现位置", "controls": None } } # 自动使用色情低俗(tid=3)举报 tid = "3" # 修改默认举报描述,使其更详细 desc = "这是黄片,审核为什么不管?这种视频明显违规,建议尽快处理,避免造成不良影响。" # 默认举报描述 confirm = input("\n确认开始举报?(y/n) [y]: ").lower() if not confirm or confirm == 'y': success_count = 0 fail_count = 0 reported = [] # 创建进度条 with tqdm(total=len(valid_videos), desc="举报进度") as pbar: for i in valid_videos: video = results[i-1] try: # 使用普通举报 if report_video(str(video['aid']), tid, desc, "", cookies, video): success_count += 1 reported.append(video['bvid']) else: fail_count += 1 except Exception as e: print(f"\n举报视频 {video['bvid']} 时出错: {str(e)}") fail_count += 1 pbar.update(1) time.sleep(2) # 保存新举报的BV号 if reported: save_reported_bvids(reported) print(f"\n批量举报完成!成功: {success_count}, 失败: {fail_count}") # 询问是否继续举报下一页 choice = input("\n是否继续举报下一页视频?(y/n) [y]: ").lower() if not choice or choice == 'y': # 直接回车或输入y都继续 page += 1 continue else: break else: print("未选择有效的视频!") break else: print("无效的选择!") def search_videos(keyword: str, cookies: Dict, page: int = 1, order: str = 'totalrank', ps: int = 50) -> list: """搜索视频""" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', 'Referer': 'https://search.bilibili.com' } try: params = { 'keyword': keyword, 'page': page, 'order': order, 'search_type': 'video', 'tids': 0, 'duration': 0, 'ps': ps } # 使用搜索结果API而不是搜索请求API url = 'https://api.bilibili.com/x/web-interface/search/type' response = requests.get(url, params=params, cookies=cookies, headers=headers) data = response.json() if data['code'] == 0: results = [] if 'result' in data['data']: for item in data['data']['result']: video_info = { 'title': re.sub(r'<.*?>', '', item['title']), 'bvid': item['bvid'], 'aid': item['aid'], 'author': item['author'], 'mid': item['mid'], 'play': item['play'], 'favorites': item['favorites'], 'duration': item['duration'], 'description': item['description'], 'pubdate': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(item['pubdate'])), 'tag': item['tag'] } results.append(video_info) return results else: print(f"搜索失败:{data['message']}") return [] except Exception as e: print(f"搜索时出错: {str(e)}") return [] if __name__ == "__main__": try: main() except Exception as e: print(f"程序出错: {str(e)}") import traceback traceback.print_exc() finally: input("\n按回车键退出...")