├── Code ├── B站图标.ico ├── B站推流码获取工具.py ├── B站直播分区修改工具.py ├── B站直播标题修改工具.py ├── GetCookies.py ├── config.ini ├── data.py ├── partition.json ├── search.py ├── search_ui.py ├── update_partition.py └── 使用说明.txt ├── LICENSE ├── READM-En.md └── README.md /Code/B站图标.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChaceQC/bilibili_live_stream_code/9fbfc746ac2c53b67cdaf497c8226054c6a58a23/Code/B站图标.ico -------------------------------------------------------------------------------- /Code/B站推流码获取工具.py: -------------------------------------------------------------------------------- 1 | """ 2 | 说明:程序主入口 3 | 4 | 作者:Chace 5 | 6 | 版本:1.0.1 7 | 8 | 更新时间:2025-05-18 9 | """ 10 | 11 | import sys 12 | import requests 13 | import os 14 | import ctypes 15 | from ctypes import wintypes 16 | import re 17 | from urllib.parse import unquote 18 | from GetCookies import get_cookies 19 | import data as dt 20 | import search_ui as s_ui 21 | 22 | from update_partition import get_new_partition 23 | 24 | # 全局变量 25 | code_file = 'code.txt' 26 | cookies_file = 'cookies.txt' 27 | room_id = None 28 | cookie_str = None 29 | csrf = None 30 | my_path = os.getcwd() 31 | 32 | 33 | class Start: 34 | """ 35 | 开始直播请求参数 36 | """ 37 | header = dt.header 38 | data = dt.start_data 39 | 40 | 41 | class Stop: 42 | """ 43 | 停止直播请求参数 44 | """ 45 | header = dt.header 46 | data = dt.stop_data 47 | 48 | 49 | def get_desktop_folder_path() -> str: 50 | """ 51 | 读取注册表,获取桌面路径 52 | :return: 桌面路径 53 | """ 54 | buf = ctypes.create_unicode_buffer(wintypes.MAX_PATH) 55 | ctypes.windll.shell32.SHGetFolderPathW(0, 0x0000, 0, 0, buf) 56 | return buf.value 57 | 58 | 59 | def manually_input() -> list: 60 | """ 61 | 手动输入room_id,cookie和csrf 62 | :return: 一个列表,按顺序包含room_id,cookie(字符串)和csrf 63 | """ 64 | print('请手动输入room_id,cookie和csrf') 65 | room_id_in = input("请输入room_id:") 66 | cookie_str_in = input("请输入cookie:") 67 | csrf_in = input("请输入csrf:") 68 | return [room_id_in, cookie_str_in, csrf_in] 69 | 70 | 71 | def automatically_input() -> tuple: 72 | """ 73 | 自动获取room_id,cookie和csrf 74 | :return: 一个元组,按顺序包含room_id,cookie(字符串)和csrf 75 | """ 76 | return get_cookies() 77 | 78 | 79 | def custom_pause(context: str, exit_code: int, title: str) -> None: 80 | """ 81 | :param context: 提示信息 82 | :param exit_code: 退出码 83 | :param title: 提示框标题 84 | :return: None 85 | """ 86 | # 定义常量 87 | MB_OK = 0x00000000 88 | MB_ICONERROR = 0x00000010 89 | MB_ICONWARNING = 0x00000030 90 | 91 | in_text = context + '\n\n退出码:' + str(exit_code) + '\n\n点击确定退出程序......' 92 | if exit_code == 0: 93 | MB_ICON = MB_ICONWARNING 94 | else: 95 | MB_ICON = MB_ICONERROR 96 | 97 | ctypes.windll.user32.MessageBoxW(0, in_text, title, MB_OK | MB_ICON) 98 | 99 | 100 | if __name__ == '__main__': 101 | # 使用说明 102 | if os.path.exists(os.path.join(my_path, 'config.ini')): 103 | with open(os.path.join(my_path, 'config.ini'), 'r', encoding='utf-8') as file: 104 | is_first = file.readline().split(':')[1].strip() 105 | if int(is_first) == 1: 106 | if os.path.exists(os.path.join(my_path, '使用说明.txt')): 107 | os.startfile(os.path.join(my_path, '使用说明.txt')) 108 | else: 109 | custom_pause('未找到使用说明.txt,请尝试重新安装此程序!', 7, '错误') 110 | sys.exit(7) 111 | else: 112 | print('提示:使用说明可在安装目录查看,或点击下方链接下载查看!') 113 | print(r'https://download.chacewebsite.cn/uploads/%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E.txt') 114 | if int(is_first) == 1: 115 | with open(os.path.join(my_path, 'config.ini'), 'w', encoding='utf-8') as file: 116 | file.write('use_first: 0') 117 | else: 118 | custom_pause('未找到config.ini,请尝试重新安装此程序!', 8, '错误') 119 | sys.exit(8) 120 | 121 | desktop = get_desktop_folder_path() # 获取桌面路径用于存储文件 122 | 123 | # 如有cookies则直接获取 124 | if os.path.exists(os.path.join(my_path, cookies_file)): 125 | if input("检测到cookies.txt文件,是否使用?(Y/N)\n").upper() == 'Y': 126 | try: 127 | with open(os.path.join(my_path, cookies_file), 'r', encoding='utf-8') as file: 128 | value = [] 129 | for line in file: 130 | if line.strip(): 131 | value.append(line.split(':')[1].strip()) 132 | 133 | room_id = value[0] 134 | cookie_str = value[1] 135 | csrf = value[2] 136 | except Exception as e: 137 | custom_pause('打开或读取cookies.txt文件时出错,错误如下\n' + str(e), 1, '错误') 138 | sys.exit(1) 139 | else: 140 | if input("是否使用自动化获取cookies?(Y/N)\n").upper() == 'Y': 141 | room_id, cookie_str, csrf = automatically_input() 142 | else: 143 | room_id, cookie_str, csrf = manually_input() 144 | else: 145 | if input("是否使用自动化获取cookies?(Y/N)\n").upper() == 'Y': 146 | room_id, cookie_str, csrf = automatically_input() 147 | else: 148 | room_id, cookie_str, csrf = manually_input() 149 | 150 | if (not room_id) or (not cookie_str) or (not csrf): 151 | custom_pause('room_id、cookie或csrf获取失败,请重新尝试,若多次错误,请手动输入', 6, '错误') 152 | sys.exit(6) 153 | 154 | # 存储cookies 155 | try: 156 | with open(os.path.join(my_path, cookies_file), 'w', encoding='utf-8') as file: 157 | file.write('room_id:' + str(room_id) + '\n\n\n') 158 | file.write('cookie:' + str(cookie_str) + '\n\n\n') 159 | file.write('csrf:' + str(csrf) + '\n') 160 | except Exception as e: 161 | custom_pause('写入文件时出错,错误如下\n' + str(e), 3, '错误') 162 | sys.exit(3) 163 | 164 | # 转换为json 165 | cookies_pattern = re.compile(r'(\w+)=([^;]+)(?:;|$)') 166 | cookies = {key: unquote(value) for key, value in cookies_pattern.findall(cookie_str)} 167 | 168 | try: 169 | get_new_partition(cookies) 170 | except Exception as e: 171 | custom_pause('获取直播分区失败,错误如下\n' + str(e), 13, '错误') 172 | sys.exit(13) 173 | 174 | # 设置信息 175 | start = Start() 176 | start.data['room_id'] = room_id 177 | start.data['csrf_token'] = start.data['csrf'] = csrf 178 | # 设置直播分区 179 | live_id = s_ui.set_partition_id_ui() 180 | if not live_id: 181 | custom_pause('设置直播分区失败,请重新尝试!', 11, '错误') 182 | sys.exit(11) 183 | else: 184 | start.data['area_v2'] = live_id 185 | # 设置直播标题 186 | title_data = dt.title_data 187 | title_data['room_id'] = room_id 188 | title_data['csrf_token'] = title_data['csrf'] = csrf 189 | if not s_ui.set_live_title_ui(dt.header, cookies, dt.title_data): 190 | custom_pause('设置直播标题失败,请重新尝试!', 12, '错误') 191 | sys.exit(12) 192 | 193 | # 获取直播推流码并开启直播 194 | print('正在获取直播推流码,请稍等...\n') 195 | try: 196 | response = requests.post('https://api.live.bilibili.com/room/v1/Room/startLive', 197 | cookies=cookies, 198 | headers=start.header, data=start.data) 199 | # print(response.status_code) 200 | # print(response.json()) 201 | except Exception as e: 202 | custom_pause('请求直播推流码时出错,错误如下\n' + str(e), 5, '错误') 203 | sys.exit(5) 204 | else: 205 | if response.status_code != 200 or response.json()['code'] != 0: 206 | print(response.json()) 207 | custom_pause('获取推流码失败,cookie可能失效,请重新获取!', 2, '错误') 208 | 209 | try: 210 | if os.path.exists(os.path.join(my_path, cookies_file)): 211 | os.remove(os.path.join(my_path, cookies_file)) 212 | except Exception as e: 213 | custom_pause('删除cookies.txt文件时出错,错误如下\n' + str(e), 9, '错误') 214 | sys.exit(9) 215 | 216 | sys.exit(2) 217 | else: 218 | rtmp_addr = response.json()['data']['rtmp']['addr'] 219 | rtmp_code = response.json()['data']['rtmp']['code'] 220 | 221 | # 开始直播提示 222 | custom_pause("已开启直播,请迅速进去第三方直播软件进行直播!\n下播时请输入Y或y关闭直播!", 0, '提示') 223 | 224 | # 写入文件 225 | try: 226 | with open(os.path.join(desktop, code_file), 'w', encoding='utf-8') as file: 227 | file.write('服务器地址:' + str(rtmp_addr) + '\n') 228 | file.write('推流码:' + str(rtmp_code)) 229 | except Exception as e: 230 | custom_pause('写入文件时出错,错误如下\n' + str(e), 3, '错误') 231 | sys.exit(3) 232 | else: 233 | print('请在桌面查看code.txt获取推流码') 234 | print('room_id和cookies和csrf已保存于cookies.txt,请于安装目录查看\n') 235 | print('下播前,请勿关闭本脚本!') 236 | os.startfile(os.path.join(desktop, code_file)) # 打开文件,便于复制 237 | 238 | # 关闭直播 239 | stop = Stop() 240 | stop.data['room_id'] = room_id 241 | stop.data['csrf_token'] = stop.data['csrf'] = csrf 242 | 243 | while not (input("下播时请输入Y或y关闭直播:").upper() == 'Y'): 244 | continue 245 | 246 | try: 247 | requests.post('https://api.live.bilibili.com/room/v1/Room/stopLive', 248 | cookies=cookies, 249 | headers=stop.header, data=stop.data) 250 | except Exception as e: 251 | custom_pause('关闭直播时出错,请手动下播,错误如下:\n' + str(e), 4, '错误') 252 | sys.exit(4) 253 | else: 254 | custom_pause('直播已关闭', 0, '提示') 255 | finally: 256 | # 删除推流码文件 257 | try: 258 | if os.path.exists(os.path.join(desktop, code_file)): 259 | os.remove(os.path.join(desktop, code_file)) 260 | except Exception as e: 261 | custom_pause('删除code.txt文件时出错,错误如下:\n' + str(e), 10, '错误') 262 | sys.exit(10) 263 | -------------------------------------------------------------------------------- /Code/B站直播分区修改工具.py: -------------------------------------------------------------------------------- 1 | """ 2 | 说明:直播分区修改工具 3 | 4 | 作者:Chace 5 | 6 | 版本:1.0.2 7 | 8 | 更新时间:2025-05-18 9 | """ 10 | 11 | import os 12 | import re 13 | from urllib.parse import unquote 14 | from B站推流码获取工具 import custom_pause 15 | import sys 16 | import requests 17 | import data as dt 18 | from search_ui import set_partition_id_ui 19 | from update_partition import get_new_partition 20 | 21 | cookies_file = 'cookies.txt' 22 | my_path = os.getcwd() 23 | 24 | 25 | def get_cookies_in_files() -> tuple: 26 | """ 27 | 获取cookies.txt文件内的数据 28 | :return: 一个元组,按顺序包含room_id, cookie_str(字符串), csrf 29 | """ 30 | if os.path.exists(os.path.join(my_path, cookies_file)): 31 | try: 32 | with open(os.path.join(my_path, cookies_file), 'r', encoding='utf-8') as file: 33 | value = [] 34 | for line in file: 35 | if line.strip(): 36 | value.append(line.split(':')[1].strip()) 37 | 38 | room_id = value[0] 39 | cookie_str = value[1] 40 | csrf = value[2] 41 | except Exception as e: 42 | custom_pause('打开或读取cookies.txt文件时出错,错误如下\n' + str(e), 1, '错误') 43 | sys.exit(1) 44 | else: 45 | custom_pause('cookies.txt文件不存在,请先登录!', 2, '错误') 46 | sys.exit(2) 47 | return room_id, cookie_str, csrf 48 | 49 | 50 | if __name__ == '__main__': 51 | 52 | room_id, cookie_str, csrf = get_cookies_in_files() 53 | 54 | # 转换为json 55 | cookies_pattern = re.compile(r'(\w+)=([^;]+)(?:;|$)') 56 | cookies = {key: unquote(value) for key, value in cookies_pattern.findall(cookie_str)} 57 | 58 | try: 59 | get_new_partition(cookies) 60 | except Exception as e: 61 | custom_pause('获取分区列表时出错,错误如下\n' + str(e), 3, '错误') 62 | sys.exit(3) 63 | 64 | headers = dt.header 65 | data = dt.id_data 66 | live_id = set_partition_id_ui() 67 | 68 | if not live_id: 69 | custom_pause("分区选择错误,请重新尝试!", 2, '错误') 70 | sys.exit(2) 71 | 72 | data['room_id'] = room_id 73 | data['csrf_token'] = data['csrf'] = csrf 74 | data['area_id'] = live_id 75 | 76 | try: 77 | requests.post('https://api.live.bilibili.com/room/v1/Room/update', 78 | cookies=cookies, 79 | headers=headers, data=data) 80 | except Exception as e: 81 | custom_pause('更改分区时出错,错误如下\n' + str(e), 1, '错误') 82 | sys.exit(1) 83 | else: 84 | custom_pause('更改分区成功!', 0, '提示') 85 | -------------------------------------------------------------------------------- /Code/B站直播标题修改工具.py: -------------------------------------------------------------------------------- 1 | """ 2 | 说明:直播标题修改工具 3 | 4 | 作者:Chace 5 | 6 | 版本:1.0.1 7 | 8 | 更新时间:2025-02-11 9 | """ 10 | 11 | 12 | import re 13 | from urllib.parse import unquote 14 | from search_ui import set_live_title_ui 15 | import os 16 | import sys 17 | from B站推流码获取工具 import custom_pause 18 | import data as dt 19 | 20 | 21 | cookies_file = 'cookies.txt' 22 | my_path = os.getcwd() 23 | 24 | 25 | def get_cookies_in_files() -> tuple: 26 | """ 27 | 获取cookies.txt文件内的数据 28 | :return: 一个元组,按顺序包含room_id, cookie_str(字符串), csrf 29 | """ 30 | if os.path.exists(os.path.join(my_path, cookies_file)): 31 | try: 32 | with open(os.path.join(my_path, cookies_file), 'r', encoding='utf-8') as file: 33 | value = [] 34 | for line in file: 35 | if line.strip(): 36 | value.append(line.split(':')[1].strip()) 37 | 38 | room_id = value[0] 39 | cookie_str = value[1] 40 | csrf = value[2] 41 | except Exception as e: 42 | custom_pause('打开或读取cookies.txt文件时出错,错误如下\n' + str(e), 1, '错误') 43 | sys.exit(1) 44 | else: 45 | custom_pause('cookies.txt文件不存在,请先登录!', 2, '错误') 46 | sys.exit(2) 47 | return room_id, cookie_str, csrf 48 | 49 | 50 | if __name__ == '__main__': 51 | room_id, cookie_str, csrf = get_cookies_in_files() 52 | 53 | # 转换为json 54 | cookies_pattern = re.compile(r'(\w+)=([^;]+)(?:;|$)') 55 | cookies = {key: unquote(value) for key, value in cookies_pattern.findall(cookie_str)} 56 | 57 | data = dt.title_data 58 | 59 | data['room_id'] = room_id 60 | data['csrf_token'] = data['csrf'] = csrf 61 | 62 | if set_live_title_ui(dt.header, cookies, data): 63 | custom_pause('直播标题修改成功!', 0, '提示') 64 | else: 65 | custom_pause('直播标题修改失败!', 1, '错误') 66 | -------------------------------------------------------------------------------- /Code/GetCookies.py: -------------------------------------------------------------------------------- 1 | """ 2 | 说明:一个用于获取登录二维码和cookies的文档 3 | 4 | 作者:Chace 5 | 6 | 版本:1.0.1 7 | 8 | 更新时间:2025-02-11 9 | 10 | 常用函数: 11 | 12 | 1. get_qrcode() -> dict 13 | 14 | 2. qr_login(qrcode_key: str) -> requests.Response 15 | 16 | 3. login(qr_string: str, qrcode_key: str) -> dict 17 | 18 | 4. get_cookies() -> tuple 19 | """ 20 | 21 | import io 22 | import requests 23 | import data as dt 24 | import time 25 | import qrcode 26 | import json 27 | import search_ui as ui 28 | 29 | 30 | def get_qrcode() -> dict: 31 | """ 32 | 生成登录二维码的URL和key 33 | 34 | :return: 二维码相关数据 35 | """ 36 | url = "https://passport.bilibili.com/x/passport-login/web/qrcode/generate" 37 | headers = {'User-Agent': dt.user_agent} 38 | response = requests.get(url, headers=headers) 39 | return response.json()["data"] 40 | 41 | 42 | def qr_login(qrcode_key: str) -> requests.Response: 43 | """ 44 | 访问Bilibili服务器检查二维码扫描后的登录状态 45 | :param qrcode_key: 二维码秘钥 46 | :return: 返回查询响应 47 | """ 48 | url = "https://passport.bilibili.com/x/passport-login/web/qrcode/poll" 49 | headers = {'User-Agent': dt.user_agent} 50 | params = {'qrcode_key': qrcode_key} 51 | response = requests.get(url, headers=headers, params=params) 52 | return response 53 | 54 | 55 | def get_qr() -> tuple: 56 | """ 57 | 生成二维码字符串,与秘钥一同返回 58 | :return: 一个元组,按顺序包含二维码字符串和秘钥 59 | """ 60 | data = get_qrcode() # 获取二维码数据 61 | qrcode_url = data["url"] 62 | qrcode_key = data["qrcode_key"] 63 | 64 | # 显示二维码URL并生成文本二维码 65 | output = io.StringIO() 66 | qr = qrcode.QRCode() 67 | qr.add_data(qrcode_url) 68 | qr.print_ascii(out=output, tty=False, invert=False) 69 | qr_string = output.getvalue() 70 | 71 | return qr_string, qrcode_key 72 | 73 | 74 | def login(qr_string: str, qrcode_key: str) -> dict: 75 | """ 76 | 登录,获取cookies 77 | :param qr_string: 二维码字符串 78 | :param qrcode_key: 二维码秘钥 79 | :return: 返回cookies,获取失败返回None 80 | """ 81 | print(qr_string) 82 | 83 | print("请扫描二维码进行登录") 84 | 85 | cookies = None 86 | while not cookies: # 等待用户扫描并登录 87 | try: 88 | login_requests = qr_login(qrcode_key) 89 | login_data = login_requests.json() 90 | except Exception: 91 | raise ConnectionError("登录连接错误!") 92 | else: 93 | code = login_data["data"]["code"] 94 | 95 | if code == 0: 96 | cookies = login_requests.cookies.get_dict() 97 | break 98 | elif code == 86038: 99 | print("\n二维码已失效,请重新生成") 100 | elif code == 86090: 101 | print("\n二维码已扫描,等待确认") 102 | 103 | time.sleep(1.5) # 每1.5秒轮询一次 104 | 105 | return cookies 106 | 107 | 108 | def get_room_id_and_csrf(cookies: dict) -> tuple: 109 | """ 110 | 获取room_id和csrf 111 | :param cookies: cookies 112 | :return: 一个元组,按顺序包含room_id和csrf 113 | """ 114 | room_id = None 115 | csrf = None 116 | 117 | dede_user_id = cookies.get("DedeUserID") 118 | url = f"https://api.live.bilibili.com/room/v2/Room/room_id_by_uid?uid={dede_user_id}" 119 | 120 | try: 121 | response = requests.get(url, headers={'User-Agent': dt.user_agent}) 122 | data = response.json() 123 | except Exception: 124 | raise ConnectionError("获取room_id失败!") 125 | else: 126 | if data['code'] == 0: 127 | room_id = data['data']['room_id'] 128 | 129 | csrf = cookies.get("bili_jct") 130 | 131 | return room_id, csrf 132 | 133 | 134 | def get_cookies() -> tuple: 135 | """ 136 | 完全获取room_id、cookies和csrf 137 | :return: 一个元组,按顺序包含room_id、cookies(字符串形式)和csrf 138 | """ 139 | room_id = None 140 | cookies_str = None 141 | csrf = None 142 | 143 | cookies = ui.login_ui() 144 | 145 | if cookies: 146 | room_id, csrf = get_room_id_and_csrf(cookies) 147 | cookies_str = json.dumps(cookies, separators=(';', '='), ensure_ascii=False).replace('{', '').replace('}', 148 | '').replace( 149 | r'"', '') 150 | return room_id, cookies_str, csrf 151 | 152 | 153 | if __name__ == '__main__': 154 | print(get_cookies()) 155 | -------------------------------------------------------------------------------- /Code/config.ini: -------------------------------------------------------------------------------- 1 | use_first: 1 -------------------------------------------------------------------------------- /Code/data.py: -------------------------------------------------------------------------------- 1 | user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36" 2 | 3 | header = \ 4 | { 5 | 'accept': 'application/json, text/plain, */*', 6 | 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 7 | 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', 8 | 'origin': 'https://link.bilibili.com', 9 | 'referer': 'https://link.bilibili.com/p/center/index', 10 | 'sec-ch-ua': '"Microsoft Edge";v="129", "Not=A?Brand";v="8", "Chromium";v="129"', 11 | 'sec-ch-ua-mobile': '?0', 12 | 'sec-ch-ua-platform': '"Windows"', 13 | 'sec-fetch-dest': 'empty', 14 | 'sec-fetch-mode': 'cors', 15 | 'sec-fetch-site': 'same-site', 16 | 'user-agent': user_agent 17 | } 18 | 19 | start_data = \ 20 | { 21 | 'room_id': '', # 填自己的room_id 22 | 'platform': 'android_link', 23 | 'area_v2': '624', 24 | 'backup_stream': '0', 25 | 'csrf_token': '', # 填csrf 26 | 'csrf': '', # 填csrf,这两个值一样的 27 | } 28 | 29 | stop_data = \ 30 | { 31 | 'room_id': '', # 一样,改room_id 32 | 'platform': 'android_link', 33 | 'csrf_token': '', # 一样,改csrf,两个都改 34 | 'csrf': '', 35 | } 36 | 37 | title_data = \ 38 | { 39 | 'room_id': '', # 填自己的room_id 40 | 'platform': 'android_link', 41 | 'title': '', 42 | 'csrf_token': '', # 填csrf 43 | 'csrf': '', # 填csrf,这两个值一样的 44 | } 45 | 46 | id_data = \ 47 | { 48 | 'room_id': '', # 填自己的room_id 49 | 'area_id': 642, 50 | 'activity_id': 0, 51 | 'platform': 'android_link', 52 | 'csrf_token': '', # 填csrf 53 | 'csrf': '', # 填csrf,这两个值一样的 54 | } -------------------------------------------------------------------------------- /Code/search.py: -------------------------------------------------------------------------------- 1 | """ 2 | 说明:一个用于设置分区和直播标题的文档 3 | 4 | 作者:Chace 5 | 6 | 版本:1.0.0 7 | 8 | 更新时间:2025-02-10 9 | 10 | 常用函数: 11 | 12 | 1. get_search_list_all() -> list 13 | 14 | 2. get_search_list(search_type: str) -> list 15 | 16 | 3. get_search_result(search_word: str, search_type: str) -> list 17 | """ 18 | 19 | 20 | import re 21 | import json 22 | import requests 23 | 24 | # 从文档获取分区信息 25 | with open('partition.json', 'r', encoding='utf-8') as f: 26 | partition = json.load(f).get('data') 27 | 28 | 29 | def pinyin(word: str, set_pattern: re.Pattern) -> bool: 30 | """ 31 | 判断待查询分区名字是否满足需要的拼音首字母 32 | :param word: 待查询分区名字 33 | :param set_pattern: 正则判断规则 34 | :return: 返回bool值,满足则返回True,否则返回False 35 | """ 36 | if set_pattern and set_pattern.match(word): 37 | return True 38 | else: 39 | return False 40 | 41 | 42 | def pinyin_pattern(input_word: str) -> re.Pattern | None: 43 | """ 44 | 获取给定的拼音首字母的正则规则 45 | :param input_word: 输入的拼音首字母 46 | :return: 返回正则规则,失败则返回None 47 | """ 48 | for i in range(len(input_word)): 49 | if not input_word[i].isalpha(): 50 | return None 51 | input_lower = input_word.lower() 52 | pattern = re.compile(''.join(f'{c}.*' for c in input_lower), re.IGNORECASE) 53 | return pattern 54 | 55 | 56 | def hanzi(word: str, input_word: str) -> bool: 57 | """ 58 | 判断给定的汉字是否被待查询分区名字包含 59 | :param word: 待查询分区名字 60 | :param input_word: 输入的汉字 61 | :return: 返回bool值,包含则返回True,否则返回False 62 | """ 63 | if input_word in word: 64 | return True 65 | else: 66 | return False 67 | 68 | 69 | def get_search_list_all() -> list: 70 | """ 71 | 获取分区主题名字 72 | :return: 返回所有分区主题名字 73 | """ 74 | results = [] 75 | 76 | for lists in partition: 77 | results.append(lists.get('name')) 78 | 79 | return results 80 | 81 | 82 | def get_search_list(search_type: str) -> list: 83 | """ 84 | 获取分区主题下的所有分区名字 85 | :param search_type: 分区主题名字 86 | :return: 返回特定主题的所有分区的名字 87 | """ 88 | results = [] 89 | 90 | for partition_data in partition: 91 | if partition_data.get('name') == search_type: 92 | partition_list = partition_data.get('list') 93 | for partition_list_data in partition_list: 94 | results.append(partition_list_data.get('name')) 95 | break 96 | 97 | return results 98 | 99 | 100 | def get_search_result(search_word: str, search_type: str) -> list: 101 | """ 102 | 获取满足条件的搜索结果 103 | :param search_word: 输入的拼音首字母或关键词 104 | :param search_type: 分区主题名字 105 | :return: 返回满足条件的搜索结果(单个结果按顺序,包含name,id,pinyin) 106 | """ 107 | results = [] 108 | 109 | input_pattern = pinyin_pattern(search_word) # 获取输入的拼音首字母的正则规则 110 | 111 | for partition_data in partition: 112 | if partition_data.get('name') == search_type: 113 | partition_list = partition_data.get('list') 114 | for partition_list_data in partition_list: 115 | name = partition_list_data.get('name') 116 | pinyin_str = partition_list_data.get('pinyin') 117 | if hanzi(name, search_word) or pinyin(pinyin_str, input_pattern): 118 | results.append({"name": name, "id": partition_list_data.get('id'), "pinyin": pinyin_str}) 119 | break # 找到对应主题则不再继续查找 120 | return results 121 | 122 | 123 | # 以下已弃用 124 | def set_partition_id(): 125 | print('种类:', get_search_list_all()) 126 | 127 | search_t = input("请输入种类:") 128 | print(get_search_list(search_t)) 129 | 130 | search_w = input("请输入拼音首字母或关键词:") 131 | result = get_search_result(search_w, search_t) 132 | print(result) 133 | 134 | partition_result = input("请输入最终选择:") 135 | for i in result: 136 | if not i: 137 | return None 138 | if i.get('name') == partition_result: 139 | return i.get('id') 140 | 141 | 142 | def set_live_title(headers, cookies, data): 143 | try: 144 | live_title = input("请输入标题:") 145 | while len(live_title) > 20: 146 | print("标题长度不能超过20个字符") 147 | live_title = input("请重新输入标题:") 148 | 149 | url = "https://api.live.bilibili.com/room/v1/Room/update" 150 | 151 | data['title'] = live_title 152 | 153 | requests.post(url, headers=headers, cookies=cookies, data=data) 154 | except Exception: 155 | return False 156 | else: 157 | return True 158 | 159 | 160 | if __name__ == '__main__': 161 | print(set_partition_id()) 162 | -------------------------------------------------------------------------------- /Code/search_ui.py: -------------------------------------------------------------------------------- 1 | """ 2 | 说明:一个用于登录、设置分区和直播标题的UI的文档 3 | 4 | 作者:Chace 5 | 6 | 版本:1.0.1 7 | 8 | 更新时间:2025-02-11 9 | 10 | 常用函数: 11 | 12 | 1. login_ui() -> dict | None 13 | 14 | 2. theme_ui() -> str | None 15 | 16 | 3. set_partition_id_ui() -> int | None 17 | 18 | 4. set_live_title_ui(headers: dict, cookies: dict, data: dict) -> bool 19 | """ 20 | import tkinter 21 | import tkinter as tk 22 | import GetCookies as gc 23 | import search as s 24 | from tkinter import messagebox 25 | import requests 26 | 27 | def center_window(root: tkinter.Tk or tkinter.Toplevel, width: int, height: int) -> None: 28 | """ 29 | 此函数用于设置窗口居中显示 30 | 31 | :param root: 接受一个tkinter.Tk()或tkinter.Toplevel()对象 32 | :param width: 接受要设置的窗口宽度 33 | :param height: 接受要设置的窗口高度 34 | :return: None 35 | """ 36 | # 获取屏幕大小 37 | screen_width = root.winfo_screenwidth() 38 | screen_height = root.winfo_screenheight() 39 | 40 | # 计算窗口左上角坐标 41 | x = (screen_width / 2) - (width / 2) 42 | y = (screen_height / 2) - (height / 2) 43 | 44 | # 居中 45 | root.geometry('%dx%d+%d+%d' % (width, height, x, y)) 46 | 47 | 48 | def add_mouse_right(entry: tkinter.Entry, window) -> None: 49 | """ 50 | 此函数用于为文本框添加鼠标右键菜单 51 | 52 | :param entry: 接受一个tkinter.Entry()文本输入框 53 | :param window: 接受一个tkinter.Tk()或tkinter.Toplevel()对象 54 | 55 | :return: None 56 | """ 57 | entry.i = False # 虚构一个bool引用,用于判断是否清空了文本框 58 | 59 | def popup(event): 60 | # 创建弹出菜单 61 | menu.post(event.x_root, event.y_root) 62 | 63 | def copy_text(entry_in): 64 | # 复制选中的文本到剪贴板 65 | data = entry_in.selection_get() 66 | window.clipboard_clear() 67 | window.clipboard_append(data) 68 | 69 | def paste_text(entry_in): 70 | # 从剪贴板粘贴文本 71 | data = window.clipboard_get() 72 | entry_in.insert('insert', data) 73 | 74 | def select_all(entry_in): 75 | # 全选 76 | entry_in.select_range(0, tk.END) 77 | 78 | def copy_and_delete(entry_in): 79 | # 剪切 80 | data = entry_in.selection_get() 81 | window.clipboard_clear() 82 | window.clipboard_append(data) 83 | entry_in.delete('sel.first', 'sel.last') 84 | 85 | def clear_text(entry_in): 86 | # 首次点击,清空文本框 87 | if not entry_in.i: 88 | entry_in.delete(0, tk.END) 89 | entry_in.i = True 90 | 91 | # 创建弹窗菜单 92 | menu = tk.Menu(window, tearoff=0) 93 | menu.add_command(label="全选", command=lambda: select_all(entry)) 94 | menu.add_command(label="复制", command=lambda: copy_text(entry)) 95 | menu.add_command(label="粘贴", command=lambda: paste_text(entry)) 96 | menu.add_command(label="剪切", command=lambda: copy_and_delete(entry)) 97 | 98 | # 绑定鼠标右键点击事件 99 | entry.bind("", popup) 100 | entry.bind("", lambda event: clear_text(entry)) 101 | 102 | 103 | def login_enter(windows: tkinter.Tk, qrcode_key: str, c_list: list, login_label: tkinter.Label, is_login: bool) -> None: 104 | """ 105 | 获取到登录二维码后,轮序检测是否登录成功 106 | :param windows: 回调函数所绑定的窗口 107 | :param qrcode_key: 二维码秘钥 108 | :param c_list: 存储cookies 109 | :param login_label: 登录状态的标签 110 | :param is_login: 是否登录成功或失效 111 | :return: None 112 | """ 113 | if not is_login: 114 | try: 115 | login_requests = gc.qr_login(qrcode_key) 116 | login_data = login_requests.json() 117 | except Exception: 118 | raise ConnectionError("登录连接错误!") 119 | else: 120 | code = login_data["data"]["code"] 121 | 122 | if code == 0: 123 | c_list[0] = login_requests.cookies.get_dict() 124 | windows.destroy() 125 | is_login = True 126 | elif code == 86038: 127 | login_label.config(text="\n二维码已失效,请重新启动软件") 128 | windows.destroy() 129 | is_login = True 130 | elif code == 86090: 131 | login_label.config(text="\n二维码已扫描,等待确认") 132 | windows.after(1000, login_enter, windows, qrcode_key, c_list, login_label, is_login) 133 | 134 | 135 | def login_ui() -> dict | None: 136 | """ 137 | 扫码登录窗口 138 | :return: 登录失败返回None,否则返回cookies 139 | """ 140 | window = tk.Tk() 141 | center_window(window, 600, 550) 142 | # 设置统一宽度的字体 143 | label_font = ('Courier New', 12) # 使用 monospace 字体 144 | window.title('B站推流码获取工具') 145 | qr_str, qr_key = gc.get_qr() 146 | cookies_list = [None] 147 | login_label = tk.Label(window, text='\n扫描二维码登录 ', anchor='center', font=label_font) 148 | login_label.pack() 149 | tk.Label(window, text=qr_str, anchor='center', font=label_font).pack() 150 | login_enter(window, qr_key, cookies_list, login_label, False) 151 | 152 | # 使窗口避免被遮挡,并且获取焦点 153 | window.attributes('-topmost', True) 154 | window.bind("", lambda event: window.attributes('-topmost', False)) 155 | 156 | window.mainloop() 157 | return cookies_list[0] 158 | 159 | 160 | def theme_button(window: tk.Tk, listbox: tk.Listbox, theme: list) -> None: 161 | """ 162 | 分区主题确认键 163 | :param window: 对应窗口 164 | :param listbox: 主题列表 165 | :param theme: 选择的主题 166 | :return: None 167 | """ 168 | selected_id = listbox.curselection() 169 | 170 | if selected_id: 171 | theme[0] = listbox.get(selected_id) 172 | window.destroy() 173 | else: # 至少要选择一个主题 174 | tk.messagebox.showwarning("警告", "请选择一个主题!") 175 | 176 | 177 | def theme_ui() -> str | None: 178 | """ 179 | 分区主题选择窗口 180 | :return: 选择的主题的名字 181 | """ 182 | theme = [None] 183 | 184 | window = tk.Tk() 185 | 186 | center_window(window, 250, 350) 187 | 188 | font = ('Courier New', 12) # 使用 monospace 字体 189 | window.title('B站推流码获取工具') 190 | 191 | label = tk.Label(window, text='\n选择主题:', anchor='center', font=font) 192 | label.pack() 193 | 194 | # 创建 Listbox 195 | listbox = tk.Listbox(window, width=20, height=13, font=font, justify=tk.CENTER) 196 | listbox.pack() 197 | # 添加数据 198 | list_all = s.get_search_list_all() 199 | for list_ in list_all: 200 | listbox.insert(tk.END, list_) 201 | 202 | button = tk.Button(window, text='确定', command=lambda: theme_button(window, listbox, theme)) 203 | button.pack() 204 | 205 | # 使窗口避免被遮挡,并且获取焦点 206 | window.attributes('-topmost', True) 207 | window.bind("", lambda event: window.attributes('-topmost', False)) 208 | 209 | window.mainloop() 210 | 211 | return theme[0] 212 | 213 | 214 | def search_button(entry: tk.Entry, theme_selected: str, listbox: tk.Listbox) -> None: 215 | """ 216 | 搜索按钮回调函数 217 | :param entry: 搜索文本输入框 218 | :param theme_selected: 选择的主题 219 | :param listbox: 搜索结果名字列表 220 | :return: None 221 | """ 222 | # 先清空清空搜索结果列表 223 | listbox.delete(0, tk.END) 224 | 225 | results_data = s.get_search_result(entry.get(), theme_selected) 226 | 227 | for result in results_data: 228 | listbox.insert(tk.END, result['name']) 229 | 230 | 231 | def search_enter_button(window: tk.Tk, result: list, listbox: tk.Listbox, theme_selected: str): 232 | """ 233 | 搜索结果确认键回调函数 234 | :param window: 回调函数按钮所绑定的窗口 235 | :param result: 选择的分区的id 236 | :param listbox: 搜索结果列表 237 | :param theme_selected: 选择的主题 238 | :return: None 239 | """ 240 | if listbox.curselection(): 241 | result[0] = s.get_search_result(listbox.get(listbox.curselection()), theme_selected)[0]['id'] 242 | window.destroy() 243 | else: 244 | tk.messagebox.showwarning("警告", "请选择一个结果!") 245 | 246 | 247 | def init_search_list(listbox: tk.Listbox, theme_selected: str) -> None: 248 | """ 249 | 初始化搜索结果列表为对应主题的全部分区 250 | :param listbox: 搜索结果列表 251 | :param theme_selected: 选择的主题名字 252 | :return: None 253 | """ 254 | results_data = s.get_search_list(theme_selected) 255 | for result in results_data: 256 | listbox.insert(tk.END, result) 257 | 258 | 259 | def set_partition_id_ui() -> int | None: 260 | """ 261 | 选择分区的窗口 262 | :return: 返回选择的分区的id 263 | """ 264 | # 首先获得选择主题 265 | theme_selected = theme_ui() 266 | results = [None] 267 | 268 | if theme_selected: 269 | window = tk.Tk() 270 | center_window(window, 500, 450) 271 | 272 | window.title('B站推流码获取工具') 273 | font = ('Courier New', 12) 274 | 275 | label_theme = tk.Label(window, text=f'\n主题:{theme_selected}', anchor='center', font=font) 276 | label_theme.pack() 277 | 278 | label = tk.Label(window, text='请输入搜索内容:', anchor='center', font=font) 279 | label.pack() 280 | 281 | entry = tk.Entry(window, width=30, font=font) 282 | add_mouse_right(entry, window) 283 | entry.pack() 284 | 285 | label_search_result = tk.Label(window, text='搜索结果:', anchor='center', font=font) 286 | label_search_result.pack() 287 | listbox = tk.Listbox(window, width=20, height=13, font=font, justify=tk.CENTER) 288 | listbox.pack() 289 | init_search_list(listbox, theme_selected) 290 | entry.bind("", lambda event: search_button(entry, theme_selected, listbox)) # 绑定回车键 291 | 292 | button = tk.Button(window, text='搜索', command=lambda: search_button(entry, theme_selected, listbox)) 293 | button.place(x=420, y=62) 294 | 295 | button_enter = tk.Button(window, text='确认', 296 | command=lambda: search_enter_button(window, results, listbox, theme_selected)) 297 | button_enter.pack() 298 | 299 | label_prompt = tk.Label(window, text='注:搜索结果可使用鼠标中间滚轮查看更多\n输入拼音首字母或全称,快速搜索', 300 | anchor='center', font=font) 301 | label_prompt.pack() 302 | 303 | # 使窗口避免被遮挡,并且获取焦点 304 | window.attributes('-topmost', True) 305 | window.bind("", lambda event: window.attributes('-topmost', False)) 306 | 307 | window.mainloop() 308 | 309 | return results[0] 310 | 311 | 312 | def title_button(headers: dict, cookies: dict, data: dict, title: str, window: tk.Tk, is_ok: list) -> None: 313 | """ 314 | 确认直播标题按钮回调函数 315 | :param headers: 请求头 316 | :param cookies: cookies 317 | :param data: 负载数据 318 | :param title: 直播标题 319 | :param window: 对应的窗口 320 | :param is_ok: 是否成功设置 321 | 322 | :return: None 323 | """ 324 | try: 325 | if len(title) > 20: 326 | tk.messagebox.showwarning("警告", "标题长度不能超过20个字符!") 327 | is_ok[0] = False 328 | return 329 | 330 | if not len(title) == 0: # 标题为空则为原标题 331 | url = "https://api.live.bilibili.com/room/v1/Room/update" 332 | 333 | data['title'] = title 334 | 335 | requests.post(url, headers=headers, cookies=cookies, data=data) 336 | except Exception: 337 | is_ok[0] = False 338 | else: 339 | is_ok[0] = True 340 | 341 | # 无论是否设置成功,都关闭窗口 342 | window.destroy() 343 | 344 | 345 | def set_live_title_ui(headers: dict, cookies: dict, data: dict) -> bool: 346 | """ 347 | 设置直播标题窗口 348 | :param headers: 请求头 349 | :param cookies: cookies 350 | :param data: 负载数据 351 | 352 | :return: 是否设置成功 353 | """ 354 | is_ok = [False] 355 | 356 | window = tk.Tk() 357 | center_window(window, 300, 100) 358 | window.title('B站推流码获取工具') 359 | font = ('Courier New', 12) 360 | 361 | label = tk.Label(window, text='请输入直播标题:', anchor='center', font=font) 362 | label.pack() 363 | 364 | entry = tk.Entry(window, width=20, font=font) 365 | add_mouse_right(entry, window) 366 | entry.bind("", lambda event: title_button(headers, cookies, data, entry.get(), window, is_ok)) 367 | entry.pack() 368 | 369 | button = tk.Button(window, text='确定', 370 | command=lambda: title_button(headers, cookies, data, entry.get(), window, is_ok)) 371 | button.pack() 372 | 373 | tk.messagebox.showwarning("提示", "输入为空则为原标题!") 374 | 375 | # 使窗口避免被遮挡,并且获取焦点 376 | window.attributes('-topmost', True) 377 | window.bind("", lambda event: window.attributes('-topmost', False)) 378 | 379 | entry.focus_force() 380 | 381 | window.mainloop() 382 | return is_ok[0] 383 | 384 | 385 | if __name__ == '__main__': 386 | # print(set_partition_id_ui()) 387 | print(set_live_title_ui(1, 1, 1)) 388 | -------------------------------------------------------------------------------- /Code/update_partition.py: -------------------------------------------------------------------------------- 1 | """ 2 | 说明:更新直播分区列表 3 | 4 | 作者:Chace 5 | 6 | 版本:1.0.0 7 | 8 | 更新时间:2025-05-18 9 | """ 10 | 11 | import requests 12 | import json 13 | from data import header 14 | 15 | def get_new_partition(cookies: dict): 16 | """ 17 | 更新直播分区列表 18 | :param cookies: 登录cookies 19 | :return: None 20 | """ 21 | url = "https://api.live.bilibili.com/room/v1/Area/getList?show_pinyin=1" 22 | headers = header 23 | 24 | resp = requests.get(url, cookies=cookies, headers=headers) 25 | 26 | pt_data = resp.json() 27 | 28 | with open("partition.json", "w", encoding="utf-8") as f: 29 | json.dump(pt_data, f, ensure_ascii=False, indent=4) 30 | -------------------------------------------------------------------------------- /Code/使用说明.txt: -------------------------------------------------------------------------------- 1 | 1.本程序适用于用OBS推流直播但不想使用哔哩哔哩直播姬的人群。 2 | 3 | 2.本程序仅用于获取推流地址以及推流码,不会封号等等,任何与账号有关的问题概不负责。 4 | 5 | 3.本程序使用教程: 6 | 手动获取cookie: 7 | (1)登录自己的哔哩哔哩网页客户端; 8 | (2)进入自己的直播间; 9 | (3)点击F12进入开发者模式,选择网络一栏; 10 | (4)给自己发送任意一条弹幕(不开播也可以发); 11 | (5)在网络一栏找到名为send的一条,点击; 12 | (6)在标头中找到请求标头中的Cookie,在负载中找到表单数据的csrf,分别复制下来; 13 | (7)获取自己直播间的ID(在个人中心-我的直播间-开播设置中可找到); 14 | (8)打开程序,按要求输入所需的值(直播间ID即为room_id); 15 | (9)设置标题和分区; 16 | (10)在obs中输入直播的服务器以及推流码然后开播即可; 17 | 自动获取cookie: 18 | (1)扫码登录; 19 | (2)设置标题和分区; 20 | (3)在obs中输入直播的服务器以及推流码然后开播即可; 21 | 22 | 4.注意事项: 23 | (1)一定要使用本程序下播,OBS停止直播不会下播!!!(非常重要!!!!); 24 | (2)如果误关了程序,再进行一遍上述操作即可; 25 | (3)获取的推流码仅可使用一次,再次直播时需重新获取; 26 | (4)保存的cookies在一定时间内可重复使用(问就是我也不知道多久),若失效就再进行一遍上述操作即可; 27 | (5)如登录后无反应,请将鼠标移动至头像处即可; 28 | 29 | 5.其他: 30 | 本程序作者:Chace。 31 | 本程序制作特别感谢:琴子。 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /READM-En.md: -------------------------------------------------------------------------------- 1 | # bilibili_live_stream_code 2 | 1. Used to retrieve third-party streaming codes when preparing for a live broadcast, allowing you to bypass the Bilibili Live Assistant and directly stream using software like OBS. The software also provides the ability to define the live broadcast category and title. 3 | 2. Suitable for people who want to use third-party streaming software to stream live without using the Bilibili Live Assistant. 4 | 5 | ## Disclaimer 6 | 7 | 1. This program is only used to obtain the streaming address and streaming code. It will not result in a ban or any other account-related issues. We are not responsible for any account-related problems. 8 | 9 | ## Usage Tutorial 10 | 11 | ### Manually Retrieve Cookie 12 | 13 | 1. Log in to your Bilibili web client. 14 | 2. Enter your live broadcast room. 15 | 3. Press ***F12*** to enter developer mode and select the Network tab. 16 | 4. Send any barrage message to yourself (you can send messages without starting a live broadcast). 17 | 5. Find the entry named ***send*** in the Network tab and click it. 18 | 6. In the Headers section, find ***Cookie*** under Request Headers and ***csrf*** in the Form Data under Payload. Copy them respectively. 19 | 7. Get your live broadcast room ID (which can be found in Personal Center - My Live Room - Live Settings). 20 | 8. Open the program and enter the required values as instructed (the room ID is ***room_id***). 21 | 9. Set the title and category. 22 | 10. Enter the live broadcast server and streaming code in OBS and start the live broadcast. 23 | 24 | ### Automatically Retrieve Cookie 25 | 26 | 1. Scan the QR code to log in. 27 | 2. Set the title and category. 28 | 3. Enter the live broadcast server and streaming code in OBS and start the live broadcast. 29 | 30 | ## Precautions 31 | 32 | 1. **Be sure to use this program to stop the broadcast. Stopping the broadcast in OBS will not stop the broadcast!!! (Very important!!!!)** 33 | 2. If you accidentally close the program, just repeat the above steps. 34 | 3. The obtained streaming code can only be used once. You need to retrieve it again for the next live broadcast. 35 | 4. The saved Cookies can be reused within a certain period (I also don't know how long). If they become invalid, just repeat the above steps. 36 | 5. When automatically retrieving Cookies, do not keep the mouse on the webpage for too long. After a successful login, remove it, otherwise, it may fail to retrieve! 37 | 6. If there is no response after logging in, please move the mouse to the avatar area. 38 | 39 | ## Others 40 | 41 | 1. Author of this program: [Chace](https://github.com/ChaceQC). 42 | 2. Special thanks for the creation of this program: [Truble-Maker](https://github.com/Truble-Maker). 43 | 44 | ## Chinese version 45 | 1. https://github.com/ChaceQC/bilibili_live_stream_code/blob/main/README.md 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 哔哩哔哩推流码获取工具 2 | 1. 用于在准备直播时获取第三方推流码,以便可以绕开哔哩哔哩直播姬,直接在如OBS等软件中进行直播,软件同时提供定义直播分区和标题功能 3 | 2. 适用于使用第三方推流直播而不想使用哔哩哔哩直播姬的人群。 4 | 5 | ## 声明 6 | 7 | 1. 本程序仅用于获取推流地址以及推流码,不会封号等等,任何与账号有关的问题概不负责。 8 | 9 | ## 使用教程 10 | 11 | ### 手动获取 Cookie 12 | 13 | 1. 登录自己的哔哩哔哩网页客户端。 14 | 2. 进入自己的直播间。 15 | 3. 点击 ***F12*** 进入开发者模式,选择网络一栏。 16 | 4. 给自己发送任意一条弹幕(不开播也可以发)。 17 | 5. 在网络一栏找到名为 ***send*** 的一条,点击。 18 | 6. 在标头中找到请求标头中的 ***Cookie*** ,在负载中找到表单数据的 ***csrf*** ,分别复制下来。 19 | 7. 获取自己直播间的 ID(在个人中心-我的直播间-开播设置中可找到)。 20 | 8. 打开程序,按要求输入所需的值(直播间ID即为 ***room_id*** )。 21 | 9. 设置标题和分区。 22 | 10. 在 OBS 中输入直播的服务器以及推流码然后开播即可。 23 | 24 | ### 自动获取 Cookie 25 | 26 | 1. 扫码登录。 27 | 2. 设置标题和分区。 28 | 3. 在 OBS 中输入直播的服务器以及推流码然后开播即可。 29 | 30 | ## 注意事项 31 | 32 | 1. **一定要使用本程序下播,OBS 停止直播不会下播!!!(非常重要!!!!)** 33 | 2. 如果误关了程序,再进行一遍上述操作即可。 34 | 3. 获取的推流码仅可使用一次,再次直播时需重新获取。 35 | 4. 保存的 Cookies 在一定时间内可重复使用(问就是我也不知道多久),若失效就再进行一遍上述操作即可。 36 | 37 | ## 其他 38 | 39 | 1. 本程序作者:[Chace](https://github.com/ChaceQC)。 40 | 2. 本程序制作特别感谢:[琴子](https://github.com/Truble-Maker)。 41 | 42 | ## 英文版 43 | 1. https://github.com/ChaceQC/bilibili_live_stream_code/blob/main/READM-En.md 44 | --------------------------------------------------------------------------------