From 49ce6660e5b01567559ec6b092be446716f73e8c Mon Sep 17 00:00:00 2001 From: eric Date: Thu, 18 Sep 2025 22:41:14 +0800 Subject: [PATCH] 's' --- .gitignore | 5 + __init__.py | 14 + backup/back | 162 ++ backup/back10 | 250 +++ backup/back2 | 203 +++ backup/back3 | 202 +++ backup/back4 | 208 +++ backup/back5 | 220 +++ backup/back6 | 209 +++ backup/back7 | 257 +++ backup/back8 | 287 ++++ backup/back9 | 275 +++ config.ini | 1 + config/URL_config.ini | 8 + config/config.ini | 129 ++ config/url.txt | 9 + http_clients/__init__.py | 0 http_clients/async_http.py | 59 + http_clients/sync_http.py | 88 + initializer.py | 220 +++ javascript/crypto-js.min.js | 1 + javascript/haixiu.js | 539 ++++++ javascript/liveme.js | 425 +++++ javascript/migu.js | 143 ++ javascript/taobao-sign.js | 78 + javascript/x-bogus.js | 564 ++++++ list | 11 + live.py | 226 +++ liveback | 109 ++ logger.py | 43 + logs/PlayURL.log | 0 logs/streamget.log | 0 main.py | 2078 ++++++++++++++++++++++ proxy.py | 92 + room.py | 158 ++ spider.py | 3232 +++++++++++++++++++++++++++++++++++ stream.py | 446 +++++ test.py | 66 + utils.py | 193 +++ verify.txt | 0 40 files changed, 11210 insertions(+) create mode 100644 .gitignore create mode 100644 __init__.py create mode 100644 backup/back create mode 100644 backup/back10 create mode 100644 backup/back2 create mode 100644 backup/back3 create mode 100644 backup/back4 create mode 100644 backup/back5 create mode 100644 backup/back6 create mode 100644 backup/back7 create mode 100644 backup/back8 create mode 100644 backup/back9 create mode 100644 config.ini create mode 100644 config/URL_config.ini create mode 100644 config/config.ini create mode 100644 config/url.txt create mode 100644 http_clients/__init__.py create mode 100644 http_clients/async_http.py create mode 100644 http_clients/sync_http.py create mode 100644 initializer.py create mode 100644 javascript/crypto-js.min.js create mode 100644 javascript/haixiu.js create mode 100644 javascript/liveme.js create mode 100644 javascript/migu.js create mode 100644 javascript/taobao-sign.js create mode 100644 javascript/x-bogus.js create mode 100644 list create mode 100644 live.py create mode 100644 liveback create mode 100644 logger.py create mode 100644 logs/PlayURL.log create mode 100644 logs/streamget.log create mode 100644 main.py create mode 100644 proxy.py create mode 100644 room.py create mode 100644 spider.py create mode 100644 stream.py create mode 100644 test.py create mode 100644 utils.py create mode 100644 verify.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60cdb0f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +cache/ +__pycache__/ +*.pyc +records/ +*.ts \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..d7060c9 --- /dev/null +++ b/__init__.py @@ -0,0 +1,14 @@ +import os +import sys +from pathlib import Path +from initializer import check_node + +current_file_path = Path(__file__).resolve() +current_dir = current_file_path.parent +JS_SCRIPT_PATH = current_dir / 'javascript' + +execute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0] +node_execute_dir = Path(execute_dir) / 'node' +current_env_path = os.environ.get('PATH') +os.environ['PATH'] = str(node_execute_dir) + os.pathsep + current_env_path +check_node() diff --git a/backup/back b/backup/back new file mode 100644 index 0000000..8342828 --- /dev/null +++ b/backup/back @@ -0,0 +1,162 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import queue +import time +import signal +import sys +import spider +import stream + +class AdaptiveHLS2YouTube: + def __init__(self, max_bitrate=6000, min_bitrate=3000, audio_bitrate=192, + delay_sec=30, segment_time=2, check_interval=5): + self.processes = {} + self.stop_flag = threading.Event() + self.delay_sec = delay_sec + self.segment_time = segment_time + self.check_interval = check_interval + self.buffer_queue = queue.Queue() + self.max_bitrate = max_bitrate + self.min_bitrate = min_bitrate + self.audio_bitrate = audio_bitrate + self.current_bitrate = max_bitrate + + def start_stream(self, real_url, youtube_key, room_name=None): + room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + print(f"[{room_name}] 启动推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._pull_thread, args=(real_url,), daemon=True).start() + threading.Thread(target=self._push_thread, args=(youtube_key, room_name), daemon=True).start() + threading.Thread(target=self._abr_thread, daemon=True).start() + + def _pull_thread(self, real_url): + cmd = [ + "ffmpeg", + "-rw_timeout", "50000000", + "-user_agent", "Mozilla/5.0", + "-headers", "Referer: https://www.douyin.com/", + "-fflags", "+nobuffer+genpts", + "-flags", "low_delay", + "-reconnect", "1", + "-reconnect_at_eof", "1", + "-reconnect_streamed", "1", + "-reconnect_delay_max", "10", + "-i", real_url, + "-c", "copy", + "-f", "mpegts", + "-" + ] + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + while not self.stop_flag.is_set(): + data = proc.stdout.read(188*50) + if not data: + time.sleep(0.05) + continue + self.buffer_queue.put(data) + except Exception as e: + print(f"拉流异常: {e}") + + def _push_thread(self, youtube_key, room_name): + min_buffer = max(1, self.delay_sec // self.segment_time) + while not self.stop_flag.is_set(): + if self.buffer_queue.qsize() < min_buffer: + time.sleep(0.05) + continue + + cmd = [ + "ffmpeg", + "-re", + "-f", "mpegts", + "-i", "-", + "-c:v", "libx264", + "-preset", "veryfast", + "-tune", "zerolatency", + "-b:v", f"{self.current_bitrate}k", + "-maxrate", f"{self.current_bitrate}k", + "-bufsize", f"{self.current_bitrate*2}k", + "-r", "30", + "-g", "60", + "-c:a", "aac", + "-b:a", f"{self.audio_bitrate}k", + "-f", "flv", + f"rtmp://a.rtmp.youtube.com/live2/{youtube_key}" + ] + try: + proc = subprocess.Popen(cmd, stdin=subprocess.PIPE) + self.processes[room_name] = proc + while not self.stop_flag.is_set(): + if not self.buffer_queue.empty(): + data = self.buffer_queue.get() + proc.stdin.write(data) + proc.stdin.flush() + else: + time.sleep(0.01) + except Exception as e: + print(f"[{room_name}] 推流异常: {e}") + + def _abr_thread(self): + # 动态码率调节 + while not self.stop_flag.is_set(): + queue_len = self.buffer_queue.qsize() + if queue_len > 100: # 队列积压大 → 网络慢 → 降低码率 + new_bitrate = max(self.min_bitrate, self.current_bitrate - 500) + elif queue_len < 30: # 队列空 → 网络好 → 提高码率 + new_bitrate = min(self.max_bitrate, self.current_bitrate + 500) + else: + new_bitrate = self.current_bitrate + if new_bitrate != self.current_bitrate: + print(f"[ABR] 调整码率: {self.current_bitrate}k -> {new_bitrate}k") + self.current_bitrate = new_bitrate + time.sleep(self.check_interval) + + def stop_all(self): + print("停止所有推流...") + self.stop_flag.set() + for proc in self.processes.values(): + if proc.poll() is None: + proc.terminate() + +# ----------------- 抖音解析逻辑 ----------------- +async def get_douyin_real_url(record_url, record_quality="原画"): + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data(url=record_url) + else: + json_data = await spider.get_douyin_app_stream_data(url=record_url) + port_info = await stream.get_douyin_stream_url(json_data, record_quality, None) + return port_info.get("record_url") + +# ----------------- 启动推流 ----------------- +def start_douyin_to_youtube(record_url, youtube_key, room_name=None, delay_sec=30): + try: + real_url = asyncio.run(get_douyin_real_url(record_url)) + except Exception as e: + print(f"获取真实直播流失败: {e}") + return + if not real_url: + print("获取真实直播流失败") + return + print(f"实际直播流: {real_url}") + hls2yt = AdaptiveHLS2YouTube(delay_sec=delay_sec) + hls2yt.start_stream(real_url, youtube_key, room_name) + return hls2yt + +# ----------------- 示例 ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/743565594721" + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" + + hls_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY, delay_sec=30) + + def signal_handler(sig, frame): + if hls_instance: + hls_instance.stop_all() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + while True: + time.sleep(60) diff --git a/backup/back10 b/backup/back10 new file mode 100644 index 0000000..78273d2 --- /dev/null +++ b/backup/back10 @@ -0,0 +1,250 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import time +import signal +import sys +import logging +import platform +import spider +import stream +import aiohttp + +# ----------------- Logging Setup ----------------- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%H:%M:%S" +) + +IS_WINDOWS = platform.system() == "Windows" + +# ----------------- Douyin Recorder (推流已删除,增加9:16裁16:9) ----------------- +class DouyinRecorder: + def __init__(self, audio_bitrate=128, max_minutes=8): + self.record_proc = None + self.stop_flag = threading.Event() + self.audio_bitrate = audio_bitrate + self.start_time = None + self.room_name = None + self.out_file = None + self.max_minutes = max_minutes + + os.makedirs("records", exist_ok=True) + + + def _build_record_cmd(self, real_url): + base_name = f"{self.room_name}_{int(time.time())}.mp4" + self.out_file = os.path.join("records", base_name).replace("\\", "/") + + # 处理竖屏视频裁切成16:9,并两边使用模糊背景填充 + # 输入假设是720x1280竖屏,输出16:9 1280x720 + # 原理:1) 原视频缩放高度720,保持宽度自适应 + # 2) 原视频模糊填充16:9背景 + vf_filter = ( + "split [a][b];" + "[a] scale=-1:720 [main];" + "[b] scale=1280:720, boxblur=luma_radius=20:luma_power=1:chroma_radius=20:chroma_power=1 [bg];" + "[bg][main] overlay=(W-w)/2:(H-h)/2" + ) + + cmd = [ + 'ffmpeg', '-y', + '-i', real_url, + '-c:v', 'libx264', + '-preset', 'veryfast', + '-tune', 'zerolatency', + '-vf', vf_filter, + '-c:a', 'aac', + '-b:a', f'{self.audio_bitrate}k', + '-ar', '44100', + '-ac', '2', + '-movflags', '+faststart+frag_keyframe', + self.out_file + ] + return cmd + + + # ----------------- 录制循环 ----------------- + def start_record(self, real_url, room_name=None): + self.room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + logging.info(f"[{self.room_name}] 启动本地录制") + threading.Thread(target=self._run_record_loop, args=(real_url,), daemon=True).start() + threading.Thread(target=self._monitor_thread, daemon=True).start() + + def _run_record_loop(self, real_url): + while not self.stop_flag.is_set(): + try: + self.start_time = time.time() + + # ---- 启动本地录制 ---- + record_cmd = self._build_record_cmd(real_url) + self.record_proc = subprocess.Popen( + record_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + logging.info(f"[{self.room_name}] 本地录制开始: {self.out_file}") + + # ---- 监控 stderr ---- + def monitor_proc(proc): + try: + for line in iter(proc.stderr.readline, b''): + if self.stop_flag.is_set(): + break + if line: + decoded = line.decode('utf-8', errors='ignore').strip() + logging.debug(f"[{self.room_name} record]: {decoded}") + except Exception as e: + logging.error(f"[{self.room_name} record stderr异常: {e}") + + threading.Thread(target=monitor_proc, args=(self.record_proc,), daemon=True).start() + + # ---- 循环检查 ---- + while not self.stop_flag.is_set(): + elapsed = time.time() - self.start_time + if elapsed >= self.max_minutes * 60: + logging.warning(f"[{self.room_name}] 超过 {self.max_minutes} 分钟,自动停止录制") + self.stop() + break + if self.record_proc.poll() is not None: + logging.warning(f"[{self.room_name}] FFmpeg 录制进程退出,1秒后重启...") + break + time.sleep(2) + + except Exception as e: + logging.error(f"[{self.room_name}] FFmpeg录制异常: {e}") + finally: + self._terminate_proc() + self._finalize_recording() + if not self.stop_flag.is_set(): + time.sleep(1) + + def _terminate_proc(self): + if self.record_proc and self.record_proc.poll() is None: + self.record_proc.terminate() + try: + self.record_proc.wait(timeout=2) + except subprocess.TimeoutExpired: + self.record_proc.kill() + self.record_proc = None + + # ----------------- 监控线程 ----------------- + def _monitor_thread(self): + while not self.stop_flag.is_set(): + uptime = int(time.time() - self.start_time) if self.start_time else 0 + m, s = divmod(uptime, 60) + status = "录制中" if self.record_proc and self.record_proc.poll() is None else "启动中" + print(f"\r[{self.room_name}] {status} | 运行时间: {m:02d}:{s:02d} | 音频码率: {self.audio_bitrate}k", end="") + time.sleep(2) + + # ----------------- 停止 ----------------- + def stop(self): + logging.info(f"[{self.room_name}] 停止录制...") + self.stop_flag.set() + self._terminate_proc() + self._finalize_recording() + + def _finalize_recording(self): + if self.out_file and os.path.exists(self.out_file): + try: + if self.record_proc and self.record_proc.poll() is None: + sig = signal.CTRL_BREAK_EVENT if IS_WINDOWS else signal.SIGINT + self.record_proc.send_signal(sig) + try: + self.record_proc.wait(timeout=5) + except subprocess.TimeoutExpired: + self.record_proc.kill() + except Exception: + pass + + elapsed = int(time.time() - self.start_time) if self.start_time else 0 + suffix = f"_{elapsed // 60}min" if elapsed >= 60 else f"_{elapsed}s" + new_name = self.out_file.replace(".mp4", f"{suffix}.mp4") + try: + os.rename(self.out_file, new_name) + except FileExistsError: + new_name = self.out_file.replace(".mp4", f"_{uuid.uuid4().hex[:4]}.mp4") + os.rename(self.out_file, new_name) + size_mb = os.path.getsize(new_name) / (1024 * 1024) + logging.info(f"[{self.room_name}] 本地录制已结束: {new_name} | 大小: {size_mb:.2f} MB") + + +# ----------------- Douyin Stream Parsing ----------------- +async def get_douyin_real_url(record_url, record_quality="原画", proxy_addr=None, cookies=''): + headers = { + "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"), + "Referer": "https://www.douyin.com/", + "Origin": "https://www.douyin.com", + "Accept": "application/json, text/javascript, */*; q=0.01", + } + + session_args = {"headers": headers} + if proxy_addr: + session_args["proxy"] = proxy_addr + + try: + async with aiohttp.ClientSession(**session_args) as session: + for attempt in range(3): + try: + async with session.get(record_url, cookies=cookies) as resp: + text = await resp.text() + if not text or "captcha" in text.lower(): + logging.warning(f"第{attempt+1}次尝试获取流失败,返回内容为空或验证码,重试...") + await asyncio.sleep(1) + continue + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data( + url=record_url, proxy_addr=proxy_addr, cookies=cookies) + else: + json_data = await spider.get_douyin_app_stream_data( + url=record_url, proxy_addr=proxy_addr, cookies=cookies) + + port_info = await stream.get_douyin_stream_url( + json_data, record_quality, proxy_addr) + if port_info and port_info.get("record_url"): + return port_info.get("record_url") + else: + logging.warning(f"第{attempt+1}次尝试解析失败,port_info为空") + await asyncio.sleep(1) + except Exception as e: + logging.warning(f"第{attempt+1}次尝试抓取抖音流异常: {e}") + await asyncio.sleep(1) + logging.error("多次尝试后仍获取抖音真实流失败") + return None + except Exception as e: + logging.error(f"初始化 aiohttp session 失败: {e}") + return None + +def start_douyin_record(record_url, room_name=None, proxy_addr=None, cookies='', audio_bitrate=128): + real_url = asyncio.run(get_douyin_real_url(record_url, proxy_addr=proxy_addr, cookies=cookies)) + if not real_url: + logging.error("获取真实直播流失败") + return None + logging.info(f"实际直播流: {real_url}") + recorder = DouyinRecorder(audio_bitrate=audio_bitrate) + recorder.start_record(real_url, room_name) + return recorder + +# ----------------- Main Program ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/530395023516" + + recorder_instance = start_douyin_record(DOUYIN_URL) + if not recorder_instance: + logging.error("录制实例创建失败,程序退出") + sys.exit(1) + + def signal_handler(sig, frame): + if recorder_instance: + recorder_instance.stop() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + logging.info("程序已启动,本地录制中,按 Ctrl+C 停止...") + while True: + time.sleep(60) diff --git a/backup/back2 b/backup/back2 new file mode 100644 index 0000000..fce5c1e --- /dev/null +++ b/backup/back2 @@ -0,0 +1,203 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import queue +import time +import signal +import sys +import shutil +import spider +import stream + +class AdaptiveHLS2YouTube: + def __init__(self, max_bitrate=6000, min_bitrate=3000, audio_bitrate=192, + delay_sec=30, segment_time=2, check_interval=5): + self.processes = {} + self.stop_flag = threading.Event() + self.delay_sec = delay_sec + self.segment_time = segment_time + self.check_interval = check_interval + self.buffer_queue = queue.Queue() + self.max_bitrate = max_bitrate + self.min_bitrate = min_bitrate + self.audio_bitrate = audio_bitrate + self.current_bitrate = max_bitrate + + def start_stream(self, real_url, youtube_key, room_name=None): + room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + print(f"[{room_name}] 启动推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._pull_thread, args=(real_url,), daemon=True).start() + threading.Thread(target=self._push_thread, args=(youtube_key, room_name), daemon=True).start() + threading.Thread(target=self._abr_thread, daemon=True).start() + + def _pull_thread(self, real_url): + cmd = [ + "ffmpeg", + "-rw_timeout", "50000000", + "-user_agent", "Mozilla/5.0", + "-headers", "Referer: https://www.douyin.com/", + "-fflags", "+nobuffer+genpts", + "-flags", "low_delay", + "-reconnect", "1", + "-reconnect_at_eof", "1", + "-reconnect_streamed", "1", + "-reconnect_delay_max", "10", + "-i", real_url, + "-c", "copy", + "-f", "mpegts", + "-" + ] + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + print("[拉流] FFmpeg 启动成功") + while not self.stop_flag.is_set(): + data = proc.stdout.read(188*50) + if not data: + time.sleep(0.05) + continue + self.buffer_queue.put(data) + except Exception as e: + print(f"拉流异常: {e}") + + def _push_thread(self, youtube_key, room_name): + min_buffer = max(1, self.delay_sec // self.segment_time) + while not self.stop_flag.is_set(): + if self.buffer_queue.qsize() < min_buffer: + time.sleep(0.05) + continue + + cmd = [ + "ffmpeg", + "-re", + "-f", "mpegts", + "-i", "-", + "-c:v", "libx264", + "-preset", "veryfast", + "-tune", "zerolatency", + "-b:v", f"{self.current_bitrate}k", + "-maxrate", f"{self.current_bitrate}k", + "-bufsize", f"{self.current_bitrate*4}k", + "-r", "30", + "-g", "60", + "-c:a", "aac", + "-b:a", f"{self.audio_bitrate}k", + "-ar", "44100", + "-ac", "2", + "-f", "flv", + "-rtmp_live", "live", + f"rtmp://a.rtmp.youtube.com/live2/{youtube_key}" + ] + try: + proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + self.processes[room_name] = proc + + # 读取 stderr 打印日志 + def read_stderr(): + while proc.poll() is None: + line = proc.stderr.readline() + if line: + print(f"[{room_name} FFmpeg]: {line.decode(errors='ignore').strip()}") + threading.Thread(target=read_stderr, daemon=True).start() + + while not self.stop_flag.is_set(): + if not self.buffer_queue.empty(): + data = self.buffer_queue.get() + proc.stdin.write(data) + proc.stdin.flush() + else: + time.sleep(0.01) + except Exception as e: + print(f"[{room_name}] 推流异常: {e}") + + def _abr_thread(self): + while not self.stop_flag.is_set(): + queue_len = self.buffer_queue.qsize() + if queue_len > 100: + new_bitrate = max(self.min_bitrate, self.current_bitrate - 500) + elif queue_len < 30: + new_bitrate = min(self.max_bitrate, self.current_bitrate + 500) + else: + new_bitrate = self.current_bitrate + if new_bitrate != self.current_bitrate: + print(f"[ABR] 调整码率: {self.current_bitrate}k -> {new_bitrate}k") + self.current_bitrate = new_bitrate + time.sleep(self.check_interval) + + def stop_all(self): + print("停止所有推流...") + self.stop_flag.set() + for proc in self.processes.values(): + if proc.poll() is None: + proc.terminate() + + +# ----------------- 抖音解析逻辑 ----------------- +async def get_douyin_real_url(record_url, record_quality="原画"): + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data(url=record_url) + else: + json_data = await spider.get_douyin_app_stream_data(url=record_url) + port_info = await stream.get_douyin_stream_url(json_data, record_quality, None) + return port_info.get("record_url") + + +# ----------------- 启动推流 ----------------- +def start_douyin_to_youtube(record_url, youtube_key, room_name=None, delay_sec=30): + try: + real_url = asyncio.run(get_douyin_real_url(record_url)) + except Exception as e: + print(f"获取真实直播流失败: {e}") + return + if not real_url: + print("获取真实直播流失败") + return + print(f"实际直播流: {real_url}") + hls2yt = AdaptiveHLS2YouTube(delay_sec=delay_sec) + hls2yt.start_stream(real_url, youtube_key, room_name) + return hls2yt + + +# ----------------- 控制台监控面板 ----------------- +def monitor_panel_thread(hls_instance, interval=2, max_width=50): + term_width, _ = shutil.get_terminal_size() + bar_width = min(max_width, term_width - 30) + + while not hls_instance.stop_flag.is_set(): + queue_len = hls_instance.buffer_queue.qsize() + bitrate = hls_instance.current_bitrate + active_push = any(proc and proc.poll() is None for proc in hls_instance.processes.values()) + + queue_bar_len = min(bar_width, queue_len) + queue_bar = "#" * queue_bar_len + "-" * (bar_width - queue_bar_len) + + print("\r", end="") + print(f"[监控] 队列: [{queue_bar}] {queue_len:4} | 码率: {bitrate:4}k | 推流活跃: {active_push}", end="") + time.sleep(interval) + + +# ----------------- 示例 ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/848698179845" + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" + + hls_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY, delay_sec=30) + if not hls_instance: + print("推流实例创建失败,程序退出") + sys.exit(1) + + # 启动实时监控面板 + threading.Thread(target=monitor_panel_thread, args=(hls_instance,), daemon=True).start() + + def signal_handler(sig, frame): + if hls_instance: + hls_instance.stop_all() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + print("程序已启动,按 Ctrl+C 停止...") + while True: + time.sleep(60) diff --git a/backup/back3 b/backup/back3 new file mode 100644 index 0000000..0769adf --- /dev/null +++ b/backup/back3 @@ -0,0 +1,202 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import queue +import time +import signal +import sys +import shutil +import spider +import stream + +class AdaptiveHLS2YouTube: + def __init__(self, max_bitrate=5000, min_bitrate=3000, audio_bitrate=192, + delay_sec=40, segment_time=2, check_interval=5): + self.processes = {} + self.stop_flag = threading.Event() + self.delay_sec = delay_sec + self.segment_time = segment_time + self.check_interval = check_interval + self.buffer_queue = queue.Queue() + self.max_bitrate = max_bitrate + self.min_bitrate = min_bitrate + self.audio_bitrate = audio_bitrate + self.current_bitrate = max_bitrate + + def start_stream(self, real_url, youtube_key, room_name=None): + room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + print(f"[{room_name}] 启动推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._pull_thread, args=(real_url,), daemon=True).start() + threading.Thread(target=self._push_thread, args=(youtube_key, room_name), daemon=True).start() + threading.Thread(target=self._abr_thread, daemon=True).start() + + def _pull_thread(self, real_url): + cmd = [ + "ffmpeg", + "-rw_timeout", "50000000", + "-user_agent", "Mozilla/5.0", + "-headers", "Referer: https://www.douyin.com/", + "-fflags", "+nobuffer+genpts", + "-flags", "low_delay", + "-reconnect", "1", + "-reconnect_at_eof", "1", + "-reconnect_streamed", "1", + "-reconnect_delay_max", "10", + "-i", real_url, + "-c", "copy", + "-f", "mpegts", + "-" + ] + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + print("[拉流] FFmpeg 启动成功") + while not self.stop_flag.is_set(): + data = proc.stdout.read(188*500) # 读取更大块,减少空跑 + if not data: + time.sleep(0.05) + continue + self.buffer_queue.put(data) + except Exception as e: + print(f"拉流异常: {e}") + + def _push_thread(self, youtube_key, room_name): + min_buffer = max(1, self.delay_sec // self.segment_time) + while not self.stop_flag.is_set(): + if self.buffer_queue.qsize() < min_buffer: + time.sleep(0.05) + continue + + cmd = [ + "ffmpeg", + "-re", + "-f", "mpegts", + "-i", "-", + "-c:v", "libx264", + "-preset", "veryfast", + "-tune", "zerolatency", + "-b:v", f"{self.current_bitrate}k", + "-maxrate", f"{self.current_bitrate}k", + "-bufsize", f"{self.current_bitrate*4}k", + "-r", "30", + "-g", "60", + "-s", "720x1280", + "-c:a", "aac", + "-b:a", f"{self.audio_bitrate}k", + "-ar", "44100", + "-ac", "2", + "-f", "flv", + "-rtmp_live", "live", + f"rtmp://a.rtmp.youtube.com/live2/{youtube_key}" + ] + try: + proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + self.processes[room_name] = proc + + def read_stderr(): + while proc.poll() is None: + line = proc.stderr.readline() + if line: + print(f"[{room_name} FFmpeg]: {line.decode(errors='ignore').strip()}") + threading.Thread(target=read_stderr, daemon=True).start() + + while not self.stop_flag.is_set(): + try: + data = self.buffer_queue.get(timeout=1) + proc.stdin.write(data) + proc.stdin.flush() + except queue.Empty: + continue + except Exception as e: + print(f"[{room_name}] 推流异常: {e}") + + def _abr_thread(self): + while not self.stop_flag.is_set(): + queue_len = self.buffer_queue.qsize() + if queue_len > 100: + new_bitrate = max(self.min_bitrate, self.current_bitrate - 300) + elif queue_len < 30: + new_bitrate = min(self.max_bitrate, self.current_bitrate + 300) + else: + new_bitrate = self.current_bitrate + if new_bitrate != self.current_bitrate: + print(f"[ABR] 调整码率: {self.current_bitrate}k -> {new_bitrate}k") + self.current_bitrate = new_bitrate + time.sleep(self.check_interval) + + def stop_all(self): + print("停止所有推流...") + self.stop_flag.set() + for proc in self.processes.values(): + if proc.poll() is None: + proc.terminate() + + +# ----------------- 抖音解析逻辑 ----------------- +async def get_douyin_real_url(record_url, record_quality="原画"): + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data(url=record_url) + else: + json_data = await spider.get_douyin_app_stream_data(url=record_url) + port_info = await stream.get_douyin_stream_url(json_data, record_quality, None) + return port_info.get("record_url") + + +# ----------------- 启动推流 ----------------- +def start_douyin_to_youtube(record_url, youtube_key, room_name=None, delay_sec=40): + try: + real_url = asyncio.run(get_douyin_real_url(record_url)) + except Exception as e: + print(f"获取真实直播流失败: {e}") + return + if not real_url: + print("获取真实直播流失败") + return + print(f"实际直播流: {real_url}") + hls2yt = AdaptiveHLS2YouTube(delay_sec=delay_sec) + hls2yt.start_stream(real_url, youtube_key, room_name) + return hls2yt + + +# ----------------- 控制台监控面板 ----------------- +def monitor_panel_thread(hls_instance, interval=2, max_width=50): + term_width, _ = shutil.get_terminal_size() + bar_width = min(max_width, term_width - 30) + + while not hls_instance.stop_flag.is_set(): + queue_len = hls_instance.buffer_queue.qsize() + bitrate = hls_instance.current_bitrate + active_push = any(proc and proc.poll() is None for proc in hls_instance.processes.values()) + + queue_bar_len = min(bar_width, queue_len) + queue_bar = "#" * queue_bar_len + "-" * (bar_width - queue_bar_len) + + print("\r", end="") + print(f"[监控] 队列: [{queue_bar}] {queue_len:4} | 码率: {bitrate:4}k | 推流活跃: {active_push}", end="") + time.sleep(interval) + + +# ----------------- 示例 ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/364595817295" + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" + + hls_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY, delay_sec=40) + if not hls_instance: + print("推流实例创建失败,程序退出") + sys.exit(1) + + threading.Thread(target=monitor_panel_thread, args=(hls_instance,), daemon=True).start() + + def signal_handler(sig, frame): + if hls_instance: + hls_instance.stop_all() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + print("程序已启动,按 Ctrl+C 停止...") + while True: + time.sleep(60) diff --git a/backup/back4 b/backup/back4 new file mode 100644 index 0000000..c75402c --- /dev/null +++ b/backup/back4 @@ -0,0 +1,208 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import queue +import time +import signal +import sys +import shutil +import spider +import stream + +class AdaptiveHLS2YouTube: + def __init__(self, max_bitrate=5000, min_bitrate=3000, audio_bitrate=192, + delay_sec=40, segment_time=2, check_interval=5): + self.processes = {} + self.stop_flag = threading.Event() + self.delay_sec = delay_sec + self.segment_time = segment_time + self.check_interval = check_interval + self.buffer_queue = queue.Queue(maxsize=500) # 增大队列,防止阻塞 + self.max_bitrate = max_bitrate + self.min_bitrate = min_bitrate + self.audio_bitrate = audio_bitrate + self.current_bitrate = max_bitrate + + def start_stream(self, real_url, youtube_key, room_name=None): + room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + print(f"[{room_name}] 启动推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._pull_thread, args=(real_url,), daemon=True).start() + threading.Thread(target=self._push_thread, args=(youtube_key, room_name), daemon=True).start() + threading.Thread(target=self._abr_thread, daemon=True).start() + + def _pull_thread(self, real_url): + cmd = [ + "ffmpeg", + "-rw_timeout", "50000000", + "-user_agent", "Mozilla/5.0", + "-headers", "Referer: https://www.douyin.com/", + "-fflags", "+nobuffer+genpts", + "-flags", "low_delay", + "-reconnect", "1", + "-reconnect_at_eof", "1", + "-reconnect_streamed", "1", + "-reconnect_delay_max", "10", + "-i", real_url, + "-c", "copy", + "-f", "mpegts", + "-" + ] + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) + print("[拉流] FFmpeg 启动成功") + while not self.stop_flag.is_set(): + data = proc.stdout.read(188*2000) # 大块读取,减少空跑 + if not data: + time.sleep(0.05) + continue + try: + self.buffer_queue.put_nowait(data) # 非阻塞写入队列 + except queue.Full: + continue # 队列满直接丢掉,保证不卡 + except Exception as e: + print(f"拉流异常: {e}") + + def _push_thread(self, youtube_key, room_name): + min_buffer = max(1, self.delay_sec // self.segment_time) + while not self.stop_flag.is_set(): + if self.buffer_queue.qsize() < min_buffer: + time.sleep(0.05) + continue + + cmd = [ + "ffmpeg", + "-re", + "-f", "mpegts", + "-i", "-", + "-c:v", "libx264", + "-preset", "veryfast", + "-tune", "zerolatency", + "-b:v", f"{self.current_bitrate}k", + "-maxrate", f"{self.current_bitrate}k", + "-bufsize", f"{self.current_bitrate*4}k", + "-r", "30", + "-g", "60", + "-s", "720x1280", + "-c:a", "aac", + "-b:a", f"{self.audio_bitrate}k", + "-ar", "44100", + "-ac", "2", + "-f", "flv", + "-rtmp_live", "live", + f"rtmp://a.rtmp.youtube.com/live2/{youtube_key}" + ] + try: + proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + self.processes[room_name] = proc + + def read_stderr(): + while proc.poll() is None: + line = proc.stderr.readline() + if line: + print(f"[{room_name} FFmpeg]: {line.decode(errors='ignore').strip()}") + threading.Thread(target=read_stderr, daemon=True).start() + + while not self.stop_flag.is_set(): + try: + data = self.buffer_queue.get(timeout=0.5) + try: + proc.stdin.write(data) + proc.stdin.flush() + except (BrokenPipeError, OSError): + break # 管道断开,退出推流循环 + except queue.Empty: + continue + except Exception as e: + print(f"[{room_name}] 推流异常: {e}") + + def _abr_thread(self): + while not self.stop_flag.is_set(): + queue_len = self.buffer_queue.qsize() + if queue_len > 100: + new_bitrate = max(self.min_bitrate, self.current_bitrate - 300) + elif queue_len < 30: + new_bitrate = min(self.max_bitrate, self.current_bitrate + 300) + else: + new_bitrate = self.current_bitrate + if new_bitrate != self.current_bitrate: + print(f"[ABR] 调整码率: {self.current_bitrate}k -> {new_bitrate}k") + self.current_bitrate = new_bitrate + time.sleep(max(self.check_interval, 10)) # 降低 ABR 更新频率,避免频繁干扰 + + def stop_all(self): + print("停止所有推流...") + self.stop_flag.set() + for proc in self.processes.values(): + if proc.poll() is None: + proc.terminate() + + +# ----------------- 抖音解析逻辑 ----------------- +async def get_douyin_real_url(record_url, record_quality="原画"): + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data(url=record_url) + else: + json_data = await spider.get_douyin_app_stream_data(url=record_url) + port_info = await stream.get_douyin_stream_url(json_data, record_quality, None) + return port_info.get("record_url") + + +# ----------------- 启动推流 ----------------- +def start_douyin_to_youtube(record_url, youtube_key, room_name=None, delay_sec=40): + try: + real_url = asyncio.run(get_douyin_real_url(record_url)) + except Exception as e: + print(f"获取真实直播流失败: {e}") + return + if not real_url: + print("获取真实直播流失败") + return + print(f"实际直播流: {real_url}") + hls2yt = AdaptiveHLS2YouTube(delay_sec=delay_sec) + hls2yt.start_stream(real_url, youtube_key, room_name) + return hls2yt + + +# ----------------- 控制台监控面板 ----------------- +def monitor_panel_thread(hls_instance, interval=2, max_width=50): + term_width, _ = shutil.get_terminal_size() + bar_width = min(max_width, term_width - 30) + + while not hls_instance.stop_flag.is_set(): + queue_len = hls_instance.buffer_queue.qsize() + bitrate = hls_instance.current_bitrate + active_push = any(proc and proc.poll() is None for proc in hls_instance.processes.values()) + + queue_bar_len = min(bar_width, queue_len) + queue_bar = "#" * queue_bar_len + "-" * (bar_width - queue_bar_len) + + print("\r", end="") + print(f"[监控] 队列: [{queue_bar}] {queue_len:4} | 码率: {bitrate:4}k | 推流活跃: {active_push}", end="") + time.sleep(interval) + + +# ----------------- 示例 ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/125742860435" + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" + + hls_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY, delay_sec=40) + if not hls_instance: + print("推流实例创建失败,程序退出") + sys.exit(1) + + threading.Thread(target=monitor_panel_thread, args=(hls_instance,), daemon=True).start() + + def signal_handler(sig, frame): + if hls_instance: + hls_instance.stop_all() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + print("程序已启动,按 Ctrl+C 停止...") + while True: + time.sleep(60) diff --git a/backup/back5 b/backup/back5 new file mode 100644 index 0000000..ef4db7c --- /dev/null +++ b/backup/back5 @@ -0,0 +1,220 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import queue +import time +import signal +import sys +import shutil +import spider +import stream + + +class AdaptiveHLS2YouTube: + def __init__(self, max_bitrate=5000, min_bitrate=3000, audio_bitrate=192, + delay_sec=40, segment_time=2, check_interval=5): + self.processes = {} + self.stop_flag = threading.Event() + self.delay_sec = delay_sec + self.segment_time = segment_time + self.check_interval = check_interval + self.buffer_queue = queue.Queue(maxsize=800) # 队列加大 + self.max_bitrate = max_bitrate + self.min_bitrate = min_bitrate + self.audio_bitrate = audio_bitrate + self.current_bitrate = max_bitrate + + def start_stream(self, real_url, youtube_key, room_name=None): + room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + print(f"[{room_name}] 启动推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._pull_thread, args=(real_url,), daemon=True).start() + threading.Thread(target=self._push_thread, args=(youtube_key, room_name), daemon=True).start() + threading.Thread(target=self._abr_thread, daemon=True).start() + + def _pull_thread(self, real_url): + cmd = [ + "ffmpeg", + "-rw_timeout", "50000000", + "-user_agent", "Mozilla/5.0", + "-headers", "Referer: https://www.douyin.com/", + "-fflags", "+nobuffer+genpts", + "-flags", "low_delay", + "-reconnect", "1", + "-reconnect_at_eof", "1", + "-reconnect_streamed", "1", + "-reconnect_delay_max", "10", + "-i", real_url, + "-c", "copy", + "-f", "mpegts", + "-" + ] + try: + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + print("[拉流] FFmpeg 启动成功") + + def read_stderr(): + while proc.poll() is None: + line = proc.stderr.readline() + if line: + print(f"[拉流FFmpeg]: {line.decode(errors='ignore').strip()}") + threading.Thread(target=read_stderr, daemon=True).start() + + while not self.stop_flag.is_set(): + data = proc.stdout.read(188 * 5000) # 提升吞吐 + if not data: + time.sleep(0.05) + continue + try: + self.buffer_queue.put_nowait(data) + except queue.Full: + continue + except Exception as e: + print(f"拉流异常: {e}") + + def _push_thread(self, youtube_key, room_name): + min_buffer = max(1, self.delay_sec // self.segment_time) + while not self.stop_flag.is_set(): + if self.buffer_queue.qsize() < min_buffer: + time.sleep(0.05) + continue + + cmd = [ + "ffmpeg", + "-re", + "-f", "mpegts", + "-i", "-", + "-c:v", "libx264", + "-preset", "veryfast", + "-tune", "zerolatency", + "-x264-params", "keyint=60:scenecut=0", + "-b:v", f"{self.current_bitrate}k", + "-maxrate", f"{self.current_bitrate}k", + "-bufsize", f"{self.current_bitrate*4}k", + "-r", "30", + "-g", "60", + "-s", "720x1280", + "-c:a", "aac", + "-b:a", f"{self.audio_bitrate}k", + "-ar", "44100", + "-ac", "2", + "-strict", "-2", + "-f", "flv", + "-rtmp_live", "live", + f"rtmp://a.rtmp.youtube.com/live2/{youtube_key}" + ] + try: + proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE) + self.processes[room_name] = proc + + def read_stderr(): + while proc.poll() is None: + line = proc.stderr.readline() + if line: + print(f"[{room_name} FFmpeg]: {line.decode(errors='ignore').strip()}") + threading.Thread(target=read_stderr, daemon=True).start() + + while not self.stop_flag.is_set(): + try: + data = self.buffer_queue.get(timeout=0.5) + try: + proc.stdin.write(data) + proc.stdin.flush() + except (BrokenPipeError, OSError): + print(f"[{room_name}] 推流管道断开,准备重启") + break + except queue.Empty: + continue + except Exception as e: + print(f"[{room_name}] 推流异常: {e}") + + def _abr_thread(self): + while not self.stop_flag.is_set(): + queue_len = self.buffer_queue.qsize() + if queue_len > 200: + new_bitrate = max(self.min_bitrate, self.current_bitrate - 500) + elif queue_len < 20: + new_bitrate = min(self.max_bitrate, self.current_bitrate + 500) + else: + new_bitrate = self.current_bitrate + if new_bitrate != self.current_bitrate: + print(f"[ABR] 调整码率: {self.current_bitrate}k -> {new_bitrate}k") + self.current_bitrate = new_bitrate + time.sleep(max(self.check_interval, 10)) + + def stop_all(self): + print("停止所有推流...") + self.stop_flag.set() + for proc in self.processes.values(): + if proc.poll() is None: + proc.terminate() + + +# ----------------- 抖音解析逻辑 ----------------- +async def get_douyin_real_url(record_url, record_quality="原画"): + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data(url=record_url) + else: + json_data = await spider.get_douyin_app_stream_data(url=record_url) + port_info = await stream.get_douyin_stream_url(json_data, record_quality, None) + return port_info.get("record_url") + + +# ----------------- 启动推流 ----------------- +def start_douyin_to_youtube(record_url, youtube_key, room_name=None, delay_sec=40): + try: + real_url = asyncio.run(get_douyin_real_url(record_url)) + except Exception as e: + print(f"获取真实直播流失败: {e}") + return + if not real_url: + print("获取真实直播流失败") + return + print(f"实际直播流: {real_url}") + hls2yt = AdaptiveHLS2YouTube(delay_sec=delay_sec) + hls2yt.start_stream(real_url, youtube_key, room_name) + return hls2yt + + +# ----------------- 控制台监控面板 ----------------- +def monitor_panel_thread(hls_instance, interval=2, max_width=50): + term_width, _ = shutil.get_terminal_size() + bar_width = min(max_width, term_width - 30) + + while not hls_instance.stop_flag.is_set(): + queue_len = hls_instance.buffer_queue.qsize() + bitrate = hls_instance.current_bitrate + active_push = sum(1 for proc in hls_instance.processes.values() if proc and proc.poll() is None) + + queue_bar_len = min(bar_width, queue_len) + queue_bar = "#" * queue_bar_len + "-" * (bar_width - queue_bar_len) + + print("\r", end="") + print(f"[监控] 队列: [{queue_bar}] {queue_len:4} | 码率: {bitrate:4}k | 推流进程: {active_push}", end="") + time.sleep(interval) + + +# ----------------- 示例 ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/592179817312" + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" + + hls_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY, delay_sec=40) + if not hls_instance: + print("推流实例创建失败,程序退出") + sys.exit(1) + + threading.Thread(target=monitor_panel_thread, args=(hls_instance,), daemon=True).start() + + def signal_handler(sig, frame): + if hls_instance: + hls_instance.stop_all() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + print("程序已启动,按 Ctrl+C 停止...") + while True: + time.sleep(60) diff --git a/backup/back6 b/backup/back6 new file mode 100644 index 0000000..6e24def --- /dev/null +++ b/backup/back6 @@ -0,0 +1,209 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import time +import signal +import sys +import shutil +import logging +import spider +import stream + +# ----------------- Logging Setup ----------------- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%H:%M:%S" +) + +# ----------------- Low-Latency Douyin to YouTube Streamer ----------------- +class DouyinToYouTubeStreamer: + def __init__(self, bitrate=5000, audio_bitrate=128, resolution='720x1280', fps=30, gop=60): + self.proc = None + self.stop_flag = threading.Event() + self.bitrate = bitrate + self.audio_bitrate = audio_bitrate + self.resolution = resolution + self.fps = fps + self.gop = gop + self.start_time = None + self.room_name = None + self.semaphore = threading.Semaphore(1) + self.pushing = False # 是否正在推流 + + def start_stream(self, real_url, youtube_key, room_name=None): + self.room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + logging.info(f"[{self.room_name}] 启动低延迟推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._run_ffmpeg_loop, args=(real_url, youtube_key), daemon=True).start() + threading.Thread(target=self._monitor_thread, daemon=True).start() + + def _build_ffmpeg_cmd(self, real_url, youtube_key): + user_agent = ("Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 " + "(KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36") + headers = "Referer: https://www.douyin.com/\r\nOrigin: https://www.douyin.com\r\n" + + cmd = [ + 'ffmpeg', '-y', '-v', 'error', '-hide_banner', + '-rw_timeout', '10000000', + '-analyzeduration', '1000000', + '-probesize', '5000000', + '-thread_queue_size', '512', + '-user_agent', user_agent, + '-headers', headers, + '-protocol_whitelist', 'file,pipe,http,https,tcp,tls,udp,rtp,crypto', + '-fflags', '+discardcorrupt+genpts', + '-flags', 'low_delay', + '-http_seekable', '0', + '-avioflags', 'direct', + '-reconnect', '1', + '-reconnect_at_eof', '1', + '-reconnect_streamed', '1', + '-reconnect_delay_max', '5', + '-flush_packets', '1', + '-i', real_url, + '-c:v', 'libx264', + '-preset', 'veryfast', + '-tune', 'zerolatency', + '-crf', '23', + '-pix_fmt', 'yuv420p', + '-profile:v', 'main', + '-level', '3.1', + '-x264-params', f'keyint={self.gop}:scenecut=0:min-keyint={self.gop//2}', + '-g', str(self.gop), + '-r', str(self.fps), + '-s', self.resolution, + '-b:v', f'{self.bitrate}k', + '-maxrate', f'{self.bitrate}k', + '-bufsize', f'{self.bitrate * 2}k', + '-c:a', 'aac', + '-b:a', f'{self.audio_bitrate}k', + '-ar', '44100', + '-ac', '2', + '-strict', '-2', + '-avoid_negative_ts', 'make_zero', + '-f', 'flv', + f'rtmp://a.rtmp.youtube.com/live2/{youtube_key}' + ] + return cmd + + def _run_ffmpeg_loop(self, real_url, youtube_key): + while not self.stop_flag.is_set(): + try: + with self.semaphore: + cmd = self._build_ffmpeg_cmd(real_url, youtube_key) + self.proc = subprocess.Popen( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, + bufsize=0, universal_newlines=False + ) + self.start_time = time.time() + self.pushing = False + logging.info(f"[{self.room_name}] FFmpeg 启动成功 (PID: {self.proc.pid})") + + def read_stderr(): + try: + for line in iter(self.proc.stderr.readline, b''): + if self.stop_flag.is_set(): + break + if line: + decoded = line.decode('utf-8', errors='ignore').strip() + logging.info(f"[{self.room_name} FFmpeg]: {decoded}") + # 判断推流是否成功 + if not self.pushing and "frame=" in decoded and "fps=" in decoded: + self.pushing = True + logging.info(f"[{self.room_name}] 推流到 YouTube 成功") + except Exception as e: + logging.error(f"[{self.room_name}] stderr线程异常: {e}") + + stderr_thread = threading.Thread(target=read_stderr, daemon=True) + stderr_thread.start() + + self.proc.wait() + if not self.stop_flag.is_set(): + logging.warning(f"[{self.room_name}] FFmpeg 退出 (返回码: {self.proc.returncode}),1秒后重启...") + self.pushing = False + except Exception as e: + logging.error(f"[{self.room_name}] FFmpeg 启动异常: {e}") + finally: + if self.proc and self.proc.poll() is None: + self.proc.terminate() + try: + self.proc.wait(timeout=1) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc = None + if not self.stop_flag.is_set(): + time.sleep(1) + + def _monitor_thread(self): + term_width, _ = shutil.get_terminal_size() + bar_width = min(50, term_width - 30) + while not self.stop_flag.is_set(): + if self.proc: + uptime = time.time() - self.start_time if self.start_time else 0 + uptime_str = f"{int(uptime // 60):02d}:{int(uptime % 60):02d}" + status = "推流中" if self.pushing else "启动中" + bar_len = bar_width if self.pushing else 0 + bar = "#" * bar_len + "-" * (bar_width - bar_len) + print(f"\r[{self.room_name}] [{bar}] {status} | 运行时间: {uptime_str} | 码率: {self.bitrate}k", end="") + else: + print(f"\r[{self.room_name}] FFmpeg 未运行", end="") + time.sleep(2) + + def stop(self): + logging.info(f"[{self.room_name}] 停止推流...") + self.stop_flag.set() + if self.proc and self.proc.poll() is None: + self.proc.terminate() + try: + self.proc.wait(timeout=1) + except subprocess.TimeoutExpired: + self.proc.kill() + + +# ----------------- Douyin Stream Parsing ----------------- +async def get_douyin_real_url(record_url, record_quality="原画", proxy_addr=None, cookies=''): + try: + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data(url=record_url, proxy_addr=proxy_addr, cookies=cookies) + else: + json_data = await spider.get_douyin_app_stream_data(url=record_url, proxy_addr=proxy_addr, cookies=cookies) + port_info = await stream.get_douyin_stream_url(json_data, record_quality, proxy_addr) + return port_info.get("record_url") + except Exception as e: + logging.error(f"获取抖音真实流失败: {e}") + return None + +def start_douyin_to_youtube(record_url, youtube_key, room_name=None, proxy_addr=None, cookies='', bitrate=5000): + real_url = asyncio.run(get_douyin_real_url(record_url, proxy_addr, cookies)) + if not real_url: + logging.error("获取真实直播流失败") + return None + logging.info(f"实际直播流: {real_url}") + streamer = DouyinToYouTubeStreamer(bitrate=bitrate) + streamer.start_stream(real_url, youtube_key, room_name) + return streamer + + +# ----------------- Main Program ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/112633436109" + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" + + hls_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY) + if not hls_instance: + logging.error("推流实例创建失败,程序退出") + sys.exit(1) + + def signal_handler(sig, frame): + if hls_instance: + hls_instance.stop() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + logging.info("程序已启动,低延迟推流中,按 Ctrl+C 停止...") + while True: + time.sleep(60) diff --git a/backup/back7 b/backup/back7 new file mode 100644 index 0000000..65ae3df --- /dev/null +++ b/backup/back7 @@ -0,0 +1,257 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import time +import signal +import sys +import logging +import platform +import spider +import stream + +# ----------------- Logging Setup ----------------- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%H:%M:%S" +) + +IS_WINDOWS = platform.system() == "Windows" + +# ----------------- Douyin to YouTube Streamer ----------------- +class DouyinToYouTubeStreamer: + def __init__(self, bitrate=5000, audio_bitrate=128, resolution='720x1280', + fps=30, gop=60, max_minutes=8): + self.push_proc = None + self.record_proc = None + self.stop_flag = threading.Event() + self.bitrate = bitrate + self.audio_bitrate = audio_bitrate + self.resolution = resolution + self.fps = fps + self.gop = gop + self.start_time = None + self.room_name = None + self.pushing = False + self.out_file = None + self.max_minutes = max_minutes + + os.makedirs("records", exist_ok=True) + + def start_stream(self, real_url, youtube_key, room_name=None): + self.room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + logging.info(f"[{self.room_name}] 启动低延迟推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._run_stream_loop, + args=(real_url, youtube_key), daemon=True).start() + threading.Thread(target=self._monitor_thread, daemon=True).start() + + # ----------------- 构建推流命令 ----------------- + def _build_push_cmd(self, real_url, youtube_key): + user_agent = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36") + headers = "Referer: https://www.douyin.com/\r\nOrigin: https://www.douyin.com\r\n" + cmd = [ + 'ffmpeg', '-y', + '-rw_timeout', '10000000', + '-analyzeduration', '1000000', + '-probesize', '5000000', + '-user_agent', user_agent, + '-headers', headers, + '-protocol_whitelist', 'file,pipe,http,https,tcp,tls,udp,rtp,crypto', + '-fflags', '+discardcorrupt+genpts', + '-flags', 'low_delay', + '-http_seekable', '0', + '-i', real_url, + '-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency', + '-b:v', f'{self.bitrate}k', + '-maxrate', f'{self.bitrate}k', + '-bufsize', f'{self.bitrate * 2}k', + '-r', str(self.fps), + '-g', str(self.gop), + '-c:a', 'aac', '-b:a', f'{self.audio_bitrate}k', '-ar', '44100', '-ac', '2', + '-f', 'flv', + f'rtmp://a.rtmp.youtube.com/live2/{youtube_key}' + ] + return cmd + + # ----------------- 构建录制命令 ----------------- + def _build_record_cmd(self, real_url): + base_name = f"{self.room_name}_{int(time.time())}.mp4" + self.out_file = os.path.join("records", base_name).replace("\\", "/") + cmd = [ + 'ffmpeg', '-y', + '-i', real_url, + '-c:v', 'libx264', + '-preset', 'veryfast', + '-tune', 'zerolatency', + '-c:a', 'aac', + '-b:a', f'{self.audio_bitrate}k', + '-ar', '44100', + '-ac', '2', + '-movflags', '+faststart+frag_keyframe', + self.out_file + ] + return cmd + + # ----------------- 推流循环 ----------------- + def _run_stream_loop(self, real_url, youtube_key): + while not self.stop_flag.is_set(): + try: + self.start_time = time.time() + self.pushing = False + + # ---- 启动推流 ---- + push_cmd = self._build_push_cmd(real_url, youtube_key) + self.push_proc = subprocess.Popen( + push_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + # ---- 启动本地录制 ---- + record_cmd = self._build_record_cmd(real_url) + self.record_proc = subprocess.Popen( + record_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + logging.info(f"[{self.room_name}] 本地录制开始: {self.out_file}") + logging.info(f"[{self.room_name}] 推流进程 PID: {self.push_proc.pid}") + + # ---- 监控 stderr ---- + def monitor_proc(proc, name): + try: + for line in iter(proc.stderr.readline, b''): + if self.stop_flag.is_set(): + break + if line: + decoded = line.decode('utf-8', errors='ignore').strip() + if name == "push" and not self.pushing and "frame=" in decoded and "fps=" in decoded: + self.pushing = True + logging.info(f"[{self.room_name}] 推流到 YouTube 成功") + logging.debug(f"[{self.room_name} {name}]: {decoded}") + except Exception as e: + logging.error(f"[{self.room_name} {name} stderr异常: {e}") + + threading.Thread(target=monitor_proc, args=(self.push_proc, "push"), daemon=True).start() + threading.Thread(target=monitor_proc, args=(self.record_proc, "record"), daemon=True).start() + + # ---- 循环检查 ---- + while not self.stop_flag.is_set(): + elapsed = time.time() - self.start_time + if elapsed >= self.max_minutes * 60: + logging.warning(f"[{self.room_name}] 超过 {self.max_minutes} 分钟,自动停止推流和录制") + self.stop() + break + if self.push_proc.poll() is not None or self.record_proc.poll() is not None: + logging.warning(f"[{self.room_name}] FFmpeg 进程退出,1秒后重启...") + break + time.sleep(2) + + except Exception as e: + logging.error(f"[{self.room_name}] FFmpeg异常: {e}") + finally: + self._terminate_procs() + self._finalize_recording() + if not self.stop_flag.is_set(): + time.sleep(1) + + def _terminate_procs(self): + for proc in [self.push_proc, self.record_proc]: + if proc and proc.poll() is None: + proc.terminate() + try: + proc.wait(timeout=2) + except subprocess.TimeoutExpired: + proc.kill() + self.push_proc, self.record_proc = None, None + + # ----------------- 监控线程 ----------------- + def _monitor_thread(self): + while not self.stop_flag.is_set(): + uptime = int(time.time() - self.start_time) if self.start_time else 0 + m, s = divmod(uptime, 60) + status = "推流中" if self.pushing else "启动中" + print(f"\r[{self.room_name}] {status} | 运行时间: {m:02d}:{s:02d} | 码率: {self.bitrate}k", end="") + time.sleep(2) + + # ----------------- 停止 ----------------- + def stop(self): + logging.info(f"[{self.room_name}] 停止推流和录制...") + self.stop_flag.set() + self._terminate_procs() + self._finalize_recording() + + def _finalize_recording(self): + if self.out_file and os.path.exists(self.out_file): + try: + # 根据系统发送信号完成封装 + if self.record_proc and self.record_proc.poll() is None: + sig = signal.CTRL_BREAK_EVENT if IS_WINDOWS else signal.SIGINT + self.record_proc.send_signal(sig) + try: + self.record_proc.wait(timeout=5) + except subprocess.TimeoutExpired: + self.record_proc.kill() + except Exception: + pass + + # 重命名文件带时长 + elapsed = int(time.time() - self.start_time) if self.start_time else 0 + suffix = f"_{elapsed // 60}min" if elapsed >= 60 else f"_{elapsed}s" + new_name = self.out_file.replace(".mp4", f"{suffix}.mp4") + try: + os.rename(self.out_file, new_name) + except FileExistsError: + new_name = self.out_file.replace(".mp4", f"_{uuid.uuid4().hex[:4]}.mp4") + os.rename(self.out_file, new_name) + size_mb = os.path.getsize(new_name) / (1024 * 1024) + logging.info(f"[{self.room_name}] 本地录制已结束: {new_name} | 大小: {size_mb:.2f} MB") + +# ----------------- Douyin Stream Parsing ----------------- +async def get_douyin_real_url(record_url, record_quality="原画", proxy_addr=None, cookies=''): + try: + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data(url=record_url, + proxy_addr=proxy_addr, cookies=cookies) + else: + json_data = await spider.get_douyin_app_stream_data(url=record_url, + proxy_addr=proxy_addr, cookies=cookies) + port_info = await stream.get_douyin_stream_url(json_data, record_quality, proxy_addr) + return port_info.get("record_url") + except Exception as e: + logging.error(f"获取抖音真实流失败: {e}") + return None + +def start_douyin_to_youtube(record_url, youtube_key, room_name=None, + proxy_addr=None, cookies='', bitrate=5000): + real_url = asyncio.run(get_douyin_real_url(record_url, proxy_addr, cookies)) + if not real_url: + logging.error("获取真实直播流失败") + return None + logging.info(f"实际直播流: {real_url}") + streamer = DouyinToYouTubeStreamer(bitrate=bitrate) + streamer.start_stream(real_url, youtube_key, room_name) + return streamer + +# ----------------- Main Program ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/321245312734" + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" + + streamer_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY) + if not streamer_instance: + logging.error("推流实例创建失败,程序退出") + sys.exit(1) + + def signal_handler(sig, frame): + if streamer_instance: + streamer_instance.stop() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + logging.info("程序已启动,低延迟推流中,按 Ctrl+C 停止...") + + while True: + time.sleep(60) diff --git a/backup/back8 b/backup/back8 new file mode 100644 index 0000000..7f65b92 --- /dev/null +++ b/backup/back8 @@ -0,0 +1,287 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import time +import signal +import sys +import logging +import platform +import spider +import stream +import aiohttp + +# ----------------- Logging Setup ----------------- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%H:%M:%S" +) + +IS_WINDOWS = platform.system() == "Windows" + +# ----------------- Douyin to YouTube Streamer ----------------- +class DouyinToYouTubeStreamer: + def __init__(self, bitrate=5000, audio_bitrate=128, resolution='720x1280', + fps=30, gop=60, max_minutes=8): + self.push_proc = None + self.record_proc = None + self.stop_flag = threading.Event() + self.bitrate = bitrate + self.audio_bitrate = audio_bitrate + self.resolution = resolution + self.fps = fps + self.gop = gop + self.start_time = None + self.room_name = None + self.pushing = False + self.out_file = None + self.max_minutes = max_minutes + + os.makedirs("records", exist_ok=True) + + def start_stream(self, real_url, youtube_key, room_name=None): + self.room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + logging.info(f"[{self.room_name}] 启动低延迟推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._run_stream_loop, + args=(real_url, youtube_key), daemon=True).start() + threading.Thread(target=self._monitor_thread, daemon=True).start() + + # ----------------- 构建推流命令 ----------------- + def _build_push_cmd(self, real_url, youtube_key): + user_agent = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36") + headers = "Referer: https://www.douyin.com/\r\nOrigin: https://www.douyin.com\r\n" + cmd = [ + 'ffmpeg', '-y', + '-rw_timeout', '10000000', + '-analyzeduration', '1000000', + '-probesize', '5000000', + '-user_agent', user_agent, + '-headers', headers, + '-protocol_whitelist', 'file,pipe,http,https,tcp,tls,udp,rtp,crypto', + '-fflags', '+discardcorrupt+genpts', + '-flags', 'low_delay', + '-http_seekable', '0', + '-i', real_url, + '-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency', + '-b:v', f'{self.bitrate}k', + '-maxrate', f'{self.bitrate}k', + '-bufsize', f'{self.bitrate * 2}k', + '-r', str(self.fps), + '-g', str(self.gop), + '-c:a', 'aac', '-b:a', f'{self.audio_bitrate}k', '-ar', '44100', '-ac', '2', + '-f', 'flv', + f'rtmp://a.rtmp.youtube.com/live2/{youtube_key}' + ] + return cmd + + # ----------------- 构建录制命令 ----------------- + def _build_record_cmd(self, real_url): + base_name = f"{self.room_name}_{int(time.time())}.mp4" + self.out_file = os.path.join("records", base_name).replace("\\", "/") + cmd = [ + 'ffmpeg', '-y', + '-i', real_url, + '-c:v', 'libx264', + '-preset', 'veryfast', + '-tune', 'zerolatency', + '-c:a', 'aac', + '-b:a', f'{self.audio_bitrate}k', + '-ar', '44100', + '-ac', '2', + '-movflags', '+faststart+frag_keyframe', + self.out_file + ] + return cmd + + # ----------------- 推流循环 ----------------- + def _run_stream_loop(self, real_url, youtube_key): + while not self.stop_flag.is_set(): + try: + self.start_time = time.time() + self.pushing = False + + # ---- 启动推流 ---- + push_cmd = self._build_push_cmd(real_url, youtube_key) + self.push_proc = subprocess.Popen( + push_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + # ---- 启动本地录制 ---- + record_cmd = self._build_record_cmd(real_url) + self.record_proc = subprocess.Popen( + record_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + + logging.info(f"[{self.room_name}] 本地录制开始: {self.out_file}") + logging.info(f"[{self.room_name}] 推流进程 PID: {self.push_proc.pid}") + + # ---- 监控 stderr ---- + def monitor_proc(proc, name): + try: + for line in iter(proc.stderr.readline, b''): + if self.stop_flag.is_set(): + break + if line: + decoded = line.decode('utf-8', errors='ignore').strip() + if name == "push" and not self.pushing and "frame=" in decoded and "fps=" in decoded: + self.pushing = True + logging.info(f"[{self.room_name}] 推流到 YouTube 成功") + logging.debug(f"[{self.room_name} {name}]: {decoded}") + except Exception as e: + logging.error(f"[{self.room_name} {name} stderr异常: {e}") + + threading.Thread(target=monitor_proc, args=(self.push_proc, "push"), daemon=True).start() + threading.Thread(target=monitor_proc, args=(self.record_proc, "record"), daemon=True).start() + + # ---- 循环检查 ---- + while not self.stop_flag.is_set(): + elapsed = time.time() - self.start_time + if elapsed >= self.max_minutes * 60: + logging.warning(f"[{self.room_name}] 超过 {self.max_minutes} 分钟,自动停止推流和录制") + self.stop() + break + if self.push_proc.poll() is not None or self.record_proc.poll() is not None: + logging.warning(f"[{self.room_name}] FFmpeg 进程退出,1秒后重启...") + break + time.sleep(2) + + except Exception as e: + logging.error(f"[{self.room_name}] FFmpeg异常: {e}") + finally: + self._terminate_procs() + self._finalize_recording() + if not self.stop_flag.is_set(): + time.sleep(1) + + def _terminate_procs(self): + for proc in [self.push_proc, self.record_proc]: + if proc and proc.poll() is None: + proc.terminate() + try: + proc.wait(timeout=2) + except subprocess.TimeoutExpired: + proc.kill() + self.push_proc, self.record_proc = None, None + + # ----------------- 监控线程 ----------------- + def _monitor_thread(self): + while not self.stop_flag.is_set(): + uptime = int(time.time() - self.start_time) if self.start_time else 0 + m, s = divmod(uptime, 60) + status = "推流中" if self.pushing else "启动中" + print(f"\r[{self.room_name}] {status} | 运行时间: {m:02d}:{s:02d} | 码率: {self.bitrate}k", end="") + time.sleep(2) + + # ----------------- 停止 ----------------- + def stop(self): + logging.info(f"[{self.room_name}] 停止推流和录制...") + self.stop_flag.set() + self._terminate_procs() + self._finalize_recording() + + def _finalize_recording(self): + if self.out_file and os.path.exists(self.out_file): + try: + if self.record_proc and self.record_proc.poll() is None: + sig = signal.CTRL_BREAK_EVENT if IS_WINDOWS else signal.SIGINT + self.record_proc.send_signal(sig) + try: + self.record_proc.wait(timeout=5) + except subprocess.TimeoutExpired: + self.record_proc.kill() + except Exception: + pass + + elapsed = int(time.time() - self.start_time) if self.start_time else 0 + suffix = f"_{elapsed // 60}min" if elapsed >= 60 else f"_{elapsed}s" + new_name = self.out_file.replace(".mp4", f"{suffix}.mp4") + try: + os.rename(self.out_file, new_name) + except FileExistsError: + new_name = self.out_file.replace(".mp4", f"_{uuid.uuid4().hex[:4]}.mp4") + os.rename(self.out_file, new_name) + size_mb = os.path.getsize(new_name) / (1024 * 1024) + logging.info(f"[{self.room_name}] 本地录制已结束: {new_name} | 大小: {size_mb:.2f} MB") + +# ----------------- Douyin Stream Parsing ----------------- +async def get_douyin_real_url(record_url, record_quality="原画", proxy_addr=None, cookies=''): + headers = { + "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"), + "Referer": "https://www.douyin.com/", + "Origin": "https://www.douyin.com", + "Accept": "application/json, text/javascript, */*; q=0.01", + } + + session_args = {"headers": headers} + if proxy_addr: + session_args["proxy"] = proxy_addr + + try: + async with aiohttp.ClientSession(**session_args) as session: + for attempt in range(3): + try: + async with session.get(record_url, cookies=cookies) as resp: + text = await resp.text() + if not text or "captcha" in text.lower(): + logging.warning(f"第{attempt+1}次尝试获取流失败,返回内容为空或验证码,重试...") + await asyncio.sleep(1) + continue + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data( + url=record_url, proxy_addr=proxy_addr, cookies=cookies) + else: + json_data = await spider.get_douyin_app_stream_data( + url=record_url, proxy_addr=proxy_addr, cookies=cookies) + + port_info = await stream.get_douyin_stream_url( + json_data, record_quality, proxy_addr) + if port_info and port_info.get("record_url"): + return port_info.get("record_url") + else: + logging.warning(f"第{attempt+1}次尝试解析失败,port_info为空") + await asyncio.sleep(1) + except Exception as e: + logging.warning(f"第{attempt+1}次尝试抓取抖音流异常: {e}") + await asyncio.sleep(1) + logging.error("多次尝试后仍获取抖音真实流失败") + return None + except Exception as e: + logging.error(f"初始化 aiohttp session 失败: {e}") + return None + +def start_douyin_to_youtube(record_url, youtube_key, room_name=None, + proxy_addr=None, cookies='', bitrate=5000): + real_url = asyncio.run(get_douyin_real_url(record_url, proxy_addr=proxy_addr, cookies=cookies)) + if not real_url: + logging.error("获取真实直播流失败") + return None + logging.info(f"实际直播流: {real_url}") + streamer = DouyinToYouTubeStreamer(bitrate=bitrate) + streamer.start_stream(real_url, youtube_key, room_name) + return streamer + +# ----------------- Main Program ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/321245312734" + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" + + streamer_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY) + if not streamer_instance: + logging.error("推流实例创建失败,程序退出") + sys.exit(1) + + def signal_handler(sig, frame): + if streamer_instance: + streamer_instance.stop() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + logging.info("程序已启动,低延迟推流中,按 Ctrl+C 停止...") + while True: + time.sleep(60) diff --git a/backup/back9 b/backup/back9 new file mode 100644 index 0000000..78d3954 --- /dev/null +++ b/backup/back9 @@ -0,0 +1,275 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import time +import signal +import sys +import logging +import platform +import aiohttp +import spider # 你的 spider 模块 +import stream # 你的 stream 模块 + +# ----------------- Logging Setup ----------------- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%H:%M:%S" +) + +IS_WINDOWS = platform.system() == "Windows" + +# ----------------- Douyin to YouTube Streamer ----------------- +class DouyinToYouTubeStreamer: + def __init__(self, bitrate=5000, audio_bitrate=128, resolution='720x1280', + fps=30, gop=60, max_minutes=None, enable_recording=False): + self.push_proc = None + self.record_proc = None + self.stop_flag = threading.Event() + self.bitrate = bitrate + self.audio_bitrate = audio_bitrate + self.resolution = resolution + self.fps = fps + self.gop = gop + self.start_time = None + self.room_name = None + self.pushing = False + self.out_file = None + self.max_minutes = max_minutes # None 表示无限制 + self.enable_recording = enable_recording + + os.makedirs("records", exist_ok=True) + + # ----------------- 启动推流 ----------------- + def start_stream(self, real_url, youtube_key, room_name=None): + self.room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + logging.info(f"[{self.room_name}] 启动低延迟推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._run_stream_loop, + args=(real_url, youtube_key), daemon=True).start() + threading.Thread(target=self._monitor_thread, daemon=True).start() + + # ----------------- 构建推流命令 ----------------- + def _build_push_cmd(self, real_url, youtube_key): + user_agent = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36") + headers = "Referer: https://www.douyin.com/\r\nOrigin: https://www.douyin.com\r\n" + cmd = [ + 'ffmpeg', '-y', + '-rw_timeout', '10000000', + '-analyzeduration', '1000000', + '-probesize', '5000000', + '-user_agent', user_agent, + '-headers', headers, + '-protocol_whitelist', 'file,pipe,http,https,tcp,tls,udp,rtp,crypto', + '-fflags', '+discardcorrupt+genpts', + '-flags', 'low_delay', + '-http_seekable', '0', + '-i', real_url, + '-c:v', 'libx264', '-preset', 'veryfast', '-tune', 'zerolatency', + '-b:v', f'{self.bitrate}k', + '-maxrate', f'{self.bitrate}k', + '-bufsize', f'{self.bitrate * 2}k', + '-r', str(self.fps), + '-g', str(self.gop), + '-s', self.resolution, + '-c:a', 'aac', '-b:a', f'{self.audio_bitrate}k', '-ar', '44100', '-ac', '2', + '-f', 'flv', + f'rtmp://a.rtmp.youtube.com/live2/{youtube_key}' + ] + return cmd + + # ----------------- 构建录制命令 ----------------- + def _build_record_cmd(self, real_url): + base_name = f"{self.room_name}_{int(time.time())}.mp4" + self.out_file = os.path.join("records", base_name).replace("\\", "/") + cmd = [ + 'ffmpeg', '-y', + '-i', real_url, + '-c:v', 'libx264', + '-preset', 'veryfast', + '-tune', 'zerolatency', + '-c:a', 'aac', + '-b:a', f'{self.audio_bitrate}k', + '-ar', '44100', + '-ac', '2', + '-movflags', '+faststart+frag_keyframe', + self.out_file + ] + return cmd + + # ----------------- 推流循环 ----------------- + def _run_stream_loop(self, real_url, youtube_key): + while not self.stop_flag.is_set(): + try: + self.start_time = time.time() + self.pushing = False + + # ---- 启动推流 ---- + push_cmd = self._build_push_cmd(real_url, youtube_key) + self.push_proc = subprocess.Popen( + push_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, + bufsize=1, universal_newlines=True + ) + logging.info(f"[{self.room_name}] 推流进程 PID: {self.push_proc.pid}") + + # ---- 启动本地录制 ---- + if self.enable_recording: + record_cmd = self._build_record_cmd(real_url) + self.record_proc = subprocess.Popen( + record_cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, + bufsize=1, universal_newlines=True + ) + logging.info(f"[{self.room_name}] 本地录制开始: {self.out_file}") + + # ---- monitor stderr 阻塞读取(Windows兼容) ---- + def monitor_proc(proc, name): + try: + while not self.stop_flag.is_set() and proc and proc.poll() is None: + line = proc.stderr.readline() + if not line: + continue + if name == "push" and not self.pushing and "frame=" in line and "fps=" in line: + self.pushing = True + logging.info(f"[{self.room_name}] 推流到 YouTube 成功") + except Exception as e: + logging.error(f"[{self.room_name} {name} stderr异常: {e}") + + threading.Thread(target=monitor_proc, args=(self.push_proc, "push"), daemon=True).start() + if self.enable_recording: + threading.Thread(target=monitor_proc, args=(self.record_proc, "record"), daemon=True).start() + + # ---- 循环检查进程状态 ---- + while not self.stop_flag.is_set(): + elapsed = time.time() - self.start_time + if self.max_minutes and elapsed >= self.max_minutes * 60: + logging.warning(f"[{self.room_name}] 超过 {self.max_minutes} 分钟,自动停止推流和录制") + self.stop() + break + + if self.push_proc.poll() is not None: + logging.warning(f"[{self.room_name}] 推流进程退出,5秒后重启...") + time.sleep(5) + break + if self.enable_recording and self.record_proc and self.record_proc.poll() is not None: + logging.warning(f"[{self.room_name}] 录制进程退出,5秒后重启...") + time.sleep(5) + break + + time.sleep(2) + + except Exception as e: + logging.error(f"[{self.room_name}] FFmpeg异常: {e}") + finally: + self._terminate_procs() + if not self.stop_flag.is_set(): + time.sleep(1) + + def _terminate_procs(self): + for proc in [self.push_proc, self.record_proc]: + if proc and proc.poll() is None: + proc.terminate() + try: + proc.wait(timeout=2) + except subprocess.TimeoutExpired: + proc.kill() + self.push_proc, self.record_proc = None, None + + # ----------------- 监控线程 ----------------- + def _monitor_thread(self): + while not self.stop_flag.is_set(): + uptime = int(time.time() - self.start_time) if self.start_time else 0 + m, s = divmod(uptime, 60) + status = "推流中" if self.pushing else "启动中" + print(f"\r[{self.room_name}] {status} | 运行时间: {m:02d}:{s:02d} | 码率: {self.bitrate}k", end="") + time.sleep(5) + + # ----------------- 停止 ----------------- + def stop(self): + logging.info(f"[{self.room_name}] 停止推流和录制...") + self.stop_flag.set() + self._terminate_procs() + + +# ----------------- Douyin Stream Parsing ----------------- +async def get_douyin_real_url(record_url, record_quality="原画", proxy_addr=None, cookies=''): + headers = { + "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"), + "Referer": "https://www.douyin.com/", + "Origin": "https://www.douyin.com", + "Accept": "application/json, text/javascript, */*; q=0.01", + } + + session_args = {"headers": headers} + if proxy_addr: + session_args["proxy"] = proxy_addr + + try: + async with aiohttp.ClientSession(**session_args) as session: + for attempt in range(3): + try: + async with session.get(record_url, cookies=cookies) as resp: + text = await resp.text() + if not text or "captcha" in text.lower(): + logging.warning(f"第{attempt+1}次尝试获取流失败,返回内容为空或验证码,重试...") + await asyncio.sleep(1) + continue + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data( + url=record_url, proxy_addr=proxy_addr, cookies=cookies) + else: + json_data = await spider.get_douyin_app_stream_data( + url=record_url, proxy_addr=proxy_addr, cookies=cookies) + + port_info = await stream.get_douyin_stream_url( + json_data, record_quality, proxy_addr) + if port_info and port_info.get("record_url"): + return port_info.get("record_url") + else: + logging.warning(f"第{attempt+1}次尝试解析失败,port_info为空") + await asyncio.sleep(1) + except Exception as e: + logging.warning(f"第{attempt+1}次尝试抓取抖音流异常: {e}") + await asyncio.sleep(1) + logging.error("多次尝试后仍获取抖音真实流失败") + return None + except Exception as e: + logging.error(f"初始化 aiohttp session 失败: {e}") + return None + +def start_douyin_to_youtube(record_url, youtube_key, room_name=None, + proxy_addr=None, cookies='', bitrate=5000, enable_recording=False): + real_url = asyncio.run(get_douyin_real_url(record_url, proxy_addr=proxy_addr, cookies=cookies)) + if not real_url: + logging.error("获取真实直播流失败") + return None + logging.info(f"实际直播流: {real_url}") + streamer = DouyinToYouTubeStreamer(bitrate=bitrate, enable_recording=enable_recording, max_minutes=None) + streamer.start_stream(real_url, youtube_key, room_name) + return streamer + + +# ----------------- Main Program ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/851742623054" + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" + + # enable_recording=True 可开启本地录制 + streamer_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY, enable_recording=False) + if not streamer_instance: + logging.error("推流实例创建失败,程序退出") + sys.exit(1) + + def signal_handler(sig, frame): + if streamer_instance: + streamer_instance.stop() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + logging.info("程序已启动,低延迟推流中,按 Ctrl+C 停止...") + while True: + time.sleep(60) diff --git a/config.ini b/config.ini new file mode 100644 index 0000000..31625fa --- /dev/null +++ b/config.ini @@ -0,0 +1 @@ +qxvb-r47b-r5ju-6ud3-6k7z \ No newline at end of file diff --git a/config/URL_config.ini b/config/URL_config.ini new file mode 100644 index 0000000..0b59dd9 --- /dev/null +++ b/config/URL_config.ini @@ -0,0 +1,8 @@ +https://live.douyin.com/210443559964,主播: 小小诺 +https://live.douyin.com/525514386431,主播: 阿闯烤肉夹馍 +https://live.douyin.com/8687122573,主播: 小彤炒粉 +https://live.douyin.com/642534242822,主播: (小王_)煎饼果子 +https://live.douyin.com/589037028237,主播: 娜姐辉哥在奋斗 +https://live.douyin.com/743565594721,主播: 汪汪汪小妞 +https://live.douyin.com/249578288248,主播: 思思努力版 +https://live.douyin.com/153648759493,主播: 晨晨_烤冷面 diff --git a/config/config.ini b/config/config.ini new file mode 100644 index 0000000..aede5da --- /dev/null +++ b/config/config.ini @@ -0,0 +1,129 @@ +[录制设置] +language(zh_cn/en) = en +是否跳过代理检测(是/否) = 否 +直播保存路径(不填则默认) = +保存文件夹是否以作者区分 = 是 +保存文件夹是否以时间区分 = 否 +保存文件夹是否以标题区分 = 否 +保存文件名是否包含标题 = 否 +是否去除名称中的表情符号 = 是 +视频保存格式ts|mkv|flv|mp4|mp3音频|m4a音频 = mp4 +原画|超清|高清|标清|流畅 = 原画 +是否使用代理ip(是/否) = 是 +代理地址 = +同一时间访问网络的线程数 = 20 +循环时间(秒) = 300 +排队读取网址时间(秒) = 0 +是否显示循环秒数 = 否 +是否显示直播源地址 = 是 +分段录制是否开启 = 是 +是否强制启用https录制 = 否 +录制空间剩余阈值(gb) = 1.0 +视频分段时间(秒) = 480 +录制完成后自动转为mp4格式 = 是 +mp4格式重新编码为h264 = 否 +追加格式后删除原文件 = 是 +生成时间字幕文件 = 否 +是否录制完成后执行自定义脚本 = 否 +自定义脚本执行命令 = +使用代理录制的平台(逗号分隔) = tiktok, sooplive, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk, shopee, shp, youtu +额外使用代理录制的平台(逗号分隔) = + +[推送配置] +直播状态推送渠道 = +钉钉推送接口链接 = +微信推送接口链接 = +bark推送接口链接 = +bark推送中断级别 = active +bark推送铃声 = +钉钉通知@对象(填手机号) = +钉钉通知@全体(是/否) = 否 +tgapi令牌 = +tg聊天id(个人或者群组id) = +smtp邮件服务器 = +是否使用smtp服务ssl加密(是/否) = +smtp邮件服务器端口 = +邮箱登录账号 = +发件人密码(授权码) = +发件人邮箱 = +发件人显示昵称 = +收件人邮箱 = +ntfy推送地址 = https://ntfy.sh/xxxx +ntfy推送标签 = tada +ntfy推送邮箱 = +pushplus推送token = +自定义推送标题 = +自定义开播推送内容 = +自定义关播推送内容 = +只推送通知不录制(是/否) = 否 +直播推送检测频率(秒) = 1800 +开播推送开启(是/否) = 是 +关播推送开启(是/否) = 否 + +[Cookie] +抖音cookie = ttwid=1%7CB1qls3GdnZhUov9o2NxOMxxYS2ff6OSvEWbv0ytbES4%7C1680522049%7C280d802d6d478e3e78d0c807f7c487e7ffec0ae4e5fdd6a0fe74c3c6af149511; my_rd=1; passport_csrf_token=3ab34460fa656183fccfb904b16ff742; passport_csrf_token_default=3ab34460fa656183fccfb904b16ff742; d_ticket=9f562383ac0547d0b561904513229d76c9c21; n_mh=hvnJEQ4Q5eiH74-84kTFUyv4VK8xtSrpRZG1AhCeFNI; store-region=cn-fj; store-region-src=uid; LOGIN_STATUS=1; __security_server_data_status=1; FORCE_LOGIN=%7B%22videoConsumedRemainSeconds%22%3A180%7D; pwa2=%223%7C0%7C3%7C0%22; download_guide=%223%2F20230729%2F0%22; volume_info=%7B%22isUserMute%22%3Afalse%2C%22isMute%22%3Afalse%2C%22volume%22%3A0.6%7D; strategyABtestKey=%221690824679.923%22; stream_recommend_feed_params=%22%7B%5C%22cookie_enabled%5C%22%3Atrue%2C%5C%22screen_width%5C%22%3A1536%2C%5C%22screen_height%5C%22%3A864%2C%5C%22browser_online%5C%22%3Atrue%2C%5C%22cpu_core_num%5C%22%3A8%2C%5C%22device_memory%5C%22%3A8%2C%5C%22downlink%5C%22%3A10%2C%5C%22effective_type%5C%22%3A%5C%224g%5C%22%2C%5C%22round_trip_time%5C%22%3A150%7D%22; VIDEO_FILTER_MEMO_SELECT=%7B%22expireTime%22%3A1691443863751%2C%22type%22%3Anull%7D; home_can_add_dy_2_desktop=%221%22; __live_version__=%221.1.1.2169%22; device_web_cpu_core=8; device_web_memory_size=8; xgplayer_user_id=346045893336; csrf_session_id=2e00356b5cd8544d17a0e66484946f28; odin_tt=724eb4dd23bc6ffaed9a1571ac4c757ef597768a70c75fef695b95845b7ffcd8b1524278c2ac31c2587996d058e03414595f0a4e856c53bd0d5e5f56dc6d82e24004dc77773e6b83ced6f80f1bb70627; __ac_nonce=064caded4009deafd8b89; __ac_signature=_02B4Z6wo00f01HLUuwwAAIDBh6tRkVLvBQBy9L-AAHiHf7; ttcid=2e9619ebbb8449eaa3d5a42d8ce88ec835; webcast_leading_last_show_time=1691016922379; webcast_leading_total_show_times=1; webcast_local_quality=sd; live_can_add_dy_2_desktop=%221%22; msToken=1JDHnVPw_9yTvzIrwb7cQj8dCMNOoesXbA_IooV8cezcOdpe4pzusZE7NB7tZn9TBXPr0ylxmv-KMs5rqbNUBHP4P7VBFUu0ZAht_BEylqrLpzgt3y5ne_38hXDOX8o=; msToken=jV_yeN1IQKUd9PlNtpL7k5vthGKcHo0dEh_QPUQhr8G3cuYv-Jbb4NnIxGDmhVOkZOCSihNpA2kvYtHiTW25XNNX_yrsv5FN8O6zm3qmCIXcEe0LywLn7oBO2gITEeg=; tt_scid=mYfqpfbDjqXrIGJuQ7q-DlQJfUSG51qG.KUdzztuGP83OjuVLXnQHjsz-BRHRJu4e986 +快手cookie = +tiktok_cookie = +虎牙cookie = +斗鱼cookie = +yy_cookie = +b站cookie = +小红书cookie = +bigo_cookie = +blued_cookie = +sooplive_cookie = +netease_cookie = +千度热播_cookie = +pandatv_cookie = +猫耳fm_cookie = +winktv_cookie = +flextv_cookie = +look_cookie = +twitcasting_cookie = +baidu_cookie = +weibo_cookie = +kugou_cookie = +twitch_cookie = +liveme_cookie = +huajiao_cookie = +liuxing_cookie = +showroom_cookie = +acfun_cookie = +changliao_cookie = +yinbo_cookie = +yingke_cookie = +zhihu_cookie = +chzzk_cookie = +haixiu_cookie = +vvxqiu_cookie = +17live_cookie = +langlive_cookie = +pplive_cookie = +6room_cookie = +lehaitv_cookie = +huamao_cookie = +shopee_cookie = +youtube_cookie = +taobao_cookie = +jd_cookie = +faceit_cookie = +migu_cookie = +lianjie_cookie = +laixiu_cookie = +picarto_cookie = + +[Authorization] +popkontv_token = + +[账号密码] +sooplive账号 = +sooplive密码 = +flextv账号 = +flextv密码 = +popkontv账号 = +partner_code = P-00001 +popkontv密码 = +twitcasting账号类型 = normal +twitcasting账号 = +twitcasting密码 = + diff --git a/config/url.txt b/config/url.txt new file mode 100644 index 0000000..c545076 --- /dev/null +++ b/config/url.txt @@ -0,0 +1,9 @@ +https://live.douyin.com/270711345461,主播: 茄茄 +https://live.douyin.com/957925266814,主播: 卓力君 +https://live.douyin.com/292625589966,主播: 思了个思(游戏版) +https://live.douyin.com/52070571116,主播: 兔然不困了 +https://live.douyin.com/913137650417,主播: 发千金 +https://live.douyin.com/905966903795,主播: 总被男人骗 +https://live.douyin.com/97282309368,主播: 会跳舞的饭 +https://live.douyin.com/640697071840,主播: 煎饼先生 +https://live.douyin.com/167254117139,主播: 乔妮妮 \ No newline at end of file diff --git a/http_clients/__init__.py b/http_clients/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/http_clients/async_http.py b/http_clients/async_http.py new file mode 100644 index 0000000..0bea871 --- /dev/null +++ b/http_clients/async_http.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +import httpx +from typing import Dict, Any +import utils + +OptionalStr = str | None +OptionalDict = Dict[str, Any] | None + + +async def async_req( + url: str, + proxy_addr: OptionalStr = None, + headers: OptionalDict = None, + data: dict | bytes | None = None, + json_data: dict | list | None = None, + timeout: int = 20, + redirect_url: bool = False, + return_cookies: bool = False, + include_cookies: bool = False, + abroad: bool = False, + content_conding: str = 'utf-8', + verify: bool = False, + http2: bool = True +) -> OptionalDict | OptionalStr | tuple: + if headers is None: + headers = {} + try: + proxy_addr = utils.handle_proxy_addr(proxy_addr) + if data or json_data: + async with httpx.AsyncClient(proxy=proxy_addr, timeout=timeout, verify=verify, http2=http2) as client: + response = await client.post(url, data=data, json=json_data, headers=headers) + else: + async with httpx.AsyncClient(proxy=proxy_addr, timeout=timeout, verify=verify, http2=http2) as client: + response = await client.get(url, headers=headers, follow_redirects=True) + + if redirect_url: + return str(response.url) + elif return_cookies: + cookies_dict = {name: value for name, value in response.cookies.items()} + return (response.text, cookies_dict) if include_cookies else cookies_dict + else: + resp_str = response.text + except Exception as e: + resp_str = str(e) + + return resp_str + + +async def get_response_status(url: str, proxy_addr: OptionalStr = None, headers: OptionalDict = None, + timeout: int = 10, abroad: bool = False, verify: bool = False, http2=False) -> bool: + + try: + proxy_addr = utils.handle_proxy_addr(proxy_addr) + async with httpx.AsyncClient(proxy=proxy_addr, timeout=timeout, verify=verify) as client: + response = await client.head(url, headers=headers, follow_redirects=True) + return response.status_code == 200 + except Exception as e: + print(e) + return False diff --git a/http_clients/sync_http.py b/http_clients/sync_http.py new file mode 100644 index 0000000..76d7c07 --- /dev/null +++ b/http_clients/sync_http.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +import gzip +import urllib.parse +import urllib.error +import requests +import ssl +import json +import urllib.request + +no_proxy_handler = urllib.request.ProxyHandler({}) +opener = urllib.request.build_opener(no_proxy_handler) + +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE +OptionalStr = str | None +OptionalDict = dict | None + + +def sync_req( + url: str, + proxy_addr: OptionalStr = None, + headers: OptionalDict = None, + data: dict | bytes | None = None, + json_data: dict | list | None = None, + timeout: int = 20, + redirect_url: bool = False, + abroad: bool = False, + content_conding: str = 'utf-8' +) -> str: + if headers is None: + headers = {} + try: + if proxy_addr: + proxies = { + 'http': proxy_addr, + 'https': proxy_addr + } + if data or json_data: + response = requests.post( + url, data=data, json=json_data, headers=headers, proxies=proxies, timeout=timeout + ) + else: + response = requests.get(url, headers=headers, proxies=proxies, timeout=timeout) + if redirect_url: + return response.url + resp_str = response.text + else: + if data and not isinstance(data, bytes): + data = urllib.parse.urlencode(data).encode(content_conding) + if json_data and isinstance(json_data, (dict, list)): + data = json.dumps(json_data).encode(content_conding) + + req = urllib.request.Request(url, data=data, headers=headers) + + try: + if abroad: + response = urllib.request.urlopen(req, timeout=timeout) + else: + response = opener.open(req, timeout=timeout) + if redirect_url: + return response.url + content_encoding = response.info().get('Content-Encoding') + try: + if content_encoding == 'gzip': + with gzip.open(response, 'rt', encoding=content_conding) as gzipped: + resp_str = gzipped.read() + else: + resp_str = response.read().decode(content_conding) + finally: + response.close() + + except urllib.error.HTTPError as e: + if e.code == 400: + resp_str = e.read().decode(content_conding) + else: + raise + except urllib.error.URLError as e: + print(f"URL Error: {e}") + raise + except Exception as e: + print(f"An error occurred: {e}") + raise + + except Exception as e: + resp_str = str(e) + + return resp_str diff --git a/initializer.py b/initializer.py new file mode 100644 index 0000000..faa9f7b --- /dev/null +++ b/initializer.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- + +""" +Author: Hmily +GitHub:https://github.com/ihmily +Copyright (c) 2024 by Hmily, All Rights Reserved. +""" + +import os +import subprocess +import sys +import platform +import zipfile +from pathlib import Path +import requests +import re +import distro +from tqdm import tqdm +from logger import logger + +current_platform = platform.system() +execute_dir = os.path.split(os.path.realpath(sys.argv[0]))[0] +current_env_path = os.environ.get('PATH') + + +def unzip_file(zip_path: str | Path, extract_to: str | Path, delete: bool = True) -> None: + if not os.path.exists(extract_to): + os.makedirs(extract_to) + + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(extract_to) + + if delete and os.path.exists(zip_path): + os.remove(zip_path) + + +def install_nodejs_windows(): + try: + logger.warning("Node.js is not installed.") + logger.debug("Installing the stable version of Node.js for Windows...") + response = requests.get('https://nodejs.cn/download/') + if response.status_code == 200: + match = re.search('https://npmmirror.com/mirrors/node/(v.*?)/node-(v.*?)-x64.msi', + response.text) + if match: + version = match.group(1) + system_bit = 'x64' if '32' not in platform.machine() else 'x86' + url = f'https://npmmirror.com/mirrors/node/{version}/node-{version}-win-{system_bit}.zip' + else: + logger.error("Failed to retrieve the download URL for the latest version of Node.js...") + return + + full_file_name = url.rsplit('/', maxsplit=1)[-1] + zip_file_path = Path(execute_dir) / full_file_name + + if Path(zip_file_path).exists(): + logger.debug("Node.js installation file already exists, start install...") + else: + response = requests.get(url, stream=True) + total_size = int(response.headers.get('Content-Length', 0)) + block_size = 1024 + + with tqdm(total=total_size, unit="B", unit_scale=True, + ncols=100, desc=f'Downloading Node.js ({version})') as t: + with open(zip_file_path, 'wb') as f: + for data in response.iter_content(block_size): + t.update(len(data)) + f.write(data) + + unzip_file(zip_file_path, execute_dir) + extract_dir_path = str(zip_file_path).rsplit('.', maxsplit=1)[0] + f_path, f_name = os.path.splitext(zip_file_path) + new_extract_dir_path = Path(f_path).parent / 'node' + if Path(extract_dir_path).exists() and not Path(new_extract_dir_path).exists(): + os.rename(extract_dir_path, new_extract_dir_path) + os.environ['PATH'] = execute_dir + '/node' + os.pathsep + current_env_path + result = subprocess.run(["node", "-v"], capture_output=True) + if result.returncode == 0: + logger.debug('Node.js installation was successful. Restart for changes to take effect') + else: + logger.debug('Node.js installation failed') + return True + else: + logger.error("Failed to retrieve the Node.js version page") + + except Exception as e: + logger.error(f"type: {type(e).__name__}, Node.js installation failed {e}") + + +def install_nodejs_centos(): + try: + logger.warning("Node.js is not installed.") + logger.debug("Installing the latest version of Node.js for CentOS...") + result = subprocess.run('curl -fsSL https://mirrors.tuna.tsinghua.edu.cn/nodesource/rpm/setup_lts.x | ' + 'bash -', shell=True, capture_output=True) + if result.returncode != 0: + logger.error("Failed to run NodeSource installation script") + return + + result = subprocess.run(['yum', 'install', '-y', 'epel-release'], capture_output=True) + if result.returncode != 0: + logger.error("Failed to install EPEL repository") + return + + result = subprocess.run(['yum', 'install', '-y', 'nodejs'], capture_output=True) + if result.returncode == 0: + logger.debug('Node.js installation was successful. Restart for changes to take effect.') + return True + else: + logger.error("Node.js installation failed") + + except Exception as e: + logger.error(f"type: {type(e).__name__}, Node.js installation failed {e}") + + +def install_nodejs_ubuntu(): + try: + logger.warning("Node.js is not installed.") + logger.debug("Installing the latest version of Node.js for Ubuntu...") + install_script = 'curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -' + result = subprocess.run(install_script, shell=True, capture_output=True) + if result.returncode != 0: + logger.error("Failed to run NodeSource installation script") + return + + install_command = ['apt', 'install', '-y', 'nodejs'] + result = subprocess.run(install_command, capture_output=True) + if result.returncode == 0: + logger.debug('Node.js installation was successful. Restart for changes to take effect.') + return True + else: + logger.error("Node.js installation failed") + except Exception as e: + logger.error(f"type: {type(e).__name__}, Node.js installation failed, {e}") + + +def install_nodejs_mac(): + logger.warning("Node.js is not installed.") + logger.debug("Installing the latest version of Node.js for macOS...") + try: + result = subprocess.run(["brew", "install", "node"], capture_output=True) + if result.returncode == 0: + logger.debug('Node.js installation was successful. Restart for changes to take effect.') + return True + else: + logger.error("Node.js installation failed") + except subprocess.CalledProcessError as e: + logger.error(f"Failed to install Node.js using Homebrew. {e}") + logger.error("Please install Node.js manually or check your Homebrew installation.") + except Exception as e: + logger.error(f"An unexpected error occurred: {e}") + + +def get_package_manager(): + dist_id = distro.id() + if dist_id in ["centos", "fedora", "rhel", "amzn", "oracle", "scientific", "opencloudos", "alinux"]: + return "RHS" + else: + return "DBS" + + +def install_nodejs() -> bool: + if current_platform == "Windows": + return install_nodejs_windows() + elif current_platform == "Linux": + os_type = get_package_manager() + if os_type == "RHS": + return install_nodejs_centos() + else: + return install_nodejs_ubuntu() + elif current_platform == "Darwin": + return install_nodejs_mac() + else: + logger.debug(f"Node.js auto installation is not supported on this platform: {current_platform}. " + f"Please install Node.js manually.") + return False + + +def ensure_nodejs_installed(func): + def wrapper(*args, **kwargs): + try: + result = subprocess.run(['node', '-v'], capture_output=True) + version = result.stdout.strip() + if result.returncode == 0 and version: + return func(*args, **kwargs) + except FileNotFoundError: + pass + return False + + def wrapped_func(*args, **kwargs): + if sys.version_info >= (3, 7): + res = wrapper(*args, **kwargs) + else: + res = wrapper(*args, **kwargs) + if not res: + install_nodejs() + res = wrapper(*args, **kwargs) + + if not res: + raise RuntimeError("Node.js is not installed.") + + return func(*args, **kwargs) + + return wrapped_func + + +def check_nodejs_installed() -> bool: + try: + result = subprocess.run(['node', '-v'], capture_output=True) + version = result.stdout.strip() + if result.returncode == 0 and version: + return True + except FileNotFoundError: + pass + return False + + +def check_node() -> bool: + if not check_nodejs_installed(): + return install_nodejs() diff --git a/javascript/crypto-js.min.js b/javascript/crypto-js.min.js new file mode 100644 index 0000000..fb96be7 --- /dev/null +++ b/javascript/crypto-js.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports?module.exports=exports=e():"function"==typeof define&&define.amd?define([],e):t.CryptoJS=e()}(this,function(){var W,O,I,U,K,X,L,l,j,T,t,N,q,e,Z,V,G,J,Q,Y,$,t1,e1,r1,i1,o1,n1,s,s1,c1,a1,h1,l1,o,f1,r,d1,u1,n,c,a,h,f,d,i=function(h){var i;if("undefined"!=typeof window&&window.crypto&&(i=window.crypto),"undefined"!=typeof self&&self.crypto&&(i=self.crypto),!(i=!(i=!(i="undefined"!=typeof globalThis&&globalThis.crypto?globalThis.crypto:i)&&"undefined"!=typeof window&&window.msCrypto?window.msCrypto:i)&&"undefined"!=typeof global&&global.crypto?global.crypto:i)&&"function"==typeof require)try{i=require("crypto")}catch(t){}var r=Object.create||function(t){return e.prototype=t,t=new e,e.prototype=null,t};function e(){}var t={},o=t.lib={},n=o.Base={extend:function(t){var e=r(this);return t&&e.mixIn(t),e.hasOwnProperty("init")&&this.init!==e.init||(e.init=function(){e.$super.init.apply(this,arguments)}),(e.init.prototype=e).$super=this,e},create:function(){var t=this.extend();return t.init.apply(t,arguments),t},init:function(){},mixIn:function(t){for(var e in t)t.hasOwnProperty(e)&&(this[e]=t[e]);t.hasOwnProperty("toString")&&(this.toString=t.toString)},clone:function(){return this.init.prototype.extend(this)}},l=o.WordArray=n.extend({init:function(t,e){t=this.words=t||[],this.sigBytes=null!=e?e:4*t.length},toString:function(t){return(t||c).stringify(this)},concat:function(t){var e=this.words,r=t.words,i=this.sigBytes,o=t.sigBytes;if(this.clamp(),i%4)for(var n=0;n>>2]>>>24-n%4*8&255;e[i+n>>>2]|=s<<24-(i+n)%4*8}else for(var c=0;c>>2]=r[c>>>2];return this.sigBytes+=o,this},clamp:function(){var t=this.words,e=this.sigBytes;t[e>>>2]&=4294967295<<32-e%4*8,t.length=h.ceil(e/4)},clone:function(){var t=n.clone.call(this);return t.words=this.words.slice(0),t},random:function(t){for(var e=[],r=0;r>>2]>>>24-o%4*8&255;i.push((n>>>4).toString(16)),i.push((15&n).toString(16))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>3]|=parseInt(t.substr(i,2),16)<<24-i%8*4;return new l.init(r,e/2)}},a=s.Latin1={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],o=0;o>>2]>>>24-o%4*8&255;i.push(String.fromCharCode(n))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>2]|=(255&t.charCodeAt(i))<<24-i%4*8;return new l.init(r,e)}},f=s.Utf8={stringify:function(t){try{return decodeURIComponent(escape(a.stringify(t)))}catch(t){throw new Error("Malformed UTF-8 data")}},parse:function(t){return a.parse(unescape(encodeURIComponent(t)))}},d=o.BufferedBlockAlgorithm=n.extend({reset:function(){this._data=new l.init,this._nDataBytes=0},_append:function(t){"string"==typeof t&&(t=f.parse(t)),this._data.concat(t),this._nDataBytes+=t.sigBytes},_process:function(t){var e,r=this._data,i=r.words,o=r.sigBytes,n=this.blockSize,s=o/(4*n),c=(s=t?h.ceil(s):h.max((0|s)-this._minBufferSize,0))*n,t=h.min(4*c,o);if(c){for(var a=0;a>>2]|=t[i]<<24-i%4*8;I.call(this,r,e)}else I.apply(this,arguments)}).prototype=p),i),p1=u.lib.WordArray;function _1(t){return t<<8&4278255360|t>>>8&16711935}(u=u.enc).Utf16=u.Utf16BE={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],o=0;o>>2]>>>16-o%4*8&65535;i.push(String.fromCharCode(n))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>1]|=t.charCodeAt(i)<<16-i%2*16;return p1.create(r,2*e)}},u.Utf16LE={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=[],o=0;o>>2]>>>16-o%4*8&65535);i.push(String.fromCharCode(n))}return i.join("")},parse:function(t){for(var e=t.length,r=[],i=0;i>>1]|=_1(t.charCodeAt(i)<<16-i%2*16);return p1.create(r,2*e)}},U=(p=i).lib.WordArray,p.enc.Base64={stringify:function(t){for(var e=t.words,r=t.sigBytes,i=this._map,o=(t.clamp(),[]),n=0;n>>2]>>>24-n%4*8&255)<<16|(e[n+1>>>2]>>>24-(n+1)%4*8&255)<<8|e[n+2>>>2]>>>24-(n+2)%4*8&255,c=0;c<4&&n+.75*c>>6*(3-c)&63));var a=i.charAt(64);if(a)for(;o.length%4;)o.push(a);return o.join("")},parse:function(t){var e=t.length,r=this._map;if(!(i=this._reverseMap))for(var i=this._reverseMap=[],o=0;o>>6-u%4*2,s=s|n,f[d>>>2]|=s<<24-d%4*8,d++);return U.create(f,d)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="},K=(u=i).lib.WordArray,u.enc.Base64url={stringify:function(t,e){for(var r=t.words,i=t.sigBytes,o=(e=void 0===e?!0:e)?this._safe_map:this._map,n=(t.clamp(),[]),s=0;s>>2]>>>24-s%4*8&255)<<16|(r[s+1>>>2]>>>24-(s+1)%4*8&255)<<8|r[s+2>>>2]>>>24-(s+2)%4*8&255,a=0;a<4&&s+.75*a>>6*(3-a)&63));var h=o.charAt(64);if(h)for(;n.length%4;)n.push(h);return n.join("")},parse:function(t,e){var r=t.length,i=(e=void 0===e?!0:e)?this._safe_map:this._map;if(!(o=this._reverseMap))for(var o=this._reverseMap=[],n=0;n>>6-u%4*2,c=c|s,f[d>>>2]|=c<<24-d%4*8,d++);return K.create(f,d)},_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",_safe_map:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"};for(var y1=Math,p=i,g1=(u=p.lib).WordArray,v1=u.Hasher,u=p.algo,A=[],B1=0;B1<64;B1++)A[B1]=4294967296*y1.abs(y1.sin(B1+1))|0;function z(t,e,r,i,o,n,s){t=t+(e&r|~e&i)+o+s;return(t<>>32-n)+e}function H(t,e,r,i,o,n,s){t=t+(e&i|r&~i)+o+s;return(t<>>32-n)+e}function C(t,e,r,i,o,n,s){t=t+(e^r^i)+o+s;return(t<>>32-n)+e}function R(t,e,r,i,o,n,s){t=t+(r^(e|~i))+o+s;return(t<>>32-n)+e}u=u.MD5=v1.extend({_doReset:function(){this._hash=new g1.init([1732584193,4023233417,2562383102,271733878])},_doProcessBlock:function(t,e){for(var r=0;r<16;r++){var i=e+r,o=t[i];t[i]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8)}var n=this._hash.words,s=t[e+0],c=t[e+1],a=t[e+2],h=t[e+3],l=t[e+4],f=t[e+5],d=t[e+6],u=t[e+7],p=t[e+8],_=t[e+9],y=t[e+10],g=t[e+11],v=t[e+12],B=t[e+13],w=t[e+14],k=t[e+15],x=z(n[0],S=n[1],m=n[2],b=n[3],s,7,A[0]),b=z(b,x,S,m,c,12,A[1]),m=z(m,b,x,S,a,17,A[2]),S=z(S,m,b,x,h,22,A[3]);x=z(x,S,m,b,l,7,A[4]),b=z(b,x,S,m,f,12,A[5]),m=z(m,b,x,S,d,17,A[6]),S=z(S,m,b,x,u,22,A[7]),x=z(x,S,m,b,p,7,A[8]),b=z(b,x,S,m,_,12,A[9]),m=z(m,b,x,S,y,17,A[10]),S=z(S,m,b,x,g,22,A[11]),x=z(x,S,m,b,v,7,A[12]),b=z(b,x,S,m,B,12,A[13]),m=z(m,b,x,S,w,17,A[14]),x=H(x,S=z(S,m,b,x,k,22,A[15]),m,b,c,5,A[16]),b=H(b,x,S,m,d,9,A[17]),m=H(m,b,x,S,g,14,A[18]),S=H(S,m,b,x,s,20,A[19]),x=H(x,S,m,b,f,5,A[20]),b=H(b,x,S,m,y,9,A[21]),m=H(m,b,x,S,k,14,A[22]),S=H(S,m,b,x,l,20,A[23]),x=H(x,S,m,b,_,5,A[24]),b=H(b,x,S,m,w,9,A[25]),m=H(m,b,x,S,h,14,A[26]),S=H(S,m,b,x,p,20,A[27]),x=H(x,S,m,b,B,5,A[28]),b=H(b,x,S,m,a,9,A[29]),m=H(m,b,x,S,u,14,A[30]),x=C(x,S=H(S,m,b,x,v,20,A[31]),m,b,f,4,A[32]),b=C(b,x,S,m,p,11,A[33]),m=C(m,b,x,S,g,16,A[34]),S=C(S,m,b,x,w,23,A[35]),x=C(x,S,m,b,c,4,A[36]),b=C(b,x,S,m,l,11,A[37]),m=C(m,b,x,S,u,16,A[38]),S=C(S,m,b,x,y,23,A[39]),x=C(x,S,m,b,B,4,A[40]),b=C(b,x,S,m,s,11,A[41]),m=C(m,b,x,S,h,16,A[42]),S=C(S,m,b,x,d,23,A[43]),x=C(x,S,m,b,_,4,A[44]),b=C(b,x,S,m,v,11,A[45]),m=C(m,b,x,S,k,16,A[46]),x=R(x,S=C(S,m,b,x,a,23,A[47]),m,b,s,6,A[48]),b=R(b,x,S,m,u,10,A[49]),m=R(m,b,x,S,w,15,A[50]),S=R(S,m,b,x,f,21,A[51]),x=R(x,S,m,b,v,6,A[52]),b=R(b,x,S,m,h,10,A[53]),m=R(m,b,x,S,y,15,A[54]),S=R(S,m,b,x,c,21,A[55]),x=R(x,S,m,b,p,6,A[56]),b=R(b,x,S,m,k,10,A[57]),m=R(m,b,x,S,d,15,A[58]),S=R(S,m,b,x,B,21,A[59]),x=R(x,S,m,b,l,6,A[60]),b=R(b,x,S,m,g,10,A[61]),m=R(m,b,x,S,a,15,A[62]),S=R(S,m,b,x,_,21,A[63]),n[0]=n[0]+x|0,n[1]=n[1]+S|0,n[2]=n[2]+m|0,n[3]=n[3]+b|0},_doFinalize:function(){for(var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes,o=(e[i>>>5]|=128<<24-i%32,y1.floor(r/4294967296)),o=(e[15+(64+i>>>9<<4)]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8),e[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(e.length+1),this._process(),this._hash),n=o.words,s=0;s<4;s++){var c=n[s];n[s]=16711935&(c<<8|c>>>24)|4278255360&(c<<24|c>>>8)}return o},clone:function(){var t=v1.clone.call(this);return t._hash=this._hash.clone(),t}}),p.MD5=v1._createHelper(u),p.HmacMD5=v1._createHmacHelper(u),u=(p=i).lib,X=u.WordArray,L=u.Hasher,u=p.algo,l=[],u=u.SHA1=L.extend({_doReset:function(){this._hash=new X.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],o=r[1],n=r[2],s=r[3],c=r[4],a=0;a<80;a++){a<16?l[a]=0|t[e+a]:(h=l[a-3]^l[a-8]^l[a-14]^l[a-16],l[a]=h<<1|h>>>31);var h=(i<<5|i>>>27)+c+l[a];h+=a<20?1518500249+(o&n|~o&s):a<40?1859775393+(o^n^s):a<60?(o&n|o&s|n&s)-1894007588:(o^n^s)-899497514,c=s,s=n,n=o<<30|o>>>2,o=i,i=h}r[0]=r[0]+i|0,r[1]=r[1]+o|0,r[2]=r[2]+n|0,r[3]=r[3]+s|0,r[4]=r[4]+c|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=Math.floor(r/4294967296),e[15+(64+i>>>9<<4)]=r,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=L.clone.call(this);return t._hash=this._hash.clone(),t}}),p.SHA1=L._createHelper(u),p.HmacSHA1=L._createHmacHelper(u);var w1=Math,p=i,k1=(u=p.lib).WordArray,x1=u.Hasher,u=p.algo,b1=[],m1=[];function S1(t){return 4294967296*(t-(0|t))|0}for(var A1=2,z1=0;z1<64;)!function(t){for(var e=w1.sqrt(t),r=2;r<=e;r++)if(!(t%r))return;return 1}(A1)||(z1<8&&(b1[z1]=S1(w1.pow(A1,.5))),m1[z1]=S1(w1.pow(A1,1/3)),z1++),A1++;var _=[],u=u.SHA256=x1.extend({_doReset:function(){this._hash=new k1.init(b1.slice(0))},_doProcessBlock:function(t,e){for(var r=this._hash.words,i=r[0],o=r[1],n=r[2],s=r[3],c=r[4],a=r[5],h=r[6],l=r[7],f=0;f<64;f++){f<16?_[f]=0|t[e+f]:(d=_[f-15],u=_[f-2],_[f]=((d<<25|d>>>7)^(d<<14|d>>>18)^d>>>3)+_[f-7]+((u<<15|u>>>17)^(u<<13|u>>>19)^u>>>10)+_[f-16]);var d=i&o^i&n^o&n,u=l+((c<<26|c>>>6)^(c<<21|c>>>11)^(c<<7|c>>>25))+(c&a^~c&h)+m1[f]+_[f],l=h,h=a,a=c,c=s+u|0,s=n,n=o,o=i,i=u+(((i<<30|i>>>2)^(i<<19|i>>>13)^(i<<10|i>>>22))+d)|0}r[0]=r[0]+i|0,r[1]=r[1]+o|0,r[2]=r[2]+n|0,r[3]=r[3]+s|0,r[4]=r[4]+c|0,r[5]=r[5]+a|0,r[6]=r[6]+h|0,r[7]=r[7]+l|0},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=w1.floor(r/4294967296),e[15+(64+i>>>9<<4)]=r,t.sigBytes=4*e.length,this._process(),this._hash},clone:function(){var t=x1.clone.call(this);return t._hash=this._hash.clone(),t}}),p=(p.SHA256=x1._createHelper(u),p.HmacSHA256=x1._createHmacHelper(u),j=(p=i).lib.WordArray,u=p.algo,T=u.SHA256,u=u.SHA224=T.extend({_doReset:function(){this._hash=new j.init([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428])},_doFinalize:function(){var t=T._doFinalize.call(this);return t.sigBytes-=4,t}}),p.SHA224=T._createHelper(u),p.HmacSHA224=T._createHmacHelper(u),i),H1=p.lib.Hasher,y=(u=p.x64).Word,C1=u.WordArray,u=p.algo;function g(){return y.create.apply(y,arguments)}for(var R1=[g(1116352408,3609767458),g(1899447441,602891725),g(3049323471,3964484399),g(3921009573,2173295548),g(961987163,4081628472),g(1508970993,3053834265),g(2453635748,2937671579),g(2870763221,3664609560),g(3624381080,2734883394),g(310598401,1164996542),g(607225278,1323610764),g(1426881987,3590304994),g(1925078388,4068182383),g(2162078206,991336113),g(2614888103,633803317),g(3248222580,3479774868),g(3835390401,2666613458),g(4022224774,944711139),g(264347078,2341262773),g(604807628,2007800933),g(770255983,1495990901),g(1249150122,1856431235),g(1555081692,3175218132),g(1996064986,2198950837),g(2554220882,3999719339),g(2821834349,766784016),g(2952996808,2566594879),g(3210313671,3203337956),g(3336571891,1034457026),g(3584528711,2466948901),g(113926993,3758326383),g(338241895,168717936),g(666307205,1188179964),g(773529912,1546045734),g(1294757372,1522805485),g(1396182291,2643833823),g(1695183700,2343527390),g(1986661051,1014477480),g(2177026350,1206759142),g(2456956037,344077627),g(2730485921,1290863460),g(2820302411,3158454273),g(3259730800,3505952657),g(3345764771,106217008),g(3516065817,3606008344),g(3600352804,1432725776),g(4094571909,1467031594),g(275423344,851169720),g(430227734,3100823752),g(506948616,1363258195),g(659060556,3750685593),g(883997877,3785050280),g(958139571,3318307427),g(1322822218,3812723403),g(1537002063,2003034995),g(1747873779,3602036899),g(1955562222,1575990012),g(2024104815,1125592928),g(2227730452,2716904306),g(2361852424,442776044),g(2428436474,593698344),g(2756734187,3733110249),g(3204031479,2999351573),g(3329325298,3815920427),g(3391569614,3928383900),g(3515267271,566280711),g(3940187606,3454069534),g(4118630271,4000239992),g(116418474,1914138554),g(174292421,2731055270),g(289380356,3203993006),g(460393269,320620315),g(685471733,587496836),g(852142971,1086792851),g(1017036298,365543100),g(1126000580,2618297676),g(1288033470,3409855158),g(1501505948,4234509866),g(1607167915,987167468),g(1816402316,1246189591)],D1=[],E1=0;E1<80;E1++)D1[E1]=g();u=u.SHA512=H1.extend({_doReset:function(){this._hash=new C1.init([new y.init(1779033703,4089235720),new y.init(3144134277,2227873595),new y.init(1013904242,4271175723),new y.init(2773480762,1595750129),new y.init(1359893119,2917565137),new y.init(2600822924,725511199),new y.init(528734635,4215389547),new y.init(1541459225,327033209)])},_doProcessBlock:function(W,O){for(var t=this._hash.words,e=t[0],r=t[1],i=t[2],o=t[3],n=t[4],s=t[5],c=t[6],t=t[7],I=e.high,a=e.low,U=r.high,h=r.low,K=i.high,l=i.low,X=o.high,f=o.low,L=n.high,d=n.low,j=s.high,u=s.low,T=c.high,p=c.low,N=t.high,_=t.low,y=I,g=a,v=U,B=h,w=K,k=l,q=X,x=f,b=L,m=d,Z=j,S=u,V=T,G=p,J=N,Q=_,A=0;A<80;A++)var z,H,C=D1[A],R=(A<16?(H=C.high=0|W[O+2*A],z=C.low=0|W[O+2*A+1]):(F=(P=D1[A-15]).high,P=P.low,M=(E=D1[A-2]).high,E=E.low,D=(R=D1[A-7]).high,R=R.low,$=(Y=D1[A-16]).high,H=(H=((F>>>1|P<<31)^(F>>>8|P<<24)^F>>>7)+D+((z=(D=(P>>>1|F<<31)^(P>>>8|F<<24)^(P>>>7|F<<25))+R)>>>0>>0?1:0))+((M>>>19|E<<13)^(M<<3|E>>>29)^M>>>6)+((z+=P=(E>>>19|M<<13)^(E<<3|M>>>29)^(E>>>6|M<<26))>>>0

>>0?1:0),z+=F=Y.low,C.high=H=H+$+(z>>>0>>0?1:0),C.low=z),b&Z^~b&V),D=m&S^~m&G,E=y&v^y&w^v&w,M=(g>>>28|y<<4)^(g<<30|y>>>2)^(g<<25|y>>>7),P=R1[A],Y=P.high,$=P.low,F=Q+((m>>>14|b<<18)^(m>>>18|b<<14)^(m<<23|b>>>9)),C=J+((b>>>14|m<<18)^(b>>>18|m<<14)^(b<<23|m>>>9))+(F>>>0>>0?1:0),t1=M+(g&B^g&k^B&k),J=V,Q=G,V=Z,G=S,Z=b,S=m,b=q+(C=C+R+((F=F+D)>>>0>>0?1:0)+Y+((F=F+$)>>>0<$>>>0?1:0)+H+((F=F+z)>>>0>>0?1:0))+((m=x+F|0)>>>0>>0?1:0)|0,q=w,x=k,w=v,k=B,v=y,B=g,y=C+(((y>>>28|g<<4)^(y<<30|g>>>2)^(y<<25|g>>>7))+E+(t1>>>0>>0?1:0))+((g=F+t1|0)>>>0>>0?1:0)|0;a=e.low=a+g,e.high=I+y+(a>>>0>>0?1:0),h=r.low=h+B,r.high=U+v+(h>>>0>>0?1:0),l=i.low=l+k,i.high=K+w+(l>>>0>>0?1:0),f=o.low=f+x,o.high=X+q+(f>>>0>>0?1:0),d=n.low=d+m,n.high=L+b+(d>>>0>>0?1:0),u=s.low=u+S,s.high=j+Z+(u>>>0>>0?1:0),p=c.low=p+G,c.high=T+V+(p>>>0>>0?1:0),_=t.low=_+Q,t.high=N+J+(_>>>0>>0?1:0)},_doFinalize:function(){var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return e[i>>>5]|=128<<24-i%32,e[30+(128+i>>>10<<5)]=Math.floor(r/4294967296),e[31+(128+i>>>10<<5)]=r,t.sigBytes=4*e.length,this._process(),this._hash.toX32()},clone:function(){var t=H1.clone.call(this);return t._hash=this._hash.clone(),t},blockSize:32}),p.SHA512=H1._createHelper(u),p.HmacSHA512=H1._createHmacHelper(u),u=(p=i).x64,t=u.Word,N=u.WordArray,u=p.algo,q=u.SHA512,u=u.SHA384=q.extend({_doReset:function(){this._hash=new N.init([new t.init(3418070365,3238371032),new t.init(1654270250,914150663),new t.init(2438529370,812702999),new t.init(355462360,4144912697),new t.init(1731405415,4290775857),new t.init(2394180231,1750603025),new t.init(3675008525,1694076839),new t.init(1203062813,3204075428)])},_doFinalize:function(){var t=q._doFinalize.call(this);return t.sigBytes-=16,t}}),p.SHA384=q._createHelper(u),p.HmacSHA384=q._createHmacHelper(u);for(var M1=Math,p=i,P1=(u=p.lib).WordArray,F1=u.Hasher,W1=p.x64.Word,u=p.algo,O1=[],I1=[],U1=[],v=1,B=0,K1=0;K1<24;K1++){O1[v+5*B]=(K1+1)*(K1+2)/2%64;var X1=(2*v+3*B)%5;v=B%5,B=X1}for(v=0;v<5;v++)for(B=0;B<5;B++)I1[v+5*B]=B+(2*v+3*B)%5*5;for(var L1=1,j1=0;j1<24;j1++){for(var T1,N1=0,q1=0,Z1=0;Z1<7;Z1++)1&L1&&((T1=(1<>>32-e}function Y1(t){return"string"==typeof t?f1:o}function $1(t,e,r){var i,o=this._iv;o?(i=o,this._iv=void 0):i=this._prevBlock;for(var n=0;n>24&255)?(r=t>>8&255,i=255&t,255===(e=t>>16&255)?(e=0,255===r?(r=0,255===i?i=0:++i):++r):++e,t=0,t=(t+=e<<16)+(r<<8)+i):t+=1<<24,t}u=u.SHA3=F1.extend({cfg:F1.cfg.extend({outputLength:512}),_doReset:function(){for(var t=this._state=[],e=0;e<25;e++)t[e]=new W1.init;this.blockSize=(1600-2*this.cfg.outputLength)/32},_doProcessBlock:function(t,e){for(var r=this._state,i=this.blockSize/2,o=0;o>>24)|4278255360&(n<<24|n>>>8);(x=r[o]).high^=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),x.low^=n}for(var c=0;c<24;c++){for(var a=0;a<5;a++){for(var h=0,l=0,f=0;f<5;f++)h^=(x=r[a+5*f]).high,l^=x.low;var d=D[a];d.high=h,d.low=l}for(a=0;a<5;a++)for(var u=D[(a+4)%5],p=D[(a+1)%5],_=p.high,p=p.low,h=u.high^(_<<1|p>>>31),l=u.low^(p<<1|_>>>31),f=0;f<5;f++)(x=r[a+5*f]).high^=h,x.low^=l;for(var y=1;y<25;y++){var g=(x=r[y]).high,v=x.low,B=O1[y],g=(l=B<32?(h=g<>>32-B,v<>>32-B):(h=v<>>64-B,g<>>64-B),D[I1[y]]);g.high=h,g.low=l}var w=D[0],k=r[0];w.high=k.high,w.low=k.low;for(a=0;a<5;a++)for(f=0;f<5;f++){var x=r[y=a+5*f],b=D[y],m=D[(a+1)%5+5*f],S=D[(a+2)%5+5*f];x.high=b.high^~m.high&S.high,x.low=b.low^~m.low&S.low}x=r[0],w=U1[c];x.high^=w.high,x.low^=w.low}},_doFinalize:function(){for(var t=this._data,e=t.words,r=(this._nDataBytes,8*t.sigBytes),i=32*this.blockSize,o=(e[r>>>5]|=1<<24-r%32,e[(M1.ceil((1+r)/i)*i>>>5)-1]|=128,t.sigBytes=4*e.length,this._process(),this._state),r=this.cfg.outputLength/8,n=r/8,s=[],c=0;c>>24)|4278255360&(h<<24|h>>>8);s.push(16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8)),s.push(h)}return new P1.init(s,r)},clone:function(){for(var t=F1.clone.call(this),e=t._state=this._state.slice(0),r=0;r<25;r++)e[r]=e[r].clone();return t}}),p.SHA3=F1._createHelper(u),p.HmacSHA3=F1._createHmacHelper(u),Math,u=(p=i).lib,e=u.WordArray,Z=u.Hasher,u=p.algo,V=e.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),G=e.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),J=e.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),Q=e.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),Y=e.create([0,1518500249,1859775393,2400959708,2840853838]),$=e.create([1352829926,1548603684,1836072691,2053994217,0]),u=u.RIPEMD160=Z.extend({_doReset:function(){this._hash=e.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(t,e){for(var r=0;r<16;r++){var i=e+r,o=t[i];t[i]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8)}for(var n,s,c,a,h,l,f=this._hash.words,d=Y.words,u=$.words,p=V.words,_=G.words,y=J.words,g=Q.words,v=n=f[0],B=s=f[1],w=c=f[2],k=a=f[3],x=h=f[4],r=0;r<80;r+=1)l=(l=Q1(l=(l=n+t[e+p[r]]|0)+(r<16?(s^c^a)+d[0]:r<32?G1(s,c,a)+d[1]:r<48?((s|~c)^a)+d[2]:r<64?J1(s,c,a)+d[3]:(s^(c|~a))+d[4])|0,y[r]))+h|0,n=h,h=a,a=Q1(c,10),c=s,s=l,l=(l=Q1(l=(l=v+t[e+_[r]]|0)+(r<16?(B^(w|~k))+u[0]:r<32?J1(B,w,k)+u[1]:r<48?((B|~w)^k)+u[2]:r<64?G1(B,w,k)+u[3]:(B^w^k)+u[4])|0,g[r]))+x|0,v=x,x=k,k=Q1(w,10),w=B,B=l;l=f[1]+c+k|0,f[1]=f[2]+a+x|0,f[2]=f[3]+h+v|0,f[3]=f[4]+n+B|0,f[4]=f[0]+s+w|0,f[0]=l},_doFinalize:function(){for(var t=this._data,e=t.words,r=8*this._nDataBytes,i=8*t.sigBytes,i=(e[i>>>5]|=128<<24-i%32,e[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(e.length+1),this._process(),this._hash),o=i.words,n=0;n<5;n++){var s=o[n];o[n]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8)}return i},clone:function(){var t=Z.clone.call(this);return t._hash=this._hash.clone(),t}}),p.RIPEMD160=Z._createHelper(u),p.HmacRIPEMD160=Z._createHmacHelper(u),u=(p=i).lib.Base,t1=p.enc.Utf8,p.algo.HMAC=u.extend({init:function(t,e){t=this._hasher=new t.init,"string"==typeof e&&(e=t1.parse(e));for(var r=t.blockSize,i=4*r,t=((e=e.sigBytes>i?t.finalize(e):e).clamp(),this._oKey=e.clone()),e=this._iKey=e.clone(),o=t.words,n=e.words,s=0;s>>2];t.sigBytes-=e}},P.BlockCipher=h1.extend({cfg:h1.cfg.extend({mode:r,padding:u}),reset:function(){h1.reset.call(this);var t,e=this.cfg,r=e.iv,e=e.mode;this._xformMode==this._ENC_XFORM_MODE?t=e.createEncryptor:(t=e.createDecryptor,this._minBufferSize=1),this._mode&&this._mode.__creator==t?this._mode.init(this,r&&r.words):(this._mode=t.call(e,this,r&&r.words),this._mode.__creator=t)},_doProcessBlock:function(t,e){this._mode.processBlock(t,e)},_doFinalize:function(){var t,e=this.cfg.padding;return this._xformMode==this._ENC_XFORM_MODE?(e.pad(this._data,this.blockSize),t=this._process(!0)):(t=this._process(!0),e.unpad(t)),t},blockSize:4}),l1=P.CipherParams=p.extend({init:function(t){this.mixIn(t)},toString:function(t){return(t||this.formatter).stringify(this)}}),r=(w.format={}).OpenSSL={stringify:function(t){var e=t.ciphertext,t=t.salt,t=t?s.create([1398893684,1701076831]).concat(t).concat(e):e;return t.toString(c1)},parse:function(t){var e,t=c1.parse(t),r=t.words;return 1398893684==r[0]&&1701076831==r[1]&&(e=s.create(r.slice(2,4)),r.splice(0,4),t.sigBytes-=16),l1.create({ciphertext:t,salt:e})}},o=P.SerializableCipher=p.extend({cfg:p.extend({format:r}),encrypt:function(t,e,r,i){i=this.cfg.extend(i);var o=t.createEncryptor(r,i),e=o.finalize(e),o=o.cfg;return l1.create({ciphertext:e,key:r,iv:o.iv,algorithm:t,mode:o.mode,padding:o.padding,blockSize:t.blockSize,formatter:i.format})},decrypt:function(t,e,r,i){return i=this.cfg.extend(i),e=this._parse(e,i.format),t.createDecryptor(r,i).finalize(e.ciphertext)},_parse:function(t,e){return"string"==typeof t?e.parse(t,this):t}}),u=(w.kdf={}).OpenSSL={execute:function(t,e,r,i,o){i=i||s.random(8),o=(o?a1.create({keySize:e+r,hasher:o}):a1.create({keySize:e+r})).compute(t,i);t=s.create(o.words.slice(e),4*r);return o.sigBytes=4*e,l1.create({key:o,iv:t,salt:i})}},f1=P.PasswordBasedCipher=o.extend({cfg:o.cfg.extend({kdf:u}),encrypt:function(t,e,r,i){r=(i=this.cfg.extend(i)).kdf.execute(r,t.keySize,t.ivSize,i.salt,i.hasher),i.iv=r.iv,t=o.encrypt.call(this,t,e,r.key,i);return t.mixIn(r),t},decrypt:function(t,e,r,i){i=this.cfg.extend(i),e=this._parse(e,i.format);r=i.kdf.execute(r,t.keySize,t.ivSize,e.salt,i.hasher);return i.iv=r.iv,o.decrypt.call(this,t,e,r.key,i)}})),i.mode.CFB=((p=i.lib.BlockCipherMode.extend()).Encryptor=p.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize;t2.call(this,t,e,i,r),this._prevBlock=t.slice(e,e+i)}}),p.Decryptor=p.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,o=t.slice(e,e+i);t2.call(this,t,e,i,r),this._prevBlock=o}}),p),i.mode.CTR=(r=i.lib.BlockCipherMode.extend(),w=r.Encryptor=r.extend({processBlock:function(t,e){var r=this._cipher,i=r.blockSize,o=this._iv,n=this._counter,s=(o&&(n=this._counter=o.slice(0),this._iv=void 0),n.slice(0));r.encryptBlock(s,0),n[i-1]=n[i-1]+1|0;for(var c=0;c>>2]|=e<<24-r%4*8,t.sigBytes+=e},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},i.pad.Iso10126={pad:function(t,e){e*=4,e-=t.sigBytes%e;t.concat(i.lib.WordArray.random(e-1)).concat(i.lib.WordArray.create([e<<24],1))},unpad:function(t){var e=255&t.words[t.sigBytes-1>>>2];t.sigBytes-=e}},i.pad.Iso97971={pad:function(t,e){t.concat(i.lib.WordArray.create([2147483648],1)),i.pad.ZeroPadding.pad(t,e)},unpad:function(t){i.pad.ZeroPadding.unpad(t),t.sigBytes--}},i.pad.ZeroPadding={pad:function(t,e){e*=4;t.clamp(),t.sigBytes+=e-(t.sigBytes%e||e)},unpad:function(t){for(var e=t.words,r=t.sigBytes-1,r=t.sigBytes-1;0<=r;r--)if(e[r>>>2]>>>24-r%4*8&255){t.sigBytes=r+1;break}}},i.pad.NoPadding={pad:function(){},unpad:function(){}},d1=(P=i).lib.CipherParams,u1=P.enc.Hex,P.format.Hex={stringify:function(t){return t.ciphertext.toString(u1)},parse:function(t){t=u1.parse(t);return d1.create({ciphertext:t})}};for(var w=i,p=w.lib.BlockCipher,u=w.algo,k=[],r2=[],i2=[],o2=[],n2=[],s2=[],c2=[],a2=[],h2=[],l2=[],x=[],b=0;b<256;b++)x[b]=b<128?b<<1:b<<1^283;for(var m=0,S=0,b=0;b<256;b++){var E=S^S<<1^S<<2^S<<3^S<<4,f2=(k[m]=E=E>>>8^255&E^99,x[r2[E]=m]),d2=x[f2],u2=x[d2],M=257*x[E]^16843008*E;i2[m]=M<<24|M>>>8,o2[m]=M<<16|M>>>16,n2[m]=M<<8|M>>>24,s2[m]=M,c2[E]=(M=16843009*u2^65537*d2^257*f2^16843008*m)<<24|M>>>8,a2[E]=M<<16|M>>>16,h2[E]=M<<8|M>>>24,l2[E]=M,m?(m=f2^x[x[x[u2^f2]]],S^=x[x[S]]):m=S=1}var p2=[0,1,2,4,8,16,32,64,128,27,54],u=u.AES=p.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var t=this._keyPriorReset=this._key,e=t.words,r=t.sigBytes/4,i=4*(1+(this._nRounds=6+r)),o=this._keySchedule=[],n=0;n>>24]<<24|k[a>>>16&255]<<16|k[a>>>8&255]<<8|k[255&a]):(a=k[(a=a<<8|a>>>24)>>>24]<<24|k[a>>>16&255]<<16|k[a>>>8&255]<<8|k[255&a],a^=p2[n/r|0]<<24),o[n]=o[n-r]^a);for(var s=this._invKeySchedule=[],c=0;c>>24]]^a2[k[a>>>16&255]]^h2[k[a>>>8&255]]^l2[k[255&a]]}}},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._keySchedule,i2,o2,n2,s2,k)},decryptBlock:function(t,e){var r=t[e+1],r=(t[e+1]=t[e+3],t[e+3]=r,this._doCryptBlock(t,e,this._invKeySchedule,c2,a2,h2,l2,r2),t[e+1]);t[e+1]=t[e+3],t[e+3]=r},_doCryptBlock:function(t,e,r,i,o,n,s,c){for(var a=this._nRounds,h=t[e]^r[0],l=t[e+1]^r[1],f=t[e+2]^r[2],d=t[e+3]^r[3],u=4,p=1;p>>24]^o[l>>>16&255]^n[f>>>8&255]^s[255&d]^r[u++],y=i[l>>>24]^o[f>>>16&255]^n[d>>>8&255]^s[255&h]^r[u++],g=i[f>>>24]^o[d>>>16&255]^n[h>>>8&255]^s[255&l]^r[u++],v=i[d>>>24]^o[h>>>16&255]^n[l>>>8&255]^s[255&f]^r[u++],h=_,l=y,f=g,d=v;_=(c[h>>>24]<<24|c[l>>>16&255]<<16|c[f>>>8&255]<<8|c[255&d])^r[u++],y=(c[l>>>24]<<24|c[f>>>16&255]<<16|c[d>>>8&255]<<8|c[255&h])^r[u++],g=(c[f>>>24]<<24|c[d>>>16&255]<<16|c[h>>>8&255]<<8|c[255&l])^r[u++],v=(c[d>>>24]<<24|c[h>>>16&255]<<16|c[l>>>8&255]<<8|c[255&f])^r[u++];t[e]=_,t[e+1]=y,t[e+2]=g,t[e+3]=v},keySize:8}),P=(w.AES=p._createHelper(u),i),_2=(w=P.lib).WordArray,w=w.BlockCipher,p=P.algo,y2=[57,49,41,33,25,17,9,1,58,50,42,34,26,18,10,2,59,51,43,35,27,19,11,3,60,52,44,36,63,55,47,39,31,23,15,7,62,54,46,38,30,22,14,6,61,53,45,37,29,21,13,5,28,20,12,4],g2=[14,17,11,24,1,5,3,28,15,6,21,10,23,19,12,4,26,8,16,7,27,20,13,2,41,52,31,37,47,55,30,40,51,45,33,48,44,49,39,56,34,53,46,42,50,36,29,32],v2=[1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28],B2=[{0:8421888,268435456:32768,536870912:8421378,805306368:2,1073741824:512,1342177280:8421890,1610612736:8389122,1879048192:8388608,2147483648:514,2415919104:8389120,2684354560:33280,2952790016:8421376,3221225472:32770,3489660928:8388610,3758096384:0,4026531840:33282,134217728:0,402653184:8421890,671088640:33282,939524096:32768,1207959552:8421888,1476395008:512,1744830464:8421378,2013265920:2,2281701376:8389120,2550136832:33280,2818572288:8421376,3087007744:8389122,3355443200:8388610,3623878656:32770,3892314112:514,4160749568:8388608,1:32768,268435457:2,536870913:8421888,805306369:8388608,1073741825:8421378,1342177281:33280,1610612737:512,1879048193:8389122,2147483649:8421890,2415919105:8421376,2684354561:8388610,2952790017:33282,3221225473:514,3489660929:8389120,3758096385:32770,4026531841:0,134217729:8421890,402653185:8421376,671088641:8388608,939524097:512,1207959553:32768,1476395009:8388610,1744830465:2,2013265921:33282,2281701377:32770,2550136833:8389122,2818572289:514,3087007745:8421888,3355443201:8389120,3623878657:0,3892314113:33280,4160749569:8421378},{0:1074282512,16777216:16384,33554432:524288,50331648:1074266128,67108864:1073741840,83886080:1074282496,100663296:1073758208,117440512:16,134217728:540672,150994944:1073758224,167772160:1073741824,184549376:540688,201326592:524304,218103808:0,234881024:16400,251658240:1074266112,8388608:1073758208,25165824:540688,41943040:16,58720256:1073758224,75497472:1074282512,92274688:1073741824,109051904:524288,125829120:1074266128,142606336:524304,159383552:0,176160768:16384,192937984:1074266112,209715200:1073741840,226492416:540672,243269632:1074282496,260046848:16400,268435456:0,285212672:1074266128,301989888:1073758224,318767104:1074282496,335544320:1074266112,352321536:16,369098752:540688,385875968:16384,402653184:16400,419430400:524288,436207616:524304,452984832:1073741840,469762048:540672,486539264:1073758208,503316480:1073741824,520093696:1074282512,276824064:540688,293601280:524288,310378496:1074266112,327155712:16384,343932928:1073758208,360710144:1074282512,377487360:16,394264576:1073741824,411041792:1074282496,427819008:1073741840,444596224:1073758224,461373440:524304,478150656:0,494927872:16400,511705088:1074266128,528482304:540672},{0:260,1048576:0,2097152:67109120,3145728:65796,4194304:65540,5242880:67108868,6291456:67174660,7340032:67174400,8388608:67108864,9437184:67174656,10485760:65792,11534336:67174404,12582912:67109124,13631488:65536,14680064:4,15728640:256,524288:67174656,1572864:67174404,2621440:0,3670016:67109120,4718592:67108868,5767168:65536,6815744:65540,7864320:260,8912896:4,9961472:256,11010048:67174400,12058624:65796,13107200:65792,14155776:67109124,15204352:67174660,16252928:67108864,16777216:67174656,17825792:65540,18874368:65536,19922944:67109120,20971520:256,22020096:67174660,23068672:67108868,24117248:0,25165824:67109124,26214400:67108864,27262976:4,28311552:65792,29360128:67174400,30408704:260,31457280:65796,32505856:67174404,17301504:67108864,18350080:260,19398656:67174656,20447232:0,21495808:65540,22544384:67109120,23592960:256,24641536:67174404,25690112:65536,26738688:67174660,27787264:65796,28835840:67108868,29884416:67109124,30932992:67174400,31981568:4,33030144:65792},{0:2151682048,65536:2147487808,131072:4198464,196608:2151677952,262144:0,327680:4198400,393216:2147483712,458752:4194368,524288:2147483648,589824:4194304,655360:64,720896:2147487744,786432:2151678016,851968:4160,917504:4096,983040:2151682112,32768:2147487808,98304:64,163840:2151678016,229376:2147487744,294912:4198400,360448:2151682112,425984:0,491520:2151677952,557056:4096,622592:2151682048,688128:4194304,753664:4160,819200:2147483648,884736:4194368,950272:4198464,1015808:2147483712,1048576:4194368,1114112:4198400,1179648:2147483712,1245184:0,1310720:4160,1376256:2151678016,1441792:2151682048,1507328:2147487808,1572864:2151682112,1638400:2147483648,1703936:2151677952,1769472:4198464,1835008:2147487744,1900544:4194304,1966080:64,2031616:4096,1081344:2151677952,1146880:2151682112,1212416:0,1277952:4198400,1343488:4194368,1409024:2147483648,1474560:2147487808,1540096:64,1605632:2147483712,1671168:4096,1736704:2147487744,1802240:2151678016,1867776:4160,1933312:2151682048,1998848:4194304,2064384:4198464},{0:128,4096:17039360,8192:262144,12288:536870912,16384:537133184,20480:16777344,24576:553648256,28672:262272,32768:16777216,36864:537133056,40960:536871040,45056:553910400,49152:553910272,53248:0,57344:17039488,61440:553648128,2048:17039488,6144:553648256,10240:128,14336:17039360,18432:262144,22528:537133184,26624:553910272,30720:536870912,34816:537133056,38912:0,43008:553910400,47104:16777344,51200:536871040,55296:553648128,59392:16777216,63488:262272,65536:262144,69632:128,73728:536870912,77824:553648256,81920:16777344,86016:553910272,90112:537133184,94208:16777216,98304:553910400,102400:553648128,106496:17039360,110592:537133056,114688:262272,118784:536871040,122880:0,126976:17039488,67584:553648256,71680:16777216,75776:17039360,79872:537133184,83968:536870912,88064:17039488,92160:128,96256:553910272,100352:262272,104448:553910400,108544:0,112640:553648128,116736:16777344,120832:262144,124928:537133056,129024:536871040},{0:268435464,256:8192,512:270532608,768:270540808,1024:268443648,1280:2097152,1536:2097160,1792:268435456,2048:0,2304:268443656,2560:2105344,2816:8,3072:270532616,3328:2105352,3584:8200,3840:270540800,128:270532608,384:270540808,640:8,896:2097152,1152:2105352,1408:268435464,1664:268443648,1920:8200,2176:2097160,2432:8192,2688:268443656,2944:270532616,3200:0,3456:270540800,3712:2105344,3968:268435456,4096:268443648,4352:270532616,4608:270540808,4864:8200,5120:2097152,5376:268435456,5632:268435464,5888:2105344,6144:2105352,6400:0,6656:8,6912:270532608,7168:8192,7424:268443656,7680:270540800,7936:2097160,4224:8,4480:2105344,4736:2097152,4992:268435464,5248:268443648,5504:8200,5760:270540808,6016:270532608,6272:270540800,6528:270532616,6784:8192,7040:2105352,7296:2097160,7552:0,7808:268435456,8064:268443656},{0:1048576,16:33555457,32:1024,48:1049601,64:34604033,80:0,96:1,112:34603009,128:33555456,144:1048577,160:33554433,176:34604032,192:34603008,208:1025,224:1049600,240:33554432,8:34603009,24:0,40:33555457,56:34604032,72:1048576,88:33554433,104:33554432,120:1025,136:1049601,152:33555456,168:34603008,184:1048577,200:1024,216:34604033,232:1,248:1049600,256:33554432,272:1048576,288:33555457,304:34603009,320:1048577,336:33555456,352:34604032,368:1049601,384:1025,400:34604033,416:1049600,432:1,448:0,464:34603008,480:33554433,496:1024,264:1049600,280:33555457,296:34603009,312:1,328:33554432,344:1048576,360:1025,376:34604032,392:33554433,408:34603008,424:0,440:34604033,456:1049601,472:1024,488:33555456,504:1048577},{0:134219808,1:131072,2:134217728,3:32,4:131104,5:134350880,6:134350848,7:2048,8:134348800,9:134219776,10:133120,11:134348832,12:2080,13:0,14:134217760,15:133152,2147483648:2048,2147483649:134350880,2147483650:134219808,2147483651:134217728,2147483652:134348800,2147483653:133120,2147483654:133152,2147483655:32,2147483656:134217760,2147483657:2080,2147483658:131104,2147483659:134350848,2147483660:0,2147483661:134348832,2147483662:134219776,2147483663:131072,16:133152,17:134350848,18:32,19:2048,20:134219776,21:134217760,22:134348832,23:131072,24:0,25:131104,26:134348800,27:134219808,28:134350880,29:133120,30:2080,31:134217728,2147483664:131072,2147483665:2048,2147483666:134348832,2147483667:133152,2147483668:32,2147483669:134348800,2147483670:134217728,2147483671:134219808,2147483672:134350880,2147483673:134217760,2147483674:134219776,2147483675:0,2147483676:133120,2147483677:2080,2147483678:131104,2147483679:134350848}],w2=[4160749569,528482304,33030144,2064384,129024,8064,504,2147483679],k2=p.DES=w.extend({_doReset:function(){for(var t=this._key.words,e=[],r=0;r<56;r++){var i=y2[r]-1;e[r]=t[i>>>5]>>>31-i%32&1}for(var o=this._subKeys=[],n=0;n<16;n++){for(var s=o[n]=[],c=v2[n],r=0;r<24;r++)s[r/6|0]|=e[(g2[r]-1+c)%28]<<31-r%6,s[4+(r/6|0)]|=e[28+(g2[r+24]-1+c)%28]<<31-r%6;s[0]=s[0]<<1|s[0]>>>31;for(r=1;r<7;r++)s[r]=s[r]>>>4*(r-1)+3;s[7]=s[7]<<5|s[7]>>>27}for(var a=this._invSubKeys=[],r=0;r<16;r++)a[r]=o[15-r]},encryptBlock:function(t,e){this._doCryptBlock(t,e,this._subKeys)},decryptBlock:function(t,e){this._doCryptBlock(t,e,this._invSubKeys)},_doCryptBlock:function(t,e,r){this._lBlock=t[e],this._rBlock=t[e+1],x2.call(this,4,252645135),x2.call(this,16,65535),b2.call(this,2,858993459),b2.call(this,8,16711935),x2.call(this,1,1431655765);for(var i=0;i<16;i++){for(var o=r[i],n=this._lBlock,s=this._rBlock,c=0,a=0;a<8;a++)c|=B2[a][((s^o[a])&w2[a])>>>0];this._lBlock=s,this._rBlock=n^c}var h=this._lBlock;this._lBlock=this._rBlock,this._rBlock=h,x2.call(this,1,1431655765),b2.call(this,8,16711935),b2.call(this,2,858993459),x2.call(this,16,65535),x2.call(this,4,252645135),t[e]=this._lBlock,t[e+1]=this._rBlock},keySize:2,ivSize:2,blockSize:2});function x2(t,e){e=(this._lBlock>>>t^this._rBlock)&e;this._rBlock^=e,this._lBlock^=e<>>t^this._lBlock)&e;this._lBlock^=e,this._rBlock^=e<192.");var e=t.slice(0,2),r=t.length<4?t.slice(0,2):t.slice(2,4),t=t.length<6?t.slice(0,2):t.slice(4,6);this._des1=k2.createEncryptor(_2.create(e)),this._des2=k2.createEncryptor(_2.create(r)),this._des3=k2.createEncryptor(_2.create(t))},encryptBlock:function(t,e){this._des1.encryptBlock(t,e),this._des2.decryptBlock(t,e),this._des3.encryptBlock(t,e)},decryptBlock:function(t,e){this._des3.decryptBlock(t,e),this._des2.encryptBlock(t,e),this._des1.decryptBlock(t,e)},keySize:6,ivSize:2,blockSize:2}),P.TripleDES=w._createHelper(p);var u=i,P=u.lib.StreamCipher,w=u.algo,m2=w.RC4=P.extend({_doReset:function(){for(var t=this._key,e=t.words,r=t.sigBytes,i=this._S=[],o=0;o<256;o++)i[o]=o;for(var o=0,n=0;o<256;o++){var s=o%r,s=e[s>>>2]>>>24-s%4*8&255,n=(n+i[o]+s)%256,s=i[o];i[o]=i[n],i[n]=s}this._i=this._j=0},_doProcessBlock:function(t,e){t[e]^=S2.call(this)},keySize:8,ivSize:0});function S2(){for(var t=this._S,e=this._i,r=this._j,i=0,o=0;o<4;o++){var r=(r+t[e=(e+1)%256])%256,n=t[e];t[e]=t[r],t[r]=n,i|=t[(t[e]+t[r])%256]<<24-8*o}return this._i=e,this._j=r,i}function A2(){for(var t=this._X,e=this._C,r=0;r<8;r++)c[r]=e[r];e[0]=e[0]+1295307597+this._b|0,e[1]=e[1]+3545052371+(e[0]>>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],o=65535&i,n=i>>>16;a[r]=((o*o>>>17)+o*n>>>15)+n*n^((4294901760&i)*i|0)+((65535&i)*i|0)}t[0]=a[0]+(a[7]<<16|a[7]>>>16)+(a[6]<<16|a[6]>>>16)|0,t[1]=a[1]+(a[0]<<8|a[0]>>>24)+a[7]|0,t[2]=a[2]+(a[1]<<16|a[1]>>>16)+(a[0]<<16|a[0]>>>16)|0,t[3]=a[3]+(a[2]<<8|a[2]>>>24)+a[1]|0,t[4]=a[4]+(a[3]<<16|a[3]>>>16)+(a[2]<<16|a[2]>>>16)|0,t[5]=a[5]+(a[4]<<8|a[4]>>>24)+a[3]|0,t[6]=a[6]+(a[5]<<16|a[5]>>>16)+(a[4]<<16|a[4]>>>16)|0,t[7]=a[7]+(a[6]<<8|a[6]>>>24)+a[5]|0}function z2(){for(var t=this._X,e=this._C,r=0;r<8;r++)f[r]=e[r];e[0]=e[0]+1295307597+this._b|0,e[1]=e[1]+3545052371+(e[0]>>>0>>0?1:0)|0,e[2]=e[2]+886263092+(e[1]>>>0>>0?1:0)|0,e[3]=e[3]+1295307597+(e[2]>>>0>>0?1:0)|0,e[4]=e[4]+3545052371+(e[3]>>>0>>0?1:0)|0,e[5]=e[5]+886263092+(e[4]>>>0>>0?1:0)|0,e[6]=e[6]+1295307597+(e[5]>>>0>>0?1:0)|0,e[7]=e[7]+3545052371+(e[6]>>>0>>0?1:0)|0,this._b=e[7]>>>0>>0?1:0;for(r=0;r<8;r++){var i=t[r]+e[r],o=65535&i,n=i>>>16;d[r]=((o*o>>>17)+o*n>>>15)+n*n^((4294901760&i)*i|0)+((65535&i)*i|0)}t[0]=d[0]+(d[7]<<16|d[7]>>>16)+(d[6]<<16|d[6]>>>16)|0,t[1]=d[1]+(d[0]<<8|d[0]>>>24)+d[7]|0,t[2]=d[2]+(d[1]<<16|d[1]>>>16)+(d[0]<<16|d[0]>>>16)|0,t[3]=d[3]+(d[2]<<8|d[2]>>>24)+d[1]|0,t[4]=d[4]+(d[3]<<16|d[3]>>>16)+(d[2]<<16|d[2]>>>16)|0,t[5]=d[5]+(d[4]<<8|d[4]>>>24)+d[3]|0,t[6]=d[6]+(d[5]<<16|d[5]>>>16)+(d[4]<<16|d[4]>>>16)|0,t[7]=d[7]+(d[6]<<8|d[6]>>>24)+d[5]|0}u.RC4=P._createHelper(m2),w=w.RC4Drop=m2.extend({cfg:m2.cfg.extend({drop:192}),_doReset:function(){m2._doReset.call(this);for(var t=this.cfg.drop;0>>24)|4278255360&(t[r]<<24|t[r]>>>8);for(var i=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],o=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]],r=this._b=0;r<4;r++)A2.call(this);for(r=0;r<8;r++)o[r]^=i[r+4&7];if(e){var e=e.words,n=e[0],e=e[1],n=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8),e=16711935&(e<<8|e>>>24)|4278255360&(e<<24|e>>>8),s=n>>>16|4294901760&e,c=e<<16|65535&n;o[0]^=n,o[1]^=s,o[2]^=e,o[3]^=c,o[4]^=n,o[5]^=s,o[6]^=e,o[7]^=c;for(r=0;r<4;r++)A2.call(this)}},_doProcessBlock:function(t,e){var r=this._X;A2.call(this),n[0]=r[0]^r[5]>>>16^r[3]<<16,n[1]=r[2]^r[7]>>>16^r[5]<<16,n[2]=r[4]^r[1]>>>16^r[7]<<16,n[3]=r[6]^r[3]>>>16^r[1]<<16;for(var i=0;i<4;i++)n[i]=16711935&(n[i]<<8|n[i]>>>24)|4278255360&(n[i]<<24|n[i]>>>8),t[e+i]^=n[i]},blockSize:4,ivSize:2}),p.Rabbit=u._createHelper(P),p=(w=i).lib.StreamCipher,u=w.algo,h=[],f=[],d=[],u=u.RabbitLegacy=p.extend({_doReset:function(){for(var t=this._key.words,e=this.cfg.iv,r=this._X=[t[0],t[3]<<16|t[2]>>>16,t[1],t[0]<<16|t[3]>>>16,t[2],t[1]<<16|t[0]>>>16,t[3],t[2]<<16|t[1]>>>16],i=this._C=[t[2]<<16|t[2]>>>16,4294901760&t[0]|65535&t[1],t[3]<<16|t[3]>>>16,4294901760&t[1]|65535&t[2],t[0]<<16|t[0]>>>16,4294901760&t[2]|65535&t[3],t[1]<<16|t[1]>>>16,4294901760&t[3]|65535&t[0]],o=this._b=0;o<4;o++)z2.call(this);for(o=0;o<8;o++)i[o]^=r[o+4&7];if(e){var t=e.words,e=t[0],t=t[1],e=16711935&(e<<8|e>>>24)|4278255360&(e<<24|e>>>8),t=16711935&(t<<8|t>>>24)|4278255360&(t<<24|t>>>8),n=e>>>16|4294901760&t,s=t<<16|65535&e;i[0]^=e,i[1]^=n,i[2]^=t,i[3]^=s,i[4]^=e,i[5]^=n,i[6]^=t,i[7]^=s;for(o=0;o<4;o++)z2.call(this)}},_doProcessBlock:function(t,e){var r=this._X;z2.call(this),h[0]=r[0]^r[5]>>>16^r[3]<<16,h[1]=r[2]^r[7]>>>16^r[5]<<16,h[2]=r[4]^r[1]>>>16^r[7]<<16,h[3]=r[6]^r[3]>>>16^r[1]<<16;for(var i=0;i<4;i++)h[i]=16711935&(h[i]<<8|h[i]>>>24)|4278255360&(h[i]<<24|h[i]>>>8),t[e+i]^=h[i]},blockSize:4,ivSize:2}),w.RabbitLegacy=p._createHelper(u);{w=(P=i).lib.BlockCipher,p=P.algo;const F=16,D2=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],E2=[[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946],[1266315497,3048417604,3681880366,3289982499,290971e4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055],[3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504],[976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409e3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462]];var H2={pbox:[],sbox:[]};function C2(t,e){var r=t.sbox[0][e>>24&255]+t.sbox[1][e>>16&255];return r=(r^=t.sbox[2][e>>8&255])+t.sbox[3][255&e]}function R2(e,t,r){let i=t,o=r,n;for(let t=0;t=a&&(e=0);let r=0,i=0,o=0;for(let t=0;t= 48 && charCode <= 57) { + number = number * 10 + (charCode - 48); + } else { + if (number !== 0) { + signature += createRandom(number); + number = 0; + } + signature += String.fromCharCode(charCode); + } + } + if (number !== 0) { + signature += createRandom(number); + } + return signature; +} + +function oC(e) { + return e && e.__esModule && Object.prototype.hasOwnProperty.call(e, "default") ? e.default : e +} +var Tm = { + exports: {} +} + , Sm = { + exports: {} +}; +(function() { + var e = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + , t = { + rotl: function(n, r) { + return n << r | n >>> 32 - r + }, + rotr: function(n, r) { + return n << 32 - r | n >>> r + }, + endian: function(n) { + if (n.constructor == Number) + return t.rotl(n, 8) & 16711935 | t.rotl(n, 24) & 4278255360; + for (var r = 0; r < n.length; r++) + n[r] = t.endian(n[r]); + return n + }, + randomBytes: function(n) { + for (var r = []; n > 0; n--) + r.push(Math.floor(Math.random() * 256)); + return r + }, + bytesToWords: function(n) { + for (var r = [], s = 0, o = 0; s < n.length; s++, + o += 8) + r[o >>> 5] |= n[s] << 24 - o % 32; + return r + }, + wordsToBytes: function(n) { + for (var r = [], s = 0; s < n.length * 32; s += 8) + r.push(n[s >>> 5] >>> 24 - s % 32 & 255); + return r + }, + bytesToHex: function(n) { + for (var r = [], s = 0; s < n.length; s++) + r.push((n[s] >>> 4).toString(16)), + r.push((n[s] & 15).toString(16)); + return r.join("") + }, + hexToBytes: function(n) { + for (var r = [], s = 0; s < n.length; s += 2) + r.push(parseInt(n.substr(s, 2), 16)); + return r + }, + bytesToBase64: function(n) { + for (var r = [], s = 0; s < n.length; s += 3) + for (var o = n[s] << 16 | n[s + 1] << 8 | n[s + 2], i = 0; i < 4; i++) + s * 8 + i * 6 <= n.length * 8 ? r.push(e.charAt(o >>> 6 * (3 - i) & 63)) : r.push("="); + return r.join("") + }, + base64ToBytes: function(n) { + n = n.replace(/[^A-Z0-9+\/]/ig, ""); + for (var r = [], s = 0, o = 0; s < n.length; o = ++s % 4) + o != 0 && r.push((e.indexOf(n.charAt(s - 1)) & Math.pow(2, -2 * o + 8) - 1) << o * 2 | e.indexOf(n.charAt(s)) >>> 6 - o * 2); + return r + } + }; + Sm.exports = t +} +)(); +var iC = Sm.exports + , nl = { + utf8: { + stringToBytes: function(e) { + return nl.bin.stringToBytes(unescape(encodeURIComponent(e))) + }, + bytesToString: function(e) { + return decodeURIComponent(escape(nl.bin.bytesToString(e))) + } + }, + bin: { + stringToBytes: function(e) { + for (var t = [], n = 0; n < e.length; n++) + t.push(e.charCodeAt(n) & 255); + return t + }, + bytesToString: function(e) { + for (var t = [], n = 0; n < e.length; n++) + t.push(String.fromCharCode(e[n])); + return t.join("") + } + } +}, sd = nl; + +var aC = function(e) { + return e != null && (Cm(e) || lC(e) || !!e._isBuffer) +}; +function Cm(e) { + return !!e.constructor && typeof e.constructor.isBuffer == "function" && e.constructor.isBuffer(e) +} +function lC(e) { + return typeof e.readFloatLE == "function" && typeof e.slice == "function" && Cm(e.slice(0, 0)) +} +(function() { + var e = iC + , t = sd.utf8 + , n = aC + , r = sd.bin + , s = function(o, i) { + o.constructor == String ? i && i.encoding === "binary" ? o = r.stringToBytes(o) : o = t.stringToBytes(o) : n(o) ? o = Array.prototype.slice.call(o, 0) : !Array.isArray(o) && o.constructor !== Uint8Array && (o = o.toString()); + for (var a = e.bytesToWords(o), l = o.length * 8, c = 1732584193, u = -271733879, f = -1732584194, d = 271733878, m = 0; m < a.length; m++) + a[m] = (a[m] << 8 | a[m] >>> 24) & 16711935 | (a[m] << 24 | a[m] >>> 8) & 4278255360; + a[l >>> 5] |= 128 << l % 32, + a[(l + 64 >>> 9 << 4) + 14] = l; + for (var v = s._ff, w = s._gg, R = s._hh, y = s._ii, m = 0; m < a.length; m += 16) { + var b = c + , _ = u + , g = f + , C = d; + c = v(c, u, f, d, a[m + 0], 7, -680876936), + d = v(d, c, u, f, a[m + 1], 12, -389564586), + f = v(f, d, c, u, a[m + 2], 17, 606105819), + u = v(u, f, d, c, a[m + 3], 22, -1044525330), + c = v(c, u, f, d, a[m + 4], 7, -176418897), + d = v(d, c, u, f, a[m + 5], 12, 1200080426), + f = v(f, d, c, u, a[m + 6], 17, -1473231341), + u = v(u, f, d, c, a[m + 7], 22, -45705983), + c = v(c, u, f, d, a[m + 8], 7, 1770035416), + d = v(d, c, u, f, a[m + 9], 12, -1958414417), + f = v(f, d, c, u, a[m + 10], 17, -42063), + u = v(u, f, d, c, a[m + 11], 22, -1990404162), + c = v(c, u, f, d, a[m + 12], 7, 1804603682), + d = v(d, c, u, f, a[m + 13], 12, -40341101), + f = v(f, d, c, u, a[m + 14], 17, -1502002290), + u = v(u, f, d, c, a[m + 15], 22, 1236535329), + c = w(c, u, f, d, a[m + 1], 5, -165796510), + d = w(d, c, u, f, a[m + 6], 9, -1069501632), + f = w(f, d, c, u, a[m + 11], 14, 643717713), + u = w(u, f, d, c, a[m + 0], 20, -373897302), + c = w(c, u, f, d, a[m + 5], 5, -701558691), + d = w(d, c, u, f, a[m + 10], 9, 38016083), + f = w(f, d, c, u, a[m + 15], 14, -660478335), + u = w(u, f, d, c, a[m + 4], 20, -405537848), + c = w(c, u, f, d, a[m + 9], 5, 568446438), + d = w(d, c, u, f, a[m + 14], 9, -1019803690), + f = w(f, d, c, u, a[m + 3], 14, -187363961), + u = w(u, f, d, c, a[m + 8], 20, 1163531501), + c = w(c, u, f, d, a[m + 13], 5, -1444681467), + d = w(d, c, u, f, a[m + 2], 9, -51403784), + f = w(f, d, c, u, a[m + 7], 14, 1735328473), + u = w(u, f, d, c, a[m + 12], 20, -1926607734), + c = R(c, u, f, d, a[m + 5], 4, -378558), + d = R(d, c, u, f, a[m + 8], 11, -2022574463), + f = R(f, d, c, u, a[m + 11], 16, 1839030562), + u = R(u, f, d, c, a[m + 14], 23, -35309556), + c = R(c, u, f, d, a[m + 1], 4, -1530992060), + d = R(d, c, u, f, a[m + 4], 11, 1272893353), + f = R(f, d, c, u, a[m + 7], 16, -155497632), + u = R(u, f, d, c, a[m + 10], 23, -1094730640), + c = R(c, u, f, d, a[m + 13], 4, 681279174), + d = R(d, c, u, f, a[m + 0], 11, -358537222), + f = R(f, d, c, u, a[m + 3], 16, -722521979), + u = R(u, f, d, c, a[m + 6], 23, 76029189), + c = R(c, u, f, d, a[m + 9], 4, -640364487), + d = R(d, c, u, f, a[m + 12], 11, -421815835), + f = R(f, d, c, u, a[m + 15], 16, 530742520), + u = R(u, f, d, c, a[m + 2], 23, -995338651), + c = y(c, u, f, d, a[m + 0], 6, -198630844), + d = y(d, c, u, f, a[m + 7], 10, 1126891415), + f = y(f, d, c, u, a[m + 14], 15, -1416354905), + u = y(u, f, d, c, a[m + 5], 21, -57434055), + c = y(c, u, f, d, a[m + 12], 6, 1700485571), + d = y(d, c, u, f, a[m + 3], 10, -1894986606), + f = y(f, d, c, u, a[m + 10], 15, -1051523), + u = y(u, f, d, c, a[m + 1], 21, -2054922799), + c = y(c, u, f, d, a[m + 8], 6, 1873313359), + d = y(d, c, u, f, a[m + 15], 10, -30611744), + f = y(f, d, c, u, a[m + 6], 15, -1560198380), + u = y(u, f, d, c, a[m + 13], 21, 1309151649), + c = y(c, u, f, d, a[m + 4], 6, -145523070), + d = y(d, c, u, f, a[m + 11], 10, -1120210379), + f = y(f, d, c, u, a[m + 2], 15, 718787259), + u = y(u, f, d, c, a[m + 9], 21, -343485551), + c = c + b >>> 0, + u = u + _ >>> 0, + f = f + g >>> 0, + d = d + C >>> 0 + } + return e.endian([c, u, f, d]) + }; + s._ff = function(o, i, a, l, c, u, f) { + var d = o + (i & a | ~i & l) + (c >>> 0) + f; + return (d << u | d >>> 32 - u) + i + } + , + s._gg = function(o, i, a, l, c, u, f) { + var d = o + (i & l | a & ~l) + (c >>> 0) + f; + return (d << u | d >>> 32 - u) + i + } + , + s._hh = function(o, i, a, l, c, u, f) { + var d = o + (i ^ a ^ l) + (c >>> 0) + f; + return (d << u | d >>> 32 - u) + i + } + , + s._ii = function(o, i, a, l, c, u, f) { + var d = o + (a ^ (i | ~l)) + (c >>> 0) + f; + return (d << u | d >>> 32 - u) + i + } + , + s._blocksize = 16, + s._digestsize = 16, + Tm.exports = function(o, i) { + if (o == null) + throw new Error("Illegal argument " + o); + var a = e.wordsToBytes(s(o, i)); + return i && i.asBytes ? a : i && i.asString ? r.bytesToString(a) : e.bytesToHex(a) + } +} +)(); +var cC = Tm.exports; +var t = { + utf8: { + stringToBytes: function(e) { + return nl.bin.stringToBytes(unescape(encodeURIComponent(e))) + }, + bytesToString: function(e) { + return decodeURIComponent(escape(nl.bin.bytesToString(e))) + } + }, + bin: { + stringToBytes: function(e) { + for (var t = [], n = 0; n < e.length; n++) + t.push(e.charCodeAt(n) & 255); + return t + }, + bytesToString: function(e) { + for (var t = [], n = 0; n < e.length; n++) + t.push(String.fromCharCode(e[n])); + return t.join("") + } + } +}; + +const hC = (e, t, n=!1) => { + if (t.params) { + const o = {}; + Object.keys(t.params).forEach(i => { + t.params[i] !== void 0 && t.params[i] !== null && (o[i] = t.params[i]) + } + ), + t.params = o + } + let r = {}; + const s = t.method.toLowerCase(); + if (s === "get") + t.params = Object.assign({}, e, t.params || {}); + else if (s === "post") + if (typeof t.data == "string") { + let o; + t.data.split("&").forEach(i => { + o = i.split("="), + r[o[0]] = o[1] + } + ), + r = Object.assign({}, e, r), + t.data = Object.keys(r).map(i => `${i}=${r[i]}`).join("&") + } else + r = Object.assign(r, e, t.data || {}), + t.data = r; + return n ? t : (r = Object.assign({}, t.params, r), + r) +} + +const Rm = oC(cC); +const s = Rm(r); + +pC = e => { + let t = Object.keys(e).sort().map(n => { + function r(s) { + return Array.isArray(s) ? s.join(",") : typeof s === "object" ? JSON.stringify(s) : s + } + return n + r(e[n]) + } + ).join(""); + return t += Am + e.lm_s_ts + rl, + Rm(t) +} + + +// final encryption function +let CryptoJS = null; +lm_s_key = atob('ZGQ0NmRiYjQ0MmI2ZTRiYTgxN2Q2MzQ3ZDJkZGY0OTM='); +function requestSign(signParams, cryptoJSPath) { + let sKey = Object.keys(signParams).sort().map(key => { + function getValue(val) { + if (Array.isArray(val)) { + return val.join(','); + } + if (typeof val === 'object') { + return JSON.stringify(val); + } + return val; + } + return key + getValue(signParams[key]); + }).join(''); + + sKey += signParams.lm_s_id + signParams.lm_s_ts + lm_s_key; + console.log(`sKey: ${sKey}`); + CryptoJS = require(cryptoJSPath); + return CryptoJS.MD5(sKey).toString(); +} + +function sign(videoid, cryptoJSPath, platform='web'){ + const vali = createSignature(); + const data_e = { + lm_s_id: Am, + lm_s_ts: r, + lm_s_str: s, + lm_s_ver: 1, + h5: 1 + }; + /* data_e example value + const data_e = { + lm_s_id: Am, + lm_s_ts: "17284909009151", + lm_s_str: "88f9777231dc2d6ac462a1d7ebf5f54e", + lm_s_ver: 1, + h5: 1 + }; + */ + console.log("data_e:",data_e); + + data_i = { + ...data_e, + _time: new Date().valueOf(), + thirdchannel: 6, + videoid: videoid, + area: 'zh', + vali: vali + } + console.log("data_i:",data_i); + + // fake lm_s_sign param value + let lm_s_sign = pC(data_i); + console.log(`fake lm_s_sign: ${lm_s_sign}`); + + //finnal request params + /* + signParams = { + "alias": "liveme", + "tongdun_black_box": "iWPU21728483558afruvSVo6x0", + "os": "android", + "lm_s_id": "LM6000101139961122666757", + "lm_s_ts": "17284909009151", + "lm_s_str": "88f9777231dc2d6ac462a1d7ebf5f54e", + "lm_s_ver": 1, + "h5": 1, + "_time": 1728490664651, + "thirdchannel": 6, + "videoid": "17284844223282059697", + "area": "zh", + "vali": "zH8SlBwnCm4AZWp" + }# + //result: 4eaf71a1ec19b49b7267e4d16e007105 + */ + signParams = { + "alias": "liveme", + "tongdun_black_box": "", + "os": platform, + ...data_i + } + console.log("signParams: ", signParams); + lm_s_sign = requestSign(signParams, cryptoJSPath); + console.log(`\x1b[32mfinal lm_s_sign: \x1b[0m${lm_s_sign}\n`); + data = { + ...signParams, + lm_s_sign + } + return data; + +} + +module.exports = { + sign + }; diff --git a/javascript/migu.js b/javascript/migu.js new file mode 100644 index 0000000..3ac86d3 --- /dev/null +++ b/javascript/migu.js @@ -0,0 +1,143 @@ +/** + * Function to get the ddCalcu parameter value + * @param {string} inputUrl - The original URL before encryption + * @returns {Promise} - Returns the calculated ddCalcu value + */ +async function getDdCalcu(inputUrl) { + let wasmInstance = null; + let memory_p = null; // Uint8Array view + let memory_h = null; // Uint32Array view + + // Fixed parameter + const f = 'PBTxuWiTEbUPPFcpyxs0ww=='; + + // Utility function: Convert string to UTF-8 in memory + function stringToUTF8(string, offset) { + const encoder = new TextEncoder(); + const encoded = encoder.encode(string); + for (let i = 0; i < encoded.length; i++) { + memory_p[offset + i] = encoded[i]; + } + memory_p[offset + encoded.length] = 0; // Null-terminate + } + + // Utility function: Read UTF-8 string from memory address + function UTF8ToString(offset) { + let s = ''; + let i = 0; + while (memory_p[offset + i]) { + s += String.fromCharCode(memory_p[offset + i]); + i++; + } + return s; + } + + // WASM import function stubs + function a(e, t, r, n) { + let s = 0; + for (let i = 0; i < r; i++) { + const d = memory_h[t + 4 >> 2]; + t += 8; + s += d; + } + memory_h[n >> 2] = s; + return 0; + } + + function b() {} + + function c() {} + + // Step 1: Retrieve playerVersion + const settingsResp = await fetch('https://app-sc.miguvideo.com/common/v1/settings/H5_DetailPage'); + const settingsData = await settingsResp.json(); + const playerVersion = JSON.parse(settingsData.body.paramValue).playerVersion; + + // Step 2: Load WASM module + const wasmUrl = `https://www.miguvideo.com/mgs/player/prd/${playerVersion}/dist/mgprtcl.wasm`; + const wasmResp = await fetch(wasmUrl); + if (!wasmResp.ok) throw new Error("Failed to download WASM"); + const wasmBuffer = await wasmResp.arrayBuffer(); + + const importObject = { + a: { a, b, c } + }; + + const { instance } = await WebAssembly.instantiate(wasmBuffer, importObject); + wasmInstance = instance; + + const memory = wasmInstance.exports.d; + memory_p = new Uint8Array(memory.buffer); + memory_h = new Uint32Array(memory.buffer); + + const exports = { + CallInterface1: wasmInstance.exports.h, + CallInterface2: wasmInstance.exports.i, + CallInterface3: wasmInstance.exports.j, + CallInterface4: wasmInstance.exports.k, + CallInterface6: wasmInstance.exports.m, + CallInterface7: wasmInstance.exports.n, + CallInterface8: wasmInstance.exports.o, + CallInterface9: wasmInstance.exports.p, + CallInterface10: wasmInstance.exports.q, + CallInterface11: wasmInstance.exports.r, + CallInterface14: wasmInstance.exports.t, + malloc: wasmInstance.exports.u, + }; + + const parsedUrl = new URL(inputUrl); + const query = Object.fromEntries(parsedUrl.searchParams); + + const o = query.userid || ''; + const a_val = query.timestamp || ''; + const s = query.ProgramID || ''; + const u = query.Channel_ID || ''; + const v = query.puData || ''; + + // Allocate memory + const d = exports.malloc(o.length + 1); + const h = exports.malloc(a_val.length + 1); + const y = exports.malloc(s.length + 1); + const m = exports.malloc(u.length + 1); + const g = exports.malloc(v.length + 1); + const b_val = exports.malloc(f.length + 1); + const E = exports.malloc(128); + const T = exports.malloc(128); + + // Write data to memory + stringToUTF8(o, d); + stringToUTF8(a_val, h); + stringToUTF8(s, y); + stringToUTF8(u, m); + stringToUTF8(v, g); + stringToUTF8(f, b_val); + + // Call interface functions + const S = exports.CallInterface6(); // Create context + exports.CallInterface1(S, y, s.length); + exports.CallInterface10(S, h, a_val.length); + exports.CallInterface9(S, d, o.length); + exports.CallInterface3(S, 0, 0); + exports.CallInterface11(S, 0, 0); + exports.CallInterface8(S, g, v.length); + exports.CallInterface2(S, m, u.length); + exports.CallInterface14(S, b_val, f.length, T, 128); + + const w = UTF8ToString(T); + const I = exports.malloc(w.length + 1); + stringToUTF8(w, I); + + exports.CallInterface7(S, I, w.length); + exports.CallInterface4(S, E, 128); + + return UTF8ToString(E); +} + +const url = process.argv[2]; + +getDdCalcu(url).then(result => { + console.log(result); +}).catch(err => { + console.error(err); + process.exit(1); +}); \ No newline at end of file diff --git a/javascript/taobao-sign.js b/javascript/taobao-sign.js new file mode 100644 index 0000000..b130db5 --- /dev/null +++ b/javascript/taobao-sign.js @@ -0,0 +1,78 @@ +function sign(e) { + function t(e, t) { + return e << t | e >>> 32 - t + } + function o(e, t) { + var o, n, r, i, a; + return r = 2147483648 & e, + i = 2147483648 & t, + a = (1073741823 & e) + (1073741823 & t), + (o = 1073741824 & e) & (n = 1073741824 & t) ? 2147483648 ^ a ^ r ^ i : o | n ? 1073741824 & a ? 3221225472 ^ a ^ r ^ i : 1073741824 ^ a ^ r ^ i : a ^ r ^ i + } + function n(e, n, r, i, a, s, u) { + return o(t(e = o(e, o(o(function(e, t, o) { + return e & t | ~e & o + }(n, r, i), a), u)), s), n) + } + function r(e, n, r, i, a, s, u) { + return o(t(e = o(e, o(o(function(e, t, o) { + return e & o | t & ~o + }(n, r, i), a), u)), s), n) + } + function i(e, n, r, i, a, s, u) { + return o(t(e = o(e, o(o(function(e, t, o) { + return e ^ t ^ o + }(n, r, i), a), u)), s), n) + } + function a(e, n, r, i, a, s, u) { + return o(t(e = o(e, o(o(function(e, t, o) { + return t ^ (e | ~o) + }(n, r, i), a), u)), s), n) + } + function s(e) { + var t, o = "", n = ""; + for (t = 0; 3 >= t; t++) + o += (n = "0" + (e >>> 8 * t & 255).toString(16)).substr(n.length - 2, 2); + return o + } + var u, l, d, c, p, f, h, m, y, g; + for (g = function(e) { + for (var t = e.length, o = t + 8, n = 16 * ((o - o % 64) / 64 + 1), r = Array(n - 1), i = 0, a = 0; t > a; ) + i = a % 4 * 8, + r[(a - a % 4) / 4] |= e.charCodeAt(a) << i, + a++; + return i = a % 4 * 8, + r[(a - a % 4) / 4] |= 128 << i, + r[n - 2] = t << 3, + r[n - 1] = t >>> 29, + r + }(e = function(e) { + var t = String.fromCharCode; + e = e.replace(/\r\n/g, "\n"); + for (var o, n = "", r = 0; r < e.length; r++) + 128 > (o = e.charCodeAt(r)) ? n += t(o) : o > 127 && 2048 > o ? (n += t(o >> 6 | 192), + n += t(63 & o | 128)) : (n += t(o >> 12 | 224), + n += t(o >> 6 & 63 | 128), + n += t(63 & o | 128)); + return n + }(e)), + f = 1732584193, + h = 4023233417, + m = 2562383102, + y = 271733878, + u = 0; u < g.length; u += 16) + l = f, + d = h, + c = m, + p = y, + h = a(h = a(h = a(h = a(h = i(h = i(h = i(h = i(h = r(h = r(h = r(h = r(h = n(h = n(h = n(h = n(h, m = n(m, y = n(y, f = n(f, h, m, y, g[u + 0], 7, 3614090360), h, m, g[u + 1], 12, 3905402710), f, h, g[u + 2], 17, 606105819), y, f, g[u + 3], 22, 3250441966), m = n(m, y = n(y, f = n(f, h, m, y, g[u + 4], 7, 4118548399), h, m, g[u + 5], 12, 1200080426), f, h, g[u + 6], 17, 2821735955), y, f, g[u + 7], 22, 4249261313), m = n(m, y = n(y, f = n(f, h, m, y, g[u + 8], 7, 1770035416), h, m, g[u + 9], 12, 2336552879), f, h, g[u + 10], 17, 4294925233), y, f, g[u + 11], 22, 2304563134), m = n(m, y = n(y, f = n(f, h, m, y, g[u + 12], 7, 1804603682), h, m, g[u + 13], 12, 4254626195), f, h, g[u + 14], 17, 2792965006), y, f, g[u + 15], 22, 1236535329), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 1], 5, 4129170786), h, m, g[u + 6], 9, 3225465664), f, h, g[u + 11], 14, 643717713), y, f, g[u + 0], 20, 3921069994), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 5], 5, 3593408605), h, m, g[u + 10], 9, 38016083), f, h, g[u + 15], 14, 3634488961), y, f, g[u + 4], 20, 3889429448), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 9], 5, 568446438), h, m, g[u + 14], 9, 3275163606), f, h, g[u + 3], 14, 4107603335), y, f, g[u + 8], 20, 1163531501), m = r(m, y = r(y, f = r(f, h, m, y, g[u + 13], 5, 2850285829), h, m, g[u + 2], 9, 4243563512), f, h, g[u + 7], 14, 1735328473), y, f, g[u + 12], 20, 2368359562), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 5], 4, 4294588738), h, m, g[u + 8], 11, 2272392833), f, h, g[u + 11], 16, 1839030562), y, f, g[u + 14], 23, 4259657740), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 1], 4, 2763975236), h, m, g[u + 4], 11, 1272893353), f, h, g[u + 7], 16, 4139469664), y, f, g[u + 10], 23, 3200236656), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 13], 4, 681279174), h, m, g[u + 0], 11, 3936430074), f, h, g[u + 3], 16, 3572445317), y, f, g[u + 6], 23, 76029189), m = i(m, y = i(y, f = i(f, h, m, y, g[u + 9], 4, 3654602809), h, m, g[u + 12], 11, 3873151461), f, h, g[u + 15], 16, 530742520), y, f, g[u + 2], 23, 3299628645), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 0], 6, 4096336452), h, m, g[u + 7], 10, 1126891415), f, h, g[u + 14], 15, 2878612391), y, f, g[u + 5], 21, 4237533241), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 12], 6, 1700485571), h, m, g[u + 3], 10, 2399980690), f, h, g[u + 10], 15, 4293915773), y, f, g[u + 1], 21, 2240044497), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 8], 6, 1873313359), h, m, g[u + 15], 10, 4264355552), f, h, g[u + 6], 15, 2734768916), y, f, g[u + 13], 21, 1309151649), m = a(m, y = a(y, f = a(f, h, m, y, g[u + 4], 6, 4149444226), h, m, g[u + 11], 10, 3174756917), f, h, g[u + 2], 15, 718787259), y, f, g[u + 9], 21, 3951481745), + f = o(f, l), + h = o(h, d), + m = o(m, c), + y = o(y, p); + return (s(f) + s(h) + s(m) + s(y)).toLowerCase() +} + +// 正确sign值:05748e8359cd3e6deaab02d15caafc11 +// var sg =sign('5655b7041ca049730330701082886efd&1719411639403&12574478&{"componentKey":"wp_pc_shop_basic_info","params":"{\\"memberId\\":\\"b2b-22133374292418351a\\"}"}') +// console.log(sg) \ No newline at end of file diff --git a/javascript/x-bogus.js b/javascript/x-bogus.js new file mode 100644 index 0000000..5cf2bde --- /dev/null +++ b/javascript/x-bogus.js @@ -0,0 +1,564 @@ +var window = null; + +function _0x5cd844(e) { + var b = { + exports: {} + }; + return e(b, b.exports), b.exports +} +jsvmp = function(e, b, a) { + function f(e, b, a) { + return (f = function() { + if ("undefined" == typeof Reflect || !Reflect.construct || Reflect.construct.sham) return !1; + if ("function" == typeof Proxy) return !0; + try { + return Date.prototype.toString.call(Reflect.construct(Date, [], function() {})), !0 + } catch (e) { + return !1 + } + }() ? Reflect.construct : function(e, b, a) { + var f = [null]; + f.push.apply(f, b); + var c = new(Function.bind.apply(e, f)); + return a && function(e, b) { + (Object.setPrototypeOf || function(e, b) { + return e.__proto__ = b, e + })(e, b) + }(c, a.prototype), c + }).apply(null, arguments) + } + + function c(e) { + return function(e) { + if (Array.isArray(e)) { + for (var b = 0, a = new Array(e.length); b < e.length; b++) a[b] = e[b]; + return a + } + }(e) || function(e) { + if (Symbol.iterator in Object(e) || "[object Arguments]" === Object.prototype.toString.call(e)) return Array.from(e) + }(e) || function() { + throw new TypeError("Invalid attempt to spread non-iterable instance") + }() + } + for (var r = [], t = 0, d = [], i = 0, n = function(e, b) { + var a = e[b++], + f = e[b], + c = parseInt("" + a + f, 16); + if (c >> 7 == 0) return [1, c]; + if (c >> 6 == 2) { + var r = parseInt("" + e[++b] + e[++b], 16); + return c &= 63, [2, r = (c <<= 8) + r] + } + if (c >> 6 == 3) { + var t = parseInt("" + e[++b] + e[++b], 16), + d = parseInt("" + e[++b] + e[++b], 16); + return c &= 63, [3, d = (c <<= 16) + (t <<= 8) + d] + } + }, s = function(e, b) { + var a = parseInt("" + e[b] + e[b + 1], 16); + return a > 127 ? -256 + a : a + }, o = function(e, b) { + var a = parseInt("" + e[b] + e[b + 1] + e[b + 2] + e[b + 3], 16); + return a > 32767 ? -65536 + a : a + }, l = function(e, b) { + var a = parseInt("" + e[b] + e[b + 1] + e[b + 2] + e[b + 3] + e[b + 4] + e[b + 5] + e[b + 6] + e[b + 7], 16); + return a > 2147483647 ? 0 + a : a + }, _ = function(e, b) { + return parseInt("" + e[b] + e[b + 1], 16) + }, x = function(e, b) { + return parseInt("" + e[b] + e[b + 1] + e[b + 2] + e[b + 3], 16) + }, u = u || this || window, h = (e.length, 0), p = "", y = h; y < h + 16; y++) { + var v = "" + e[y++] + e[y]; + v = parseInt(v, 16), p += String.fromCharCode(v) + } + if ("HNOJ@?RC" != p) throw new Error("error magic number " + p); + parseInt("" + e[h += 16] + e[h + 1], 16), h += 8, t = 0; + for (var g = 0; g < 4; g++) { + var w = h + 2 * g, + A = parseInt("" + e[w++] + e[w], 16); + t += (3 & A) << 2 * g + } + h += 16; + var C = parseInt("" + e[h += 8] + e[h + 1] + e[h + 2] + e[h + 3] + e[h + 4] + e[h + 5] + e[h + 6] + e[h + 7], 16), + m = C, + S = h += 8, + z = x(e, h += C); + z[1], h += 4, r = { + p: [], + q: [] + }; + for (var B = 0; B < z; B++) { + for (var R = n(e, h), q = h += 2 * R[0], I = r.p.length, k = 0; k < R[1]; k++) { + var j = n(e, q); + r.p.push(j[1]), q += 2 * j[0] + } + h = q, r.q.push([I, r.p.length]) + } + var O = { + 5: 1, + 6: 1, + 70: 1, + 22: 1, + 23: 1, + 37: 1, + 73: 1 + }, + U = { + 72: 1 + }, + D = { + 74: 1 + }, + N = { + 11: 1, + 12: 1, + 24: 1, + 26: 1, + 27: 1, + 31: 1 + }, + J = { + 10: 1 + }, + L = { + 2: 1, + 29: 1, + 30: 1, + 20: 1 + }, + T = [], + E = []; + + function M(e, b, a) { + for (var f = b; f < b + a;) { + var c = _(e, f); + T[f] = c, f += 2, U[c] ? (E[f] = s(e, f), f += 2) : O[c] ? (E[f] = o(e, f), f += 4) : D[c] ? (E[f] = l(e, f), f += 8) : N[c] ? (E[f] = _(e, f), f += 2) : J[c] ? (E[f] = x(e, f), f += 4) : L[c] && (E[f] = x(e, f), f += 4) + } + } + return F(e, S, m / 2, [], b, a); + + function P(e, b, a, n, h, p, y, v) { + null == p && (p = this); + var g, w, A, C, m = [], + S = 0; + y && (w = y); + var z, B, R = b, + q = R + 2 * a; + if (!v) + for (; R < q;) { + var I = parseInt("" + e[R] + e[R + 1], 16); + R += 2; + var j = 3 & (z = 13 * I % 241); + if (z >>= 2, j < 1) + if (j = 3 & z, z >>= 2, j < 1) { + if ((j = z) < 1) return [1, m[S--]]; + j < 5 ? (w = m[S--], m[S] = m[S] * w) : j < 7 ? (w = m[S--], m[S] = m[S] != w) : j < 14 ? (A = m[S--], C = m[S--], (j = m[S--]).x === P ? j.y >= 1 ? m[++S] = F(e, j.c, j.l, A, j.z, C, null, 1) : (m[++S] = F(e, j.c, j.l, A, j.z, C, null, 0), j.y++) : m[++S] = j.apply(C, A)) : j < 16 && (B = o(e, R), (g = function b() { + var a = arguments; + return b.y > 0 || b.y++, F(e, b.c, b.l, a, b.z, this, null, 0) + }).c = R + 4, g.l = B - 2, g.x = P, g.y = 0, g.z = h, m[S] = g, R += 2 * B - 2) + } else if (j < 2)(j = z) > 8 ? (w = m[S--], m[S] = typeof w) : j > 4 ? m[S -= 1] = m[S][m[S + 1]] : j > 2 && (A = m[S--], (j = m[S]).x === P ? j.y >= 1 ? m[S] = F(e, j.c, j.l, [A], j.z, C, null, 1) : (m[S] = F(e, j.c, j.l, [A], j.z, C, null, 0), j.y++) : m[S] = j(A)); + else if (j < 3) { + if ((j = z) < 9) { + for (w = m[S--], B = x(e, R), j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); + R += 4, m[S--][j] = w + } else if (j < 13) throw m[S--] + } else(j = z) < 1 ? m[++S] = null : j < 3 ? (w = m[S--], m[S] = m[S] >= w) : j < 12 && (m[++S] = void 0); + else if (j < 2) + if (j = 3 & z, z >>= 2, j < 1) + if ((j = z) < 5) { + B = o(e, R); + try { + if (d[i][2] = 1, 1 == (w = P(e, R + 4, B - 3, [], h, p, null, 0))[0]) return w + } catch (b) { + if (d[i] && d[i][1] && 1 == (w = P(e, d[i][1][0], d[i][1][1], [], h, p, b, 0))[0]) return w + } finally { + if (d[i] && d[i][0] && 1 == (w = P(e, d[i][0][0], d[i][0][1], [], h, p, null, 0))[0]) return w; + d[i] = 0, i-- + } + R += 2 * B - 2 + } else j < 7 ? (B = _(e, R), R += 2, m[S -= B] = 0 === B ? new m[S] : f(m[S], c(m.slice(S + 1, S + B + 1)))) : j < 9 && (w = m[S--], m[S] = m[S] & w); + else if (j < 2) + if ((j = z) > 12) m[++S] = s(e, R), R += 2; + else if (j > 10) w = m[S--], m[S] = m[S] << w; + else if (j > 8) { + for (B = x(e, R), j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); + R += 4, m[S] = m[S][j] + } else j > 6 && (A = m[S--], w = delete m[S--][A]); + else if (j < 3)(j = z) < 2 ? m[++S] = w : j < 11 ? (w = m[S -= 2][m[S + 1]] = m[S + 2], S--) : j < 13 && (w = m[S], m[++S] = w); + else if ((j = z) > 12) m[++S] = p; + else if (j > 5) w = m[S--], m[S] = m[S] !== w; + else if (j > 3) w = m[S--], m[S] = m[S] / w; + else if (j > 1) { + if ((B = o(e, R)) < 0) { + v = 1, M(e, b, 2 * a), R += 2 * B - 2; + break + } + R += 2 * B - 2 + } else j > -1 && (m[S] = !m[S]); + else if (j < 3) + if (j = 3 & z, z >>= 2, j < 1)(j = z) > 13 ? (m[++S] = o(e, R), R += 4) : j > 11 ? (w = m[S--], m[S] = m[S] >> w) : j > 9 ? (B = _(e, R), R += 2, w = m[S--], h[B] = w) : j > 7 ? (B = x(e, R), R += 4, A = S + 1, m[S -= B - 1] = B ? m.slice(S, A) : []) : j > 0 && (w = m[S--], m[S] = m[S] > w); + else if (j < 2)(j = z) > 12 ? (w = m[S - 1], A = m[S], m[++S] = w, m[++S] = A) : j > 3 ? (w = m[S--], m[S] = m[S] == w) : j > 1 ? (w = m[S--], m[S] = m[S] + w) : j > -1 && (m[++S] = u); + else if (j < 3) { + if ((j = z) > 13) m[++S] = !1; + else if (j > 6) w = m[S--], m[S] = m[S] instanceof w; + else if (j > 4) w = m[S--], m[S] = m[S] % w; + else if (j > 2) + if (m[S--]) R += 4; + else { + if ((B = o(e, R)) < 0) { + v = 1, M(e, b, 2 * a), R += 2 * B - 2; + break + } + R += 2 * B - 2 + } + else if (j > 0) { + for (B = x(e, R), w = "", k = r.q[B][0]; k < r.q[B][1]; k++) w += String.fromCharCode(t ^ r.p[k]); + m[++S] = w, R += 4 + } + } else(j = z) > 7 ? (w = m[S--], m[S] = m[S] | w) : j > 5 ? (B = _(e, R), R += 2, m[++S] = h["$" + B]) : j > 3 && (B = o(e, R), d[i][0] && !d[i][2] ? d[i][1] = [R + 4, B - 3] : d[i++] = [0, [R + 4, B - 3], 0], R += 2 * B - 2); + else if (j = 3 & z, z >>= 2, j > 2)(j = z) > 13 ? (m[++S] = l(e, R), R += 8) : j > 11 ? (w = m[S--], m[S] = m[S] >>> w) : j > 9 ? m[++S] = !0 : j > 7 ? (B = _(e, R), R += 2, m[S] = m[S][B]) : j > 0 && (w = m[S--], m[S] = m[S] < w); + else if (j > 1)(j = z) > 10 ? (B = o(e, R), d[++i] = [ + [R + 4, B - 3], 0, 0 + ], R += 2 * B - 2) : j > 8 ? (w = m[S--], m[S] = m[S] ^ w) : j > 6 && (w = m[S--]); + else if (j > 0) { + if ((j = z) > 7) w = m[S--], m[S] = m[S] in w; + else if (j > 5) m[S] = ++m[S]; + else if (j > 3) B = _(e, R), R += 2, w = h[B], m[++S] = w; + else if (j > 1) { + var O = 0, + U = m[S].length, + D = m[S]; + m[++S] = function() { + var e = O < U; + if (e) { + var b = D[O++]; + m[++S] = b + } + m[++S] = e + } + } + } else if ((j = z) > 13) w = m[S], m[S] = m[S - 1], m[S - 1] = w; + else if (j > 4) w = m[S--], m[S] = m[S] === w; + else if (j > 2) w = m[S--], m[S] = m[S] - w; + else if (j > 0) { + for (B = x(e, R), j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); + j = +j, R += 4, m[++S] = j + } + } + if (v) + for (; R < q;) + if (I = T[R], R += 2, j = 3 & (z = 13 * I % 241), z >>= 2, j > 2) + if (j = 3 & z, z >>= 2, j > 2)(j = z) < 2 ? (w = m[S--], m[S] = m[S] < w) : j < 9 ? (B = E[R], R += 2, m[S] = m[S][B]) : j < 11 ? m[++S] = !0 : j < 13 ? (w = m[S--], m[S] = m[S] >>> w) : j < 15 && (m[++S] = E[R], R += 8); + else if (j > 1)(j = z) < 6 || (j < 8 ? w = m[S--] : j < 10 ? (w = m[S--], m[S] = m[S] ^ w) : j < 12 && (B = E[R], d[++i] = [ + [R + 4, B - 3], 0, 0 + ], R += 2 * B - 2)); + else if (j > 0)(j = z) > 7 ? (w = m[S--], m[S] = m[S] in w) : j > 5 ? m[S] = ++m[S] : j > 3 ? (B = E[R], R += 2, w = h[B], m[++S] = w) : j > 1 && (O = 0, U = m[S].length, D = m[S], m[++S] = function() { + var e = O < U; + if (e) { + var b = D[O++]; + m[++S] = b + } + m[++S] = e + }); + else if ((j = z) < 2) { + for (B = E[R], j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); + j = +j, R += 4, m[++S] = j + } else j < 4 ? (w = m[S--], m[S] = m[S] - w) : j < 6 ? (w = m[S--], m[S] = m[S] === w) : j < 15 && (w = m[S], m[S] = m[S - 1], m[S - 1] = w); + else if (j > 1) + if (j = 3 & z, z >>= 2, j < 1)(j = z) > 13 ? (m[++S] = E[R], R += 4) : j > 11 ? (w = m[S--], m[S] = m[S] >> w) : j > 9 ? (B = E[R], R += 2, w = m[S--], h[B] = w) : j > 7 ? (B = E[R], R += 4, A = S + 1, m[S -= B - 1] = B ? m.slice(S, A) : []) : j > 0 && (w = m[S--], m[S] = m[S] > w); + else if (j < 2)(j = z) < 1 ? m[++S] = u : j < 3 ? (w = m[S--], m[S] = m[S] + w) : j < 5 ? (w = m[S--], m[S] = m[S] == w) : j < 14 && (w = m[S - 1], A = m[S], m[++S] = w, m[++S] = A); + else if (j < 3) { + if ((j = z) > 13) m[++S] = !1; + else if (j > 6) w = m[S--], m[S] = m[S] instanceof w; + else if (j > 4) w = m[S--], m[S] = m[S] % w; + else if (j > 2) m[S--] ? R += 4 : R += 2 * (B = E[R]) - 2; + else if (j > 0) { + for (B = E[R], w = "", k = r.q[B][0]; k < r.q[B][1]; k++) w += String.fromCharCode(t ^ r.p[k]); + m[++S] = w, R += 4 + } + } else(j = z) > 7 ? (w = m[S--], m[S] = m[S] | w) : j > 5 ? (B = E[R], R += 2, m[++S] = h["$" + B]) : j > 3 && (B = E[R], d[i][0] && !d[i][2] ? d[i][1] = [R + 4, B - 3] : d[i++] = [0, [R + 4, B - 3], 0], R += 2 * B - 2); + else if (j > 0) + if (j = 3 & z, z >>= 2, j < 1) { + if ((j = z) > 9); + else if (j > 7) w = m[S--], m[S] = m[S] & w; + else if (j > 5) B = E[R], R += 2, m[S -= B] = 0 === B ? new m[S] : f(m[S], c(m.slice(S + 1, S + B + 1))); + else if (j > 3) { + B = E[R]; + try { + if (d[i][2] = 1, 1 == (w = P(e, R + 4, B - 3, [], h, p, null, 0))[0]) return w + } catch (b) { + if (d[i] && d[i][1] && 1 == (w = P(e, d[i][1][0], d[i][1][1], [], h, p, b, 0))[0]) return w + } finally { + if (d[i] && d[i][0] && 1 == (w = P(e, d[i][0][0], d[i][0][1], [], h, p, null, 0))[0]) return w; + d[i] = 0, i-- + } + R += 2 * B - 2 + } + } else if (j < 2) + if ((j = z) < 8) A = m[S--], w = delete m[S--][A]; + else if (j < 10) { + for (B = E[R], j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); + R += 4, m[S] = m[S][j] + } else j < 12 ? (w = m[S--], m[S] = m[S] << w) : j < 14 && (m[++S] = E[R], R += 2); + else j < 3 ? (j = z) < 2 ? m[++S] = w : j < 11 ? (w = m[S -= 2][m[S + 1]] = m[S + 2], S--) : j < 13 && (w = m[S], m[++S] = w) : (j = z) > 12 ? m[++S] = p : j > 5 ? (w = m[S--], m[S] = m[S] !== w) : j > 3 ? (w = m[S--], m[S] = m[S] / w) : j > 1 ? R += 2 * (B = E[R]) - 2 : j > -1 && (m[S] = !m[S]); + else if (j = 3 & z, z >>= 2, j < 1) { + if ((j = z) < 1) return [1, m[S--]]; + j < 5 ? (w = m[S--], m[S] = m[S] * w) : j < 7 ? (w = m[S--], m[S] = m[S] != w) : j < 14 ? (A = m[S--], C = m[S--], (j = m[S--]).x === P ? j.y >= 1 ? m[++S] = F(e, j.c, j.l, A, j.z, C, null, 1) : (m[++S] = F(e, j.c, j.l, A, j.z, C, null, 0), j.y++) : m[++S] = j.apply(C, A)) : j < 16 && (B = E[R], (g = function b() { + var a = arguments; + return b.y > 0 || b.y++, F(e, b.c, b.l, a, b.z, this, null, 0) + }).c = R + 4, g.l = B - 2, g.x = P, g.y = 0, g.z = h, m[S] = g, R += 2 * B - 2) + } else if (j < 2)(j = z) > 8 ? (w = m[S--], m[S] = typeof w) : j > 4 ? m[S -= 1] = m[S][m[S + 1]] : j > 2 && (A = m[S--], (j = m[S]).x === P ? j.y >= 1 ? m[S] = F(e, j.c, j.l, [A], j.z, C, null, 1) : (m[S] = F(e, j.c, j.l, [A], j.z, C, null, 0), j.y++) : m[S] = j(A)); + else if (j < 3) { + if ((j = z) < 9) { + for (w = m[S--], B = E[R], j = "", k = r.q[B][0]; k < r.q[B][1]; k++) j += String.fromCharCode(t ^ r.p[k]); + R += 4, m[S--][j] = w + } else if (j < 13) throw m[S--] + } else(j = z) < 1 ? m[++S] = null : j < 3 ? (w = m[S--], m[S] = m[S] >= w) : j < 12 && (m[++S] = void 0); + return [0, null] + } + + function F(e, b, a, f, c, r, t, d) { + null == r && (r = this), c && !c.d && (c.d = 0, c.$0 = c, c[1] = {}); + var i, n, s = {}, + o = s.d = c ? c.d + 1 : 0; + for (s["$" + o] = s, n = 0; n < o; n++) s[i = "$" + n] = c[i]; + for (n = 0, o = s.length = f.length; n < o; n++) s[n] = f[n]; + return d && !T[b] && M(e, b, 2 * a), T[b] ? P(e, b, a, 0, s, r, null, 1)[1] : P(e, b, a, 0, s, r, null, 0)[1] + } +}; +var _0x397dc7 = "undefined" != typeof globalThis ? globalThis : void 0 !== window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : {}, + _0x124d1a = _0x5cd844(function(_0x770f81) { + ! function() { + var _0x250d36 = "input is invalid type", + _0x4cfaee = !1, + _0x1702f9 = {}, + _0x5ccbb3 = !_0x4cfaee && "object" == typeof self, + _0x54d876 = !_0x1702f9.JS_MD5_NO_NODE_JS && "object" == typeof process && process.versions && process.versions.node, + _0x185caf; + _0x54d876 ? _0x1702f9 = _0x397dc7 : _0x5ccbb3 && (_0x1702f9 = self); + var _0x17dcbf = !_0x1702f9.JS_MD5_NO_COMMON_JS && _0x770f81.exports, + _0x554fed = !1, + _0x2de28f = !_0x1702f9.JS_MD5_NO_ARRAY_BUFFER && "undefined" != typeof ArrayBuffer, + _0x3a9a1b = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"], + _0x465562 = [128, 32768, 8388608, -2147483648], + _0x20b37e = [0, 8, 16, 24], + _0x323604 = ["hex", "array", "digest", "buffer", "arrayBuffer", "base64"], + _0x2c185e = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"], + _0x4b59e0 = []; + if (_0x2de28f) { + var _0x395837 = new ArrayBuffer(68); + _0x185caf = new Uint8Array(_0x395837), _0x4b59e0 = new Uint32Array(_0x395837) + }!_0x1702f9.JS_MD5_NO_NODE_JS && Array.isArray || (Array.isArray = function(e) { + return "[object Array]" === Object.prototype.toString.call(e) + }), _0x2de28f && (_0x1702f9.JS_MD5_NO_ARRAY_BUFFER_IS_VIEW || !ArrayBuffer.isView) && (ArrayBuffer.isView = function(e) { + return "object" == typeof e && e.buffer && e.buffer.constructor === ArrayBuffer + }); + var _0x4e9930 = function(e) { + return function(b) { + return new _0x5887c8(!0).update(b)[e]() + } + }, + _0x38ba77 = function() { + var e = _0x4e9930("hex"); + _0x54d876 && (e = _0x474989(e)), e.create = function() { + return new _0x5887c8 + }, e.update = function(b) { + return e.create().update(b) + }; + for (var b = 0; b < _0x323604.length; ++b) { + var a = _0x323604[b]; + e[a] = _0x4e9930(a) + } + return e + }, + _0x474989 = function(_0x57eeaa) { + var _0x114910, _0x226465 = eval("require('crypto');"), + _0x1f6ae0 = eval("require('buffer')['Buffer'];"); + return function(e) { + if ("string" == typeof e) return _0x226465.createHash("md5").update(e, "utf8").digest("hex"); + if (null == e) throw _0x250d36; + return e.constructor === ArrayBuffer && (e = new Uint8Array(e)), Array.isArray(e) || ArrayBuffer.isView(e) || e.constructor === _0x1f6ae0 ? _0x226465.createHash("md5").update(new _0x1f6ae0.from(e)).digest("hex") : _0x57eeaa(e) + } + }; + + function _0x5887c8(e) { + if (e) _0x4b59e0[0] = _0x4b59e0[16] = _0x4b59e0[1] = _0x4b59e0[2] = _0x4b59e0[3] = _0x4b59e0[4] = _0x4b59e0[5] = _0x4b59e0[6] = _0x4b59e0[7] = _0x4b59e0[8] = _0x4b59e0[9] = _0x4b59e0[10] = _0x4b59e0[11] = _0x4b59e0[12] = _0x4b59e0[13] = _0x4b59e0[14] = _0x4b59e0[15] = 0, this.blocks = _0x4b59e0, this.buffer8 = _0x185caf; + else if (_0x2de28f) { + var b = new ArrayBuffer(68); + this.buffer8 = new Uint8Array(b), this.blocks = new Uint32Array(b) + } else this.blocks = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + this.h0 = this.h1 = this.h2 = this.h3 = this.start = this.bytes = this.hBytes = 0, this.finalized = this.hashed = !1, this.first = !0 + } + _0x5887c8.prototype.update = function(e) { + if (!this.finalized) { + var b, a = typeof e; + if ("string" !== a) { + if ("object" !== a || null === e) throw _0x250d36; + if (_0x2de28f && e.constructor === ArrayBuffer) e = new Uint8Array(e); + else if (!(Array.isArray(e) || _0x2de28f && ArrayBuffer.isView(e))) throw _0x250d36; + b = !0 + } + for (var f, c, r = 0, t = e.length, d = this.blocks, i = this.buffer8; r < t;) { + if (this.hashed && (this.hashed = !1, d[0] = d[16], d[16] = d[1] = d[2] = d[3] = d[4] = d[5] = d[6] = d[7] = d[8] = d[9] = d[10] = d[11] = d[12] = d[13] = d[14] = d[15] = 0), b) + if (_0x2de28f) + for (c = this.start; r < t && c < 64; ++r) i[c++] = e[r]; + else + for (c = this.start; r < t && c < 64; ++r) d[c >> 2] |= e[r] << _0x20b37e[3 & c++]; + else if (_0x2de28f) + for (c = this.start; r < t && c < 64; ++r)(f = e.charCodeAt(r)) < 128 ? i[c++] = f : f < 2048 ? (i[c++] = 192 | f >> 6, i[c++] = 128 | 63 & f) : f < 55296 || f >= 57344 ? (i[c++] = 224 | f >> 12, i[c++] = 128 | f >> 6 & 63, i[c++] = 128 | 63 & f) : (f = 65536 + ((1023 & f) << 10 | 1023 & e.charCodeAt(++r)), i[c++] = 240 | f >> 18, i[c++] = 128 | f >> 12 & 63, i[c++] = 128 | f >> 6 & 63, i[c++] = 128 | 63 & f); + else + for (c = this.start; r < t && c < 64; ++r)(f = e.charCodeAt(r)) < 128 ? d[c >> 2] |= f << _0x20b37e[3 & c++] : f < 2048 ? (d[c >> 2] |= (192 | f >> 6) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | 63 & f) << _0x20b37e[3 & c++]) : f < 55296 || f >= 57344 ? (d[c >> 2] |= (224 | f >> 12) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | f >> 6 & 63) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | 63 & f) << _0x20b37e[3 & c++]) : (f = 65536 + ((1023 & f) << 10 | 1023 & e.charCodeAt(++r)), d[c >> 2] |= (240 | f >> 18) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | f >> 12 & 63) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | f >> 6 & 63) << _0x20b37e[3 & c++], d[c >> 2] |= (128 | 63 & f) << _0x20b37e[3 & c++]); + this.lastByteIndex = c, this.bytes += c - this.start, c >= 64 ? (this.start = c - 64, this.hash(), this.hashed = !0) : this.start = c + } + return this.bytes > 4294967295 && (this.hBytes += this.bytes / 4294967296 << 0, this.bytes = this.bytes % 4294967296), this + } + }, _0x5887c8.prototype.finalize = function() { + if (!this.finalized) { + this.finalized = !0; + var e = this.blocks, + b = this.lastByteIndex; + e[b >> 2] |= _0x465562[3 & b], b >= 56 && (this.hashed || this.hash(), e[0] = e[16], e[16] = e[1] = e[2] = e[3] = e[4] = e[5] = e[6] = e[7] = e[8] = e[9] = e[10] = e[11] = e[12] = e[13] = e[14] = e[15] = 0), e[14] = this.bytes << 3, e[15] = this.hBytes << 3 | this.bytes >>> 29, this.hash() + } + }, _0x5887c8.prototype.hash = function() { + var e, b, a, f, c, r, t = this.blocks; + this.first ? b = ((b = ((e = ((e = t[0] - 680876937) << 7 | e >>> 25) - 271733879 << 0) ^ (a = ((a = (-271733879 ^ (f = ((f = (-1732584194 ^ 2004318071 & e) + t[1] - 117830708) << 12 | f >>> 20) + e << 0) & (-271733879 ^ e)) + t[2] - 1126478375) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[3] - 1316259209) << 22 | b >>> 10) + a << 0 : (e = this.h0, b = this.h1, a = this.h2, b = ((b += ((e = ((e += ((f = this.h3) ^ b & (a ^ f)) + t[0] - 680876936) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[1] - 389564586) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[2] + 606105819) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[3] - 1044525330) << 22 | b >>> 10) + a << 0), b = ((b += ((e = ((e += (f ^ b & (a ^ f)) + t[4] - 176418897) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[5] + 1200080426) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[6] - 1473231341) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[7] - 45705983) << 22 | b >>> 10) + a << 0, b = ((b += ((e = ((e += (f ^ b & (a ^ f)) + t[8] + 1770035416) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[9] - 1958414417) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[10] - 42063) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[11] - 1990404162) << 22 | b >>> 10) + a << 0, b = ((b += ((e = ((e += (f ^ b & (a ^ f)) + t[12] + 1804603682) << 7 | e >>> 25) + b << 0) ^ (a = ((a += (b ^ (f = ((f += (a ^ e & (b ^ a)) + t[13] - 40341101) << 12 | f >>> 20) + e << 0) & (e ^ b)) + t[14] - 1502002290) << 17 | a >>> 15) + f << 0) & (f ^ e)) + t[15] + 1236535329) << 22 | b >>> 10) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[1] - 165796510) << 5 | e >>> 27) + b << 0) ^ b)) + t[6] - 1069501632) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[11] + 643717713) << 14 | a >>> 18) + f << 0) ^ f)) + t[0] - 373897302) << 20 | b >>> 12) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[5] - 701558691) << 5 | e >>> 27) + b << 0) ^ b)) + t[10] + 38016083) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[15] - 660478335) << 14 | a >>> 18) + f << 0) ^ f)) + t[4] - 405537848) << 20 | b >>> 12) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[9] + 568446438) << 5 | e >>> 27) + b << 0) ^ b)) + t[14] - 1019803690) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[3] - 187363961) << 14 | a >>> 18) + f << 0) ^ f)) + t[8] + 1163531501) << 20 | b >>> 12) + a << 0, b = ((b += ((f = ((f += (b ^ a & ((e = ((e += (a ^ f & (b ^ a)) + t[13] - 1444681467) << 5 | e >>> 27) + b << 0) ^ b)) + t[2] - 51403784) << 9 | f >>> 23) + e << 0) ^ e & ((a = ((a += (e ^ b & (f ^ e)) + t[7] + 1735328473) << 14 | a >>> 18) + f << 0) ^ f)) + t[12] - 1926607734) << 20 | b >>> 12) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[5] - 378558) << 4 | e >>> 28) + b << 0)) + t[8] - 2022574463) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[11] + 1839030562) << 16 | a >>> 16) + f << 0)) + t[14] - 35309556) << 23 | b >>> 9) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[1] - 1530992060) << 4 | e >>> 28) + b << 0)) + t[4] + 1272893353) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[7] - 155497632) << 16 | a >>> 16) + f << 0)) + t[10] - 1094730640) << 23 | b >>> 9) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[13] + 681279174) << 4 | e >>> 28) + b << 0)) + t[0] - 358537222) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[3] - 722521979) << 16 | a >>> 16) + f << 0)) + t[6] + 76029189) << 23 | b >>> 9) + a << 0, b = ((b += ((r = (f = ((f += ((c = b ^ a) ^ (e = ((e += (c ^ f) + t[9] - 640364487) << 4 | e >>> 28) + b << 0)) + t[12] - 421815835) << 11 | f >>> 21) + e << 0) ^ e) ^ (a = ((a += (r ^ b) + t[15] + 530742520) << 16 | a >>> 16) + f << 0)) + t[2] - 995338651) << 23 | b >>> 9) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[0] - 198630844) << 6 | e >>> 26) + b << 0) | ~a)) + t[7] + 1126891415) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[14] - 1416354905) << 15 | a >>> 17) + f << 0) | ~e)) + t[5] - 57434055) << 21 | b >>> 11) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[12] + 1700485571) << 6 | e >>> 26) + b << 0) | ~a)) + t[3] - 1894986606) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[10] - 1051523) << 15 | a >>> 17) + f << 0) | ~e)) + t[1] - 2054922799) << 21 | b >>> 11) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[8] + 1873313359) << 6 | e >>> 26) + b << 0) | ~a)) + t[15] - 30611744) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[6] - 1560198380) << 15 | a >>> 17) + f << 0) | ~e)) + t[13] + 1309151649) << 21 | b >>> 11) + a << 0, b = ((b += ((f = ((f += (b ^ ((e = ((e += (a ^ (b | ~f)) + t[4] - 145523070) << 6 | e >>> 26) + b << 0) | ~a)) + t[11] - 1120210379) << 10 | f >>> 22) + e << 0) ^ ((a = ((a += (e ^ (f | ~b)) + t[2] + 718787259) << 15 | a >>> 17) + f << 0) | ~e)) + t[9] - 343485551) << 21 | b >>> 11) + a << 0, this.first ? (this.h0 = e + 1732584193 << 0, this.h1 = b - 271733879 << 0, this.h2 = a - 1732584194 << 0, this.h3 = f + 271733878 << 0, this.first = !1) : (this.h0 = this.h0 + e << 0, this.h1 = this.h1 + b << 0, this.h2 = this.h2 + a << 0, this.h3 = this.h3 + f << 0) + }, _0x5887c8.prototype.hex = function() { + this.finalize(); + var e = this.h0, + b = this.h1, + a = this.h2, + f = this.h3; + return _0x3a9a1b[e >> 4 & 15] + _0x3a9a1b[15 & e] + _0x3a9a1b[e >> 12 & 15] + _0x3a9a1b[e >> 8 & 15] + _0x3a9a1b[e >> 20 & 15] + _0x3a9a1b[e >> 16 & 15] + _0x3a9a1b[e >> 28 & 15] + _0x3a9a1b[e >> 24 & 15] + _0x3a9a1b[b >> 4 & 15] + _0x3a9a1b[15 & b] + _0x3a9a1b[b >> 12 & 15] + _0x3a9a1b[b >> 8 & 15] + _0x3a9a1b[b >> 20 & 15] + _0x3a9a1b[b >> 16 & 15] + _0x3a9a1b[b >> 28 & 15] + _0x3a9a1b[b >> 24 & 15] + _0x3a9a1b[a >> 4 & 15] + _0x3a9a1b[15 & a] + _0x3a9a1b[a >> 12 & 15] + _0x3a9a1b[a >> 8 & 15] + _0x3a9a1b[a >> 20 & 15] + _0x3a9a1b[a >> 16 & 15] + _0x3a9a1b[a >> 28 & 15] + _0x3a9a1b[a >> 24 & 15] + _0x3a9a1b[f >> 4 & 15] + _0x3a9a1b[15 & f] + _0x3a9a1b[f >> 12 & 15] + _0x3a9a1b[f >> 8 & 15] + _0x3a9a1b[f >> 20 & 15] + _0x3a9a1b[f >> 16 & 15] + _0x3a9a1b[f >> 28 & 15] + _0x3a9a1b[f >> 24 & 15] + }, _0x5887c8.prototype.toString = _0x5887c8.prototype.hex, _0x5887c8.prototype.digest = function() { + this.finalize(); + var e = this.h0, + b = this.h1, + a = this.h2, + f = this.h3; + return [255 & e, e >> 8 & 255, e >> 16 & 255, e >> 24 & 255, 255 & b, b >> 8 & 255, b >> 16 & 255, b >> 24 & 255, 255 & a, a >> 8 & 255, a >> 16 & 255, a >> 24 & 255, 255 & f, f >> 8 & 255, f >> 16 & 255, f >> 24 & 255] + }, _0x5887c8.prototype.array = _0x5887c8.prototype.digest, _0x5887c8.prototype.arrayBuffer = function() { + this.finalize(); + var e = new ArrayBuffer(16), + b = new Uint32Array(e); + return b[0] = this.h0, b[1] = this.h1, b[2] = this.h2, b[3] = this.h3, e + }, _0x5887c8.prototype.buffer = _0x5887c8.prototype.arrayBuffer, _0x5887c8.prototype.base64 = function() { + for (var e, b, a, f = "", c = this.array(), r = 0; r < 15;) e = c[r++], b = c[r++], a = c[r++], f += _0x2c185e[e >>> 2] + _0x2c185e[63 & (e << 4 | b >>> 4)] + _0x2c185e[63 & (b << 2 | a >>> 6)] + _0x2c185e[63 & a]; + return f + (_0x2c185e[(e = c[r]) >>> 2] + _0x2c185e[e << 4 & 63] + "==") + }; + var _0x4dd781 = _0x38ba77(); + _0x17dcbf ? _0x770f81.exports = _0x4dd781 : (_0x1702f9.md5 = _0x4dd781, _0x554fed && (void 0)(function() { + return _0x4dd781 + })) + }() + }); + +function _0x178cef(e) { + return jsvmp("484e4f4a403f52430038001eab0015840e8ee21a00000000000000621b000200001d000146000306000e271f001b000200021d00010500121b001b000b021b000b04041d0001071b000b0500000003000126207575757575757575757575757575757575757575757575757575757575757575", [, , void 0 !== _0x124d1a ? _0x124d1a : void 0, _0x178cef, e]) +} +for (var _0xb55f3e = { + boe: !1, + aid: 0, + dfp: !1, + sdi: !1, + enablePathList: [], + _enablePathListRegex: [], + urlRewriteRules: [], + _urlRewriteRules: [], + initialized: !1, + enableTrack: !1, + track: { + unitTime: 0, + unitAmount: 0, + fre: 0 + }, + triggerUnload: !1, + region: "", + regionConf: {}, + umode: 0, + v: !1, + perf: !1, + xxbg: !0 + }, _0x3eaf64 = { + debug: function(e, b) { + let a = !1; + a = !1 + } + }, _0x233455 = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"], _0x2e9f6d = [], _0x511f86 = [], _0x3d35de = 0; _0x3d35de < 256; _0x3d35de++) _0x2e9f6d[_0x3d35de] = _0x233455[_0x3d35de >> 4 & 15] + _0x233455[15 & _0x3d35de], _0x3d35de < 16 && (_0x3d35de < 10 ? _0x511f86[48 + _0x3d35de] = _0x3d35de : _0x511f86[87 + _0x3d35de] = _0x3d35de); +var _0x2ce54d = function(e) { + for (var b = e.length, a = "", f = 0; f < b;) a += _0x2e9f6d[e[f++]]; + return a + }, + _0x5960a2 = function(e) { + for (var b = e.length >> 1, a = b << 1, f = new Uint8Array(b), c = 0, r = 0; r < a;) f[c++] = _0x511f86[e.charCodeAt(r++)] << 4 | _0x511f86[e.charCodeAt(r++)]; + return f + }, + _0x4e46b6 = { + encode: _0x2ce54d, + decode: _0x5960a2 + }; + +function sign(e, b) { + return jsvmp("484e4f4a403f5243001f240fbf2031ccf317480300000000000007181b0002012e1d00921b000b171b000b02402217000a1c1b000b1726402217000c1c1b000b170200004017002646000306000e271f001b000200021d00920500121b001b000b031b000b17041d0092071b000b041e012f17000d1b000b05260a0000101c1b000b06260a0000101c1b001b000b071e01301d00931b001b000b081e00081d00941b0048021d00951b001b000b1b1d00961b0048401d009e1b001b000b031b000b16041d009f1b001b000b09221e0131241b000b031b000b09221e0131241b000b1e0a000110040a0001101d00d51b001b000b09221e0131241b000b031b000b09221e0131241b000b180a000110040a0001101d00d71b001b000b0a1e00101d00d91b001b000b0b261b000b1a1b000b190a0002101d00db1b001b000b0c261b000b221b000b210a0002101d00dc1b001b000b0d261b000b230200200a0002101d00dd1b001b000b09221e0131241b000b031b000b24040a0001101d00df1b001b000b0e1a00221e00de240a0000104903e82b1d00e31b001b000b0f260a0000101d00e41b001b000b1d1d00e71b001b000b1a4901002b1d00e81b001b000b1a4901002c1d00ea1b001b000b191d00f21b001b000b1f480e191d00f81b001b000b1f480f191d00f91b001b000b20480e191d00fb1b001b000b20480f191d00fe1b001b000b25480e191d01001b001b000b25480f191d01011b001b000b264818344900ff2f1d01031b001b000b264810344900ff2f1d01321b001b000b264808344900ff2f1d01331b001b000b264800344900ff2f1d01341b001b000b274818344900ff2f1d01351b001b000b274810344900ff2f1d01361b001b000b274808344900ff2f1d01371b001b000b274800344900ff2f1d01381b001b000b281b000b29311b000b2a311b000b2b311b000b2c311b000b2d311b000b2e311b000b2f311b000b30311b000b31311b000b32311b000b33311b000b34311b000b35311b000b36311b000b37311b000b38311b000b39311d01391b004900ff1d013a1b001b000b10261b000b281b000b2a1b000b2c1b000b2e1b000b301b000b321b000b341b000b361b000b381b000b3a1b000b291b000b2b1b000b2d1b000b2f1b000b311b000b331b000b351b000b371b000b390a0013101d013b1b001b000b0c261b000b111b000b3b041b000b3c0a0002101d013c1b001b000b12261b000b1c1b000b3b1b000b3d0a0003101d013d1b001b000b13261b000b3e0200240a0002101d013e1b000b3f0000013f000126207575757575757575757575757575757575757575757575757575757575757575012b0e7776757a7d7643617c637661676a027a77065c717976706708777671667474766107767d65707c77760374766707707c7d607c7f7607757a61767166740a7c66677661447a77677b0a7a7d7d7661447a77677b0b7c666776615b767a747b670b7a7d7d76615b767a747b6709666076615274767d670b677c5f7c64766150726076077a7d77766b5c7508767f767067617c7d09667d7776757a7d76770963617c677c676a637608677c4067617a7d740470727f7f0763617c7076606010487c71797670673363617c707660604e067c717976706705677a677f76047d7c7776012e0125012402602341525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a383c2e0260224157787763747b2749586042512b233c5e75656420254b5a22412126384446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e0260214157787763747b2749586042512b233c5e75656420254b5a224121263e4446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e02602041525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e4c2e012a022222067f767d74677b0a707b7261507c7776526702222306707b726152670f487c717976706733447a7d777c644e08577c70667e767d6712487c7179767067335d72657a7472677c614e057960777c7e10487c7179767067335b7a60677c616a4e07637f66747a7d60084c637b727d677c7e0b70727f7f437b727d677c7e0b4c4c7d7a747b677e726176055266777a7c1850727d65726041767d7776617a7d74507c7d67766b6721570964767177617a657661137476675c647d43617c637661676a5d727e7660097f727d74667274766006707b617c7e760761667d677a7e7607707c7d7d767067144c4c64767177617a6576614c7665727f66726776134c4c60767f767d7a667e4c7665727f667267761b4c4c64767177617a6576614c6070617a63674c75667d70677a7c7d174c4c64767177617a6576614c6070617a63674c75667d70154c4c64767177617a6576614c6070617a63674c757d134c4c756b77617a6576614c7665727f66726776124c4c77617a6576614c667d64617263637677154c4c64767177617a6576614c667d64617263637677114c4c77617a6576614c7665727f66726776144c4c60767f767d7a667e4c667d64617263637677144c4c756b77617a6576614c667d64617263637677094c60767f767d7a667e0c70727f7f40767f767d7a667e164c40767f767d7a667e4c5a57564c4176707c6177766108777c70667e767d670478766a60057e7267707b06417674566b630a4f3748723e694e77704c067072707b764c04607c7e7608707675407b72616308507675407b72616305767c72637a16767c44767151617c64607661577a60637267707b76610f717a7d775c717976706752606a7d700e7a60565c44767151617c646076610120047c63767d0467766067097a7d707c747d7a677c077c7d7661617c6104707c77761242465c47524c564b5056565756574c5641410e607660607a7c7d40677c61727476076076675a67767e10607c7e7658766a5b766176516a6776770a61767e7c65765a67767e097a7d77766b767757510c437c7a7d6776615665767d670e5e40437c7a7d6776615665767d670d706176726776567f767e767d670670727d65726009677c5772677246415f076176637f727076034f603901740a7d72677a6576707c777614487c717976706733437f66747a7d526161726a4e4a4d7b676763602c294f3c4f3c3b48233e2a4e68223f206e3b4f3d48233e2a4e68223f206e3a68206e6f48723e75233e2a4e68223f276e3b2948723e75233e2a4e68223f276e3a68246e3a0127087f7c7072677a7c7d047b61767504757a7f76107b676763293c3c7f7c70727f7b7c606708637f7267757c617e02222102222007647a7d777c646002222703647a7d02222607727d77617c7a77022225057f7a7d666b022224067a637b7c7d7602222b047a63727702222a047a637c77022123037e7270022122097e72707a7d677c607b0c7e72704c637c64766163703a0470617c60036b22220570617a7c6005756b7a7c6004637a787602212102212002212702212602212502212402212b08757a6176757c6b3c067c637661723c05337c63613c05337c63673c07707b617c7e763c0867617a77767d673c047e607a7602212a0220230665767d777c6106547c7c747f760e4c637261727e40647a67707b5c7d0a777a61767067407a747d0a707c7d607a6067767d670660647a67707b03777c7e07637b727d677c7e047b7c7c7840525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e3d03727a77017d01750161096067726167477a7e7601670972717a7f7a677a76600a677a7e766067727e6322137b72617764726176507c7d70666161767d706a0c7776657a70765e767e7c616a087f727d74667274760a6176607c7f66677a7c7d0f7265727a7f4176607c7f66677a7c7d0960706176767d477c630a60706176767d5f767567107776657a7076437a6b767f4172677a7c0a63617c77667067406671077172676776616a016309677c66707b5a7d757c08677a7e76697c7d760a677a7e766067727e6321077463665a7d757c0b7960557c7d67605f7a60670b637f66747a7d605f7a60670a677a7e766067727e63200a76657661507c7c787a760767674c60707a77017e0b606a7d67726b5661617c610c7d72677a65765f767d74677b056167705a43097563457661607a7c7d0b4c4c657661607a7c7d4c4c08707f7a767d675a770a677a7e766067727e63270b766b67767d77557a767f77046366607b03727f7f04677b767d097172607625274c707b0c75617c7e507b7261507c7776067125274c2023022022087172607625274c23022021087172607625274c22022020087172607625274c2102202702202602202507747667477a7e760220240b777c7e5d7c6745727f7a77096066716067617a7d740863617c677c707c7f02202b02202a01230e222323232323232322222323232302272302272207757c616176727f02272104717c776a096067617a7d747a756a02686e0b717c776a45727f216067610a717c776a4c7b72607b2e01350366617f02272005626676616a0a72607c7f774c607a747d096372677b7d727e762e0967674c6476717a772e063566667a772e0227270227260e4c716a6776774c6076704c777a770227250a27212a272a2524212a25097576457661607a7c7d0227240e4c232151274925647c232323232202272b02272a05607f7a7076022623074056505a5d555c037d7c6409677a7e766067727e6305757f7c7c610661727d777c7e0f7476674747447671507c7c787a7660056767647a770867674c6476717a770767674476715a770b67674c6476717a774c65210967674476717a7745210761667d7d7a7d7405757f66607b087e7c65765f7a60670660637f7a70760671765e7c657609707f7a70785f7a6067077176507f7a70780c78766a717c7261775f7a60670a717658766a717c7261770b7270677a657640677267760b647a7d777c6440677267760360477e05676172707808667d7a67477a7e76037270700a667d7a67527e7c667d670871767b72657a7c61077e6074476a637603645a5707727a775f7a60670b63617a6572706a5e7c777606706660677c7e067260607a747d0f4456514c5756455a50564c5a5d555c0479607c7d0a6176747a7c7d507c7d75096176637c616746617f04766b7a67094b3e5e403e404746510c4b3e5e403e43524a5f5c525720232323232323232323232323232323232323232323232323232323232323232320772722772b70772a2b75232371212327762a2b23232a2a2b7670752b272124760165066671707c7776067776707c777602262202262102262002262702262602262502262402262b02262a022523022522022521022520", [, , void 0, void 0 !== _0x178cef ? _0x178cef : void 0, { + boe: !1, + aid: 0, + dfp: !1, + sdi: !1, + enablePathList: [], + _enablePathListRegex: [/\/web\/report/], + urlRewriteRules: [], + _urlRewriteRules: [], + initialized: !1, + enableTrack: !1, + track: { + unitTime: 0, + unitAmount: 0, + fre: 0 + }, + triggerUnload: !1, + region: "", + regionConf: {}, + umode: 0, + v: !1, + perf: !1, + xxbg: !0 + }, () => 0, () => "03v", { + ubcode: 0 + }, { + bogusIndex: 0, + msNewTokenList: [], + moveList: [], + clickList: [], + keyboardList: [], + activeState: [], + aidList: [], + envcode: 0, + msToken: "", + msStatus: 0, + __ac_testid: "", + ttwid: "", + tt_webid: "", + tt_webid_v2: "" + }, void 0 !== _0x4e46b6 ? _0x4e46b6 : void 0, { + userAgent: b + }, (e, b) => { + let a = new Uint8Array(3); + return a[0] = e / 256, a[1] = e % 256, a[2] = b % 256, String.fromCharCode.apply(null, a) + }, (e, b) => { + let a, f = [], + c = 0, + r = ""; + for (let e = 0; e < 256; e++) f[e] = e; + for (let b = 0; b < 256; b++) c = (c + f[b] + e.charCodeAt(b % e.length)) % 256, a = f[b], f[b] = f[c], f[c] = a; + let t = 0; + c = 0; + for (let e = 0; e < b.length; e++) c = (c + f[t = (t + 1) % 256]) % 256, a = f[t], f[t] = f[c], f[c] = a, r += String.fromCharCode(b.charCodeAt(e) ^ f[(f[t] + f[c]) % 256]); + return r + }, (e, b) => jsvmp("484e4f4a403f524300281018f7b851f02d296e5b00000000000004a21b0002001d1d001e1b00131e00061a001d001f1b000b070200200200210d1b000b070200220200230d1b000b070200240200250d1b000b070200260200270d1b001b000b071b000b05191d00031b000200001d00281b0048001d00291b000b041e002a1b000b0b4803283b1700f11b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f480833301b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b08221e002d241b000b0a490fc02f4806340a000110281d00281b00220b091b000b08221e002d241b000b0a483f2f0a000110281d002816ff031b000b041e002a1b000b0b294800391700e01b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b041e002a1b000b0b3917001e1b000b04221e002b241b000b0b0a0001104900ff2f4808331600054800301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b041e002a1b000b0b3917001e1b000b08221e002d241b000b0a490fc02f4806340a0001101600071b000b06281d00281b00220b091b000b06281d00281b000b090000002e000126207575757575757575757575757575757575757575757575757575757575757575012b0e7776757a7d7643617c637661676a027a77065c717976706708777671667474766107767d65707c77760374766707707c7d607c7f7607757a61767166740a7c66677661447a77677b0a7a7d7d7661447a77677b0b7c666776615b767a747b670b7a7d7d76615b767a747b6709666076615274767d670b677c5f7c64766150726076077a7d77766b5c7508767f767067617c7d09667d7776757a7d76770963617c677c676a637608677c4067617a7d740470727f7f0763617c7076606010487c71797670673363617c707660604e067c717976706705677a677f76047d7c7776012e0125012402602341525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a383c2e0260224157787763747b2749586042512b233c5e75656420254b5a22412126384446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e0260214157787763747b2749586042512b233c5e75656420254b5a224121263e4446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e02602041525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e4c2e012a022222067f767d74677b0a707b7261507c7776526702222306707b72615267", [, , , , e, b]), "undefined" != typeof Date ? Date : void 0, () => 0, (e, b, a, f, c, r, t, d, i, n, s, o, l, _, x, u, h, p, y) => { + let v = new Uint8Array(19); + return v[0] = e, v[1] = s, v[2] = b, v[3] = o, v[4] = a, v[5] = l, v[6] = f, v[7] = _, v[8] = c, v[9] = x, v[10] = r, v[11] = u, v[12] = t, v[13] = h, v[14] = d, v[15] = p, v[16] = i, v[17] = y, v[18] = n, String.fromCharCode.apply(null, v) + }, e => String.fromCharCode(e), (e, b, a) => String.fromCharCode(e) + String.fromCharCode(b) + a, (e, b) => jsvmp("484e4f4a403f524300281018f7b851f02d296e5b00000000000004a21b0002001d1d001e1b00131e00061a001d001f1b000b070200200200210d1b000b070200220200230d1b000b070200240200250d1b000b070200260200270d1b001b000b071b000b05191d00031b000200001d00281b0048001d00291b000b041e002a1b000b0b4803283b1700f11b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f480833301b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b08221e002d241b000b0a490fc02f4806340a000110281d00281b00220b091b000b08221e002d241b000b0a483f2f0a000110281d002816ff031b000b041e002a1b000b0b294800391700e01b001b000b04221e002b241b001e0029222d1b00241d00290a0001104900ff2f4810331b000b041e002a1b000b0b3917001e1b000b04221e002b241b000b0b0a0001104900ff2f4808331600054800301d002c1b00220b091b000b08221e002d241b000b0a4a00fc00002f4812340a000110281d00281b00220b091b000b08221e002d241b000b0a4a0003f0002f480c340a000110281d00281b00220b091b000b041e002a1b000b0b3917001e1b000b08221e002d241b000b0a490fc02f4806340a0001101600071b000b06281d00281b00220b091b000b06281d00281b000b090000002e000126207575757575757575757575757575757575757575757575757575757575757575012b0e7776757a7d7643617c637661676a027a77065c717976706708777671667474766107767d65707c77760374766707707c7d607c7f7607757a61767166740a7c66677661447a77677b0a7a7d7d7661447a77677b0b7c666776615b767a747b670b7a7d7d76615b767a747b6709666076615274767d670b677c5f7c64766150726076077a7d77766b5c7508767f767067617c7d09667d7776757a7d76770963617c677c676a637608677c4067617a7d740470727f7f0763617c7076606010487c71797670673363617c707660604e067c717976706705677a677f76047d7c7776012e0125012402602341525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a383c2e0260224157787763747b2749586042512b233c5e75656420254b5a22412126384446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e0260214157787763747b2749586042512b233c5e75656420254b5a224121263e4446527f567a245d5f717c624a475c4366697e5579597d616a6b2a5b45547072406750762e02602041525150575655545b5a59585f5e5d5c43424140474645444b4a49727170777675747b7a79787f7e7d7c63626160676665646b6a6923222120272625242b2a3e4c2e012a022222067f767d74677b0a707b7261507c7776526702222306707b72615267", [, , , , e, b]), , sign, e, void 0]) +} + +module.exports = { + sign + }; \ No newline at end of file diff --git a/list b/list new file mode 100644 index 0000000..98dd15b --- /dev/null +++ b/list @@ -0,0 +1,11 @@ +https://live.douyin.com/525514386431 +https://live.douyin.com/8687122573 +https://live.douyin.com/642534242822 +https://live.douyin.com/589037028237 +https://live.douyin.com/743565594721 +https://live.douyin.com/249578288248 +https://live.douyin.com/472140253414 +https://live.douyin.com/700846653732 +https://live.douyin.com/81849868631 + +https://live.douyin.com/642534242822 \ No newline at end of file diff --git a/live.py b/live.py new file mode 100644 index 0000000..eafe091 --- /dev/null +++ b/live.py @@ -0,0 +1,226 @@ +import os +import subprocess +import threading +import asyncio +import uuid +import time +import signal +import sys +import logging +import platform +import aiohttp +import spider +import stream + +# ----------------- Logging Setup ----------------- +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + datefmt="%H:%M:%S" +) + +IS_WINDOWS = platform.system() == "Windows" + +# ----------------- Douyin Recorder ----------------- +class DouyinRecorder: + def __init__(self, audio_bitrate=128, max_minutes=8): + self.record_proc = None + self.stop_flag = threading.Event() + self.audio_bitrate = audio_bitrate + self.start_time = None + self.room_name = None + self.out_file = None + self.temp_file = None + self.max_minutes = max_minutes + os.makedirs("records", exist_ok=True) + + # ---- 构建 FFmpeg 命令 ---- + def _build_record_cmd(self, real_url): + ts_name = f"{self.room_name}_{int(time.time())}.ts" + self.temp_file = os.path.join("records", ts_name).replace("\\", "/") + + # 竖屏裁 16:9 + 模糊背景 + vf_filter = ( + "split [a][b];" + "[a] scale=-1:720 [main];" + "[b] scale=1280:720, boxblur=luma_radius=20:luma_power=1:chroma_radius=20:chroma_power=1 [bg];" + "[bg][main] overlay=(W-w)/2:(H-h)/2" + ) + + cmd = [ + "ffmpeg", "-y", "-i", real_url, + "-c:v", "libx264", "-preset", "veryfast", "-tune", "zerolatency", + "-vf", vf_filter, + "-c:a", "aac", "-b:a", f"{self.audio_bitrate}k", + "-ar", "44100", "-ac", "2", + "-f", "mpegts", + self.temp_file + ] + return cmd + + # ---- 开始录制 ---- + def start_record(self, real_url, room_name=None): + self.room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + logging.info(f"[{self.room_name}] 启动本地录制") + threading.Thread(target=self._run_record_loop, args=(real_url,), daemon=True).start() + threading.Thread(target=self._monitor_thread, daemon=True).start() + + def _start_new_record(self, real_url): + record_cmd = self._build_record_cmd(real_url) + self.record_proc = subprocess.Popen( + record_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) + logging.info(f"[{self.room_name}] 本地录制开始: {self.temp_file}") + + def monitor_proc(proc): + try: + for line in iter(proc.stderr.readline, b""): + if self.stop_flag.is_set(): + break + if line: + decoded = line.decode("utf-8", errors="ignore").strip() + logging.debug(f"[{self.room_name} record]: {decoded}") + except Exception as e: + logging.error(f"[{self.room_name} record stderr异常: {e}") + + threading.Thread(target=monitor_proc, args=(self.record_proc,), daemon=True).start() + + # ---- 录制循环 ---- + def _run_record_loop(self, real_url): + while not self.stop_flag.is_set(): + try: + self.start_time = time.time() + self._start_new_record(real_url) + + while not self.stop_flag.is_set(): + elapsed = time.time() - self.start_time + if elapsed >= self.max_minutes * 60: + logging.info(f"[{self.room_name}] 达到 {self.max_minutes} 分钟,切分录制段") + self._finalize_recording() + self.start_time = time.time() + self._start_new_record(real_url) + if self.record_proc.poll() is not None: + logging.warning(f"[{self.room_name}] FFmpeg 进程退出,1秒后重启") + time.sleep(1) + self._start_new_record(real_url) + time.sleep(2) + except Exception as e: + logging.error(f"[{self.room_name}] FFmpeg异常: {e}") + time.sleep(1) + + # ---- 停止 FFmpeg ---- + def _terminate_proc(self): + if self.record_proc and self.record_proc.poll() is None: + self.record_proc.terminate() + try: + self.record_proc.wait(timeout=2) + except subprocess.TimeoutExpired: + self.record_proc.kill() + self.record_proc = None + + # ---- 监控线程显示 ---- + def _monitor_thread(self): + while not self.stop_flag.is_set(): + uptime = int(time.time() - self.start_time) if self.start_time else 0 + m, s = divmod(uptime, 60) + status = "录制中" if self.record_proc and self.record_proc.poll() is None else "启动中" + print(f"\r[{self.room_name}] {status} | 运行时间: {m:02d}:{s:02d} | 音频码率: {self.audio_bitrate}k", end="") + time.sleep(2) + + # ---- 停止 ---- + def stop(self): + logging.info(f"[{self.room_name}] 停止录制...") + self.stop_flag.set() + self._terminate_proc() + self._finalize_recording() + + # ---- TS → MP4 ---- + def _finalize_recording(self): + if self.temp_file and os.path.exists(self.temp_file): + mp4_name = self.temp_file.replace(".ts", f".mp4") + logging.info(f"[{self.room_name}] 转 MP4: {mp4_name}") + cmd = [ + "ffmpeg", "-y", "-i", self.temp_file, + "-c", "copy", "-movflags", "+faststart", + mp4_name + ] + try: + subprocess.run(cmd, check=True) + size_mb = os.path.getsize(mp4_name) / (1024 * 1024) + logging.info(f"[{self.room_name}] MP4 转换完成 | 大小: {size_mb:.2f} MB") + except Exception as e: + logging.error(f"[{self.room_name}] MP4 转换失败: {e}") + finally: + try: + os.remove(self.temp_file) + except Exception: + pass + +# ----------------- Douyin 流解析 ----------------- +async def get_douyin_real_url(record_url, record_quality="原画", proxy_addr=None, cookies=''): + headers = { + "User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36"), + "Referer": "https://www.douyin.com/", + "Origin": "https://www.douyin.com", + "Accept": "application/json, text/javascript, */*; q=0.01", + } + session_args = {"headers": headers} + if proxy_addr: + session_args["proxy"] = proxy_addr + + async with aiohttp.ClientSession(**session_args) as session: + for attempt in range(3): + try: + async with session.get(record_url, cookies=cookies) as resp: + text = await resp.text() + if not text or "captcha" in text.lower(): + logging.warning(f"第{attempt+1}次尝试获取流失败,返回空或验证码,重试...") + await asyncio.sleep(1) + continue + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data( + url=record_url, proxy_addr=proxy_addr, cookies=cookies) + else: + json_data = await spider.get_douyin_app_stream_data( + url=record_url, proxy_addr=proxy_addr, cookies=cookies) + port_info = await stream.get_douyin_stream_url(json_data, record_quality, proxy_addr) + if port_info and port_info.get("record_url"): + return port_info.get("record_url") + logging.warning(f"第{attempt+1}次尝试解析失败,port_info为空") + await asyncio.sleep(1) + except Exception as e: + logging.warning(f"第{attempt+1}次尝试抓取抖音流异常: {e}") + await asyncio.sleep(1) + logging.error("多次尝试后仍获取抖音真实流失败") + return None + +def start_douyin_record(record_url, room_name=None, proxy_addr=None, cookies='', audio_bitrate=128): + real_url = asyncio.run(get_douyin_real_url(record_url, proxy_addr=proxy_addr, cookies=cookies)) + if not real_url: + logging.error("获取真实直播流失败") + return None + logging.info(f"实际直播流: {real_url}") + recorder = DouyinRecorder(audio_bitrate=audio_bitrate) + recorder.start_record(real_url, room_name) + return recorder + +# ----------------- Main ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/846050151095" + recorder_instance = start_douyin_record(DOUYIN_URL) + if not recorder_instance: + logging.error("录制实例创建失败,程序退出") + sys.exit(1) + + def signal_handler(sig, frame): + if recorder_instance: + recorder_instance.stop() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + logging.info("程序已启动,本地录制中,按 Ctrl+C 停止...") + while True: + time.sleep(60) diff --git a/liveback b/liveback new file mode 100644 index 0000000..182a582 --- /dev/null +++ b/liveback @@ -0,0 +1,109 @@ +import os +import subprocess +import asyncio +import uuid +import signal +import sys +import spider +import stream +import threading +import time + +class HLS2YouTube: + def __init__(self): + self.processes = {} + self.stop_flag = threading.Event() + + def start_stream(self, real_url, youtube_key, room_name=None): + room_name = room_name or f"抖音_{str(uuid.uuid4())[:8]}" + print(f"[{room_name}] 启动推流,目标 YouTube Key: {youtube_key}") + threading.Thread(target=self._stream_thread, args=(real_url, youtube_key, room_name), daemon=True).start() + + def _stream_thread(self, real_url, youtube_key, room_name): + while not self.stop_flag.is_set(): + cmd = [ + "ffmpeg", + "-rw_timeout", "50000000", + "-user_agent", "Mozilla/5.0", + "-headers", "Referer: https://www.douyin.com/", + "-fflags", "+nobuffer+genpts", + "-flags", "low_delay", + "-reconnect", "1", + "-reconnect_at_eof", "1", + "-reconnect_streamed", "1", + "-reconnect_delay_max", "10", + "-i", real_url, + "-c:v", "libx264", + "-preset", "veryfast", + "-tune", "zerolatency", + "-b:v", "3000k", + "-maxrate", "3000k", + "-bufsize", "6000k", + "-r", "30", + "-g", "60", + "-c:a", "aac", + "-b:a", "128k", + "-f", "flv", + f"rtmp://a.rtmp.youtube.com/live2/{youtube_key}" + ] + try: + proc = subprocess.Popen(cmd) + self.processes[room_name] = proc + while proc.poll() is None and not self.stop_flag.is_set(): + time.sleep(1) + if proc.poll() is None: + proc.terminate() + print(f"[{room_name}] 推流中断,5秒后重连") + time.sleep(5) + except Exception as e: + print(f"[{room_name}] 推流异常: {e}, 5秒后重试") + time.sleep(5) + + def stop_all(self): + print("停止所有推流...") + self.stop_flag.set() + for room_name, proc in self.processes.items(): + if proc.poll() is None: + proc.terminate() + +# ----------------- 抖音解析逻辑 ----------------- +async def get_douyin_real_url(record_url, record_quality="原画"): + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = await spider.get_douyin_stream_data(url=record_url) + else: + json_data = await spider.get_douyin_app_stream_data(url=record_url) + port_info = await stream.get_douyin_stream_url(json_data, record_quality, None) + return port_info.get("record_url") + +# ----------------- 启动推流 ----------------- +def start_douyin_to_youtube(record_url, youtube_key, room_name=None): + try: + real_url = asyncio.run(get_douyin_real_url(record_url)) + except Exception as e: + print(f"获取真实直播流失败: {e}") + return + if not real_url: + print("获取真实直播流失败") + return + print(f"实际直播流: {real_url}") + hls2yt = HLS2YouTube() + hls2yt.start_stream(real_url, youtube_key, room_name) + return hls2yt + +# ----------------- 示例 ----------------- +if __name__ == "__main__": + DOUYIN_URL = "https://live.douyin.com/104914538985" # 抖音直播 URL + YOUTUBE_KEY = "qxvb-r47b-r5ju-6ud3-6k7z" # YouTube 直播 Key + + hls_instance = start_douyin_to_youtube(DOUYIN_URL, YOUTUBE_KEY) + + def signal_handler(sig, frame): + if hls_instance: + hls_instance.stop_all() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + while True: + time.sleep(60) diff --git a/logger.py b/logger.py new file mode 100644 index 0000000..93faeae --- /dev/null +++ b/logger.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +import os +import sys +from loguru import logger + +logger.remove() + +custom_format = "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} - {message}" + +logger.add( + sink=sys.stderr, + format=custom_format, + level="DEBUG", + colorize=True, + enqueue=True +) + +script_path = os.path.split(os.path.realpath(sys.argv[0]))[0] + +logger.add( + f"{script_path}/logs/streamget.log", + level="DEBUG", + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}", + filter=lambda i: i["level"].name != "INFO", + serialize=False, + enqueue=True, + retention=1, + rotation="300 KB", + encoding='utf-8' +) + +logger.add( + f"{script_path}/logs/PlayURL.log", + level="INFO", + format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {message}", + filter=lambda i: i["level"].name == "INFO", + serialize=False, + enqueue=True, + retention=1, + rotation="300 KB", + encoding='utf-8' +) diff --git a/logs/PlayURL.log b/logs/PlayURL.log new file mode 100644 index 0000000..e69de29 diff --git a/logs/streamget.log b/logs/streamget.log new file mode 100644 index 0000000..e69de29 diff --git a/main.py b/main.py new file mode 100644 index 0000000..9eb42ec --- /dev/null +++ b/main.py @@ -0,0 +1,2078 @@ +# -*- encoding: utf-8 -*- + +""" +Author: Hmily +GitHub: https://github.com/ihmily +Date: 2023-07-17 23:52:05 +Update: 2025-07-19 17:43:00 +Copyright (c) 2023-2025 by Hmily, All Rights Reserved. +Function: Record live stream video. +""" +import asyncio +import os +import sys +import builtins +import subprocess +import signal +import threading +import time +import datetime +import re +import shutil +import random +import uuid +from pathlib import Path +import urllib.parse +import urllib.request +from urllib.error import URLError, HTTPError +from typing import Any +import configparser +from src import spider, stream +from src.proxy import ProxyDetector +from src.utils import logger +from src import utils +from msg_push import ( + dingtalk, xizhi, tg_bot, send_email, bark, ntfy, pushplus +) +from ffmpeg_install import ( + check_ffmpeg, ffmpeg_path, current_env_path +) + +from dy2ytlive import start_record_and_push +import json +# 读取 YouTube key +with open("config.json", "r", encoding="utf-8") as f: + config = json.load(f) +youtube_key = config.get("youtube_key") + +version = "v4.0.6" +platforms = ("\n国内站点:抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo|blued|网易CC|千度热播|猫耳FM|Look|TwitCasting|百度|微博|" + "酷狗|花椒|流星|Acfun|畅聊|映客|音播|知乎|嗨秀|VV星球|17Live|浪Live|漂漂|六间房|乐嗨|花猫|淘宝|京东|咪咕|连接|来秀" + "\n海外站点:TikTok|SOOP|PandaTV|WinkTV|FlexTV|PopkonTV|TwitchTV|LiveMe|ShowRoom|CHZZK|Shopee|" + "Youtube|Faceit|Picarto") + +recording = set() +error_count = 0 +pre_max_request = 10 +max_request_lock = threading.Lock() +error_window = [] +error_window_size = 10 +error_threshold = 5 +monitoring = 0 +running_list = [] +url_tuples_list = [] +url_comments = [] +text_no_repeat_url = [] +create_var = locals() +first_start = True +exit_recording = False +need_update_line_list = [] +first_run = True +not_record_list = [] +start_display_time = datetime.datetime.now() +global_proxy = False +recording_time_list = {} +script_path = os.path.split(os.path.realpath(sys.argv[0]))[0] +config_file = f'{script_path}/config/config.ini' +url_config_file = f'{script_path}/config/URL_config.ini' +backup_dir = f'{script_path}/backup_config' +text_encoding = 'utf-8-sig' +rstr = r"[\/\\\:\*\??\"\<\>\|&#.。,, ~!· ]" +default_path = f'{script_path}/downloads' +os.makedirs(default_path, exist_ok=True) +file_update_lock = threading.Lock() +os_type = os.name +clear_command = "cls" if os_type == 'nt' else "clear" +color_obj = utils.Color() +os.environ['PATH'] = ffmpeg_path + os.pathsep + current_env_path + + +def signal_handler(_signal, _frame): + sys.exit(0) + + +signal.signal(signal.SIGTERM, signal_handler) + + +def display_info() -> None: + global start_display_time + time.sleep(5) + while True: + try: + sys.stdout.flush() + time.sleep(5) + if Path(sys.executable).name != 'pythonw.exe': + os.system(clear_command) + print(f"\r共监测{monitoring}个直播中", end=" | ") + print(f"同一时间访问网络的线程数: {max_request}", end=" | ") + print(f"是否开启代理录制: {'是' if use_proxy else '否'}", end=" | ") + if split_video_by_time: + print(f"录制分段开启: {split_time}秒", end=" | ") + else: + print("录制分段开启: 否", end=" | ") + if create_time_file: + print("是否生成时间文件: 是", end=" | ") + print(f"录制视频质量为: {video_record_quality}", end=" | ") + print(f"录制视频格式为: {video_save_type}", end=" | ") + print(f"目前瞬时错误数为: {error_count}", end=" | ") + now = time.strftime("%H:%M:%S", time.localtime()) + print(f"当前时间: {now}") + + if len(recording) == 0: + time.sleep(5) + if monitoring == 0: + print("\r没有正在监测和录制的直播") + else: + print(f"\r没有正在录制的直播 循环监测间隔时间:{delay_default}秒") + else: + now_time = datetime.datetime.now() + print("x" * 60) + no_repeat_recording = list(set(recording)) + print(f"正在录制{len(no_repeat_recording)}个直播: ") + for recording_live in no_repeat_recording: + rt, qa = recording_time_list[recording_live] + have_record_time = now_time - rt + print(f"{recording_live}[{qa}] 正在录制中 {str(have_record_time).split('.')[0]}") + + # print('\n本软件已运行:'+str(now_time - start_display_time).split('.')[0]) + print("x" * 60) + start_display_time = now_time + except Exception as e: + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + + +def update_file(file_path: str, old_str: str, new_str: str, start_str: str = None) -> str | None: + if old_str == new_str and start_str is None: + return old_str + with file_update_lock: + file_data = [] + with open(file_path, "r", encoding=text_encoding) as f: + try: + for text_line in f: + if old_str in text_line: + text_line = text_line.replace(old_str, new_str) + if start_str: + text_line = f'{start_str}{text_line}' + if text_line not in file_data: + file_data.append(text_line) + except RuntimeError as e: + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + if ini_URL_content: + with open(file_path, "w", encoding=text_encoding) as f2: + f2.write(ini_URL_content) + return old_str + if file_data: + with open(file_path, "w", encoding=text_encoding) as f: + f.write(''.join(file_data)) + return new_str + + +def delete_line(file_path: str, del_line: str, delete_all: bool = False) -> None: + with file_update_lock: + with open(file_path, 'r+', encoding=text_encoding) as f: + lines = f.readlines() + f.seek(0) + f.truncate() + skip_line = False + for txt_line in lines: + if del_line in txt_line: + if delete_all or not skip_line: + skip_line = True + continue + else: + skip_line = False + f.write(txt_line) + + +def get_startup_info(system_type: str): + if system_type == 'nt': + startup_info = subprocess.STARTUPINFO() + startup_info.dwFlags |= subprocess.STARTF_USESHOWWINDOW + else: + startup_info = None + return startup_info + + +def segment_video(converts_file_path: str, segment_save_file_path: str, segment_format: str, segment_time: str, + is_original_delete: bool = True) -> None: + try: + if os.path.exists(converts_file_path) and os.path.getsize(converts_file_path) > 0: + ffmpeg_command = [ + "ffmpeg", + "-i", converts_file_path, + "-c:v", "copy", + "-c:a", "copy", + "-map", "0", + "-f", "segment", + "-segment_time", segment_time, + "-segment_format", segment_format, + "-reset_timestamps", "1", + "-movflags", "+frag_keyframe+empty_moov", + segment_save_file_path, + ] + _output = subprocess.check_output( + ffmpeg_command, stderr=subprocess.STDOUT, startupinfo=get_startup_info(os_type) + ) + if is_original_delete: + time.sleep(1) + if os.path.exists(converts_file_path): + os.remove(converts_file_path) + except subprocess.CalledProcessError as e: + logger.error(f'Error occurred during conversion: {e}') + except Exception as e: + logger.error(f'An unknown error occurred: {e}') + + +def converts_mp4(converts_file_path: str, is_original_delete: bool = True) -> None: + try: + if os.path.exists(converts_file_path) and os.path.getsize(converts_file_path) > 0: + if converts_to_h264: + color_obj.print_colored("正在转码为MP4格式并重新编码为h264\n", color_obj.YELLOW) + ffmpeg_command = [ + "ffmpeg", "-i", converts_file_path, + "-c:v", "libx264", + "-preset", "veryfast", + "-crf", "23", + "-vf", "format=yuv420p", + "-c:a", "copy", + "-f", "mp4", converts_file_path.rsplit('.', maxsplit=1)[0] + ".mp4", + ] + else: + color_obj.print_colored("正在转码为MP4格式\n", color_obj.YELLOW) + ffmpeg_command = [ + "ffmpeg", "-i", converts_file_path, + "-c:v", "copy", + "-c:a", "copy", + "-f", "mp4", converts_file_path.rsplit('.', maxsplit=1)[0] + ".mp4", + ] + _output = subprocess.check_output( + ffmpeg_command, stderr=subprocess.STDOUT, startupinfo=get_startup_info(os_type) + ) + if is_original_delete: + time.sleep(1) + if os.path.exists(converts_file_path): + os.remove(converts_file_path) + except subprocess.CalledProcessError as e: + logger.error(f'Error occurred during conversion: {e}') + except Exception as e: + logger.error(f'An unknown error occurred: {e}') + + +def converts_m4a(converts_file_path: str, is_original_delete: bool = True) -> None: + try: + if os.path.exists(converts_file_path) and os.path.getsize(converts_file_path) > 0: + _output = subprocess.check_output([ + "ffmpeg", "-i", converts_file_path, + "-n", "-vn", + "-c:a", "aac", "-bsf:a", "aac_adtstoasc", "-ab", "320k", + converts_file_path.rsplit('.', maxsplit=1)[0] + ".m4a", + ], stderr=subprocess.STDOUT, startupinfo=get_startup_info(os_type)) + if is_original_delete: + time.sleep(1) + if os.path.exists(converts_file_path): + os.remove(converts_file_path) + except subprocess.CalledProcessError as e: + logger.error(f'Error occurred during conversion: {e}') + except Exception as e: + logger.error(f'An unknown error occurred: {e}') + + +def generate_subtitles(record_name: str, ass_filename: str, sub_format: str = 'srt') -> None: + index_time = 0 + today = datetime.datetime.now() + re_datatime = today.strftime('%Y-%m-%d %H:%M:%S') + + def transform_int_to_time(seconds: int) -> str: + m, s = divmod(seconds, 60) + h, m = divmod(m, 60) + return f"{h:02d}:{m:02d}:{s:02d}" + + while True: + index_time += 1 + txt = str(index_time) + "\n" + transform_int_to_time(index_time) + ',000 --> ' + transform_int_to_time( + index_time + 1) + ',000' + "\n" + str(re_datatime) + "\n\n" + + with open(f"{ass_filename}.{sub_format.lower()}", 'a', encoding=text_encoding) as f: + f.write(txt) + + if record_name not in recording: + return + time.sleep(1) + today = datetime.datetime.now() + re_datatime = today.strftime('%Y-%m-%d %H:%M:%S') + + +def adjust_max_request() -> None: + global max_request, error_count, pre_max_request, error_window + preset = max_request + + while True: + time.sleep(5) + with max_request_lock: + if error_window: + error_rate = sum(error_window) / len(error_window) + else: + error_rate = 0 + + if error_rate > error_threshold: + max_request = max(1, max_request - 1) + elif error_rate < error_threshold / 2 and max_request < preset: + max_request += 1 + else: + pass + + if pre_max_request != max_request: + pre_max_request = max_request + print(f"\r同一时间访问网络的线程数动态改为 {max_request}") + + error_window.append(error_count) + if len(error_window) > error_window_size: + error_window.pop(0) + error_count = 0 + + +def push_message(record_name: str, live_url: str, content: str) -> None: + msg_title = push_message_title.strip() or "直播间状态更新通知" + push_functions = { + '微信': lambda: xizhi(xizhi_api_url, msg_title, content), + '钉钉': lambda: dingtalk(dingtalk_api_url, content, dingtalk_phone_num, dingtalk_is_atall), + '邮箱': lambda: send_email( + email_host, login_email, email_password, sender_email, sender_name, + to_email, msg_title, content, smtp_port, open_smtp_ssl + ), + 'TG': lambda: tg_bot(tg_chat_id, tg_token, content), + 'BARK': lambda: bark( + bark_msg_api, title=msg_title, content=content, level=bark_msg_level, sound=bark_msg_ring + ), + 'NTFY': lambda: ntfy( + ntfy_api, title=msg_title, content=content, tags=ntfy_tags, action_url=live_url, email=ntfy_email + ), + 'PUSHPLUS': lambda: pushplus(pushplus_token, msg_title, content), + } + + for platform, func in push_functions.items(): + if platform in live_status_push.upper(): + try: + result = func() + print(f'提示信息:已经将[{record_name}]直播状态消息推送至你的{platform},' + f' 成功{len(result["success"])}, 失败{len(result["error"])}') + except Exception as e: + color_obj.print_colored(f"直播消息推送到{platform}失败: {e}", color_obj.RED) + + +def run_script(command: str) -> None: + try: + process = subprocess.Popen( + command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, startupinfo=get_startup_info(os_type) + ) + stdout, stderr = process.communicate() + stdout_decoded = stdout.decode('utf-8') + stderr_decoded = stderr.decode('utf-8') + if stdout_decoded.strip(): + print(stdout_decoded) + if stderr_decoded.strip(): + print(stderr_decoded) + except PermissionError as e: + logger.error(e) + logger.error('脚本无执行权限!, 若是Linux环境, 请先执行:chmod +x your_script.sh 授予脚本可执行权限') + except OSError as e: + logger.error(e) + logger.error('Please add `#!/bin/bash` at the beginning of your bash script file.') + + +def clear_record_info(record_name: str, record_url: str) -> None: + global monitoring + recording.discard(record_name) + if record_url in url_comments and record_url in running_list: + running_list.remove(record_url) + monitoring -= 1 + color_obj.print_colored(f"[{record_name}]已经从录制列表中移除\n", color_obj.YELLOW) + + +def check_subprocess(record_name: str, record_url: str, ffmpeg_command: list, save_type: str, + script_command: str | None = None) -> bool: + save_file_path = ffmpeg_command[-1] + process = subprocess.Popen( + ffmpeg_command, stdin=subprocess.PIPE, stderr=subprocess.STDOUT, startupinfo=get_startup_info(os_type) + ) + + subs_file_path = save_file_path.rsplit('.', maxsplit=1)[0] + subs_thread_name = f'subs_{Path(subs_file_path).name}' + if create_time_file and not split_video_by_time and '音频' not in save_type: + create_var[subs_thread_name] = threading.Thread( + target=generate_subtitles, args=(record_name, subs_file_path) + ) + create_var[subs_thread_name].daemon = True + create_var[subs_thread_name].start() + + while process.poll() is None: + if record_url in url_comments or exit_recording: + color_obj.print_colored(f"[{record_name}]录制时已被注释,本条线程将会退出", color_obj.YELLOW) + clear_record_info(record_name, record_url) + # process.terminate() + if os.name == 'nt': + if process.stdin: + process.stdin.write(b'q') + process.stdin.close() + else: + process.send_signal(signal.SIGINT) + process.wait() + return True + time.sleep(1) + + return_code = process.returncode + stop_time = time.strftime('%Y-%m-%d %H:%M:%S') + if return_code == 0: + if converts_to_mp4 and save_type == 'TS': + if split_video_by_time: + file_paths = utils.get_file_paths(os.path.dirname(save_file_path)) + prefix = os.path.basename(save_file_path).rsplit('_', maxsplit=1)[0] + for path in file_paths: + if prefix in path: + threading.Thread(target=converts_mp4, args=(path, delete_origin_file)).start() + else: + threading.Thread(target=converts_mp4, args=(save_file_path, delete_origin_file)).start() + print(f"\n{record_name} {stop_time} 直播录制完成\n") + + if script_command: + logger.debug("开始执行脚本命令!") + if "python" in script_command: + params = [ + f'--record_name "{record_name}"', + f'--save_file_path "{save_file_path}"', + f'--save_type {save_type}', + f'--split_video_by_time {split_video_by_time}', + f'--converts_to_mp4 {converts_to_mp4}', + ] + else: + params = [ + f'"{record_name.split(" ", maxsplit=1)[-1]}"', + f'"{save_file_path}"', + save_type, + f'split_video_by_time:{split_video_by_time}', + f'converts_to_mp4:{converts_to_mp4}' + ] + script_command = script_command.strip() + ' ' + ' '.join(params) + run_script(script_command) + logger.debug("脚本命令执行结束!") + + else: + color_obj.print_colored(f"\n{record_name} {stop_time} 直播录制出错,返回码: {return_code}\n", color_obj.RED) + + recording.discard(record_name) + return False + + +def clean_name(input_text): + cleaned_name = re.sub(rstr, "_", input_text.strip()).strip('_') + cleaned_name = cleaned_name.replace("(", "(").replace(")", ")") + if clean_emoji: + cleaned_name = utils.remove_emojis(cleaned_name, '_').strip('_') + return cleaned_name or '空白昵称' + + +def get_quality_code(qn): + QUALITY_MAPPING = { + "原画": "OD", + "蓝光": "BD", + "超清": "UHD", + "高清": "HD", + "标清": "SD", + "流畅": "LD" + } + return QUALITY_MAPPING.get(qn) + + +def start_record(url_data: tuple, count_variable: int = -1) -> None: + global error_count + + while True: + try: + record_finished = False + run_once = False + start_pushed = False + new_record_url = '' + count_time = time.time() + retry = 0 + record_quality_zh, record_url, anchor_name = url_data + record_quality = get_quality_code(record_quality_zh) + proxy_address = proxy_addr + platform = '未知平台' + live_domain = '/'.join(record_url.split('/')[0:3]) + + if proxy_addr: + proxy_address = None + for platform in enable_proxy_platform_list: + if platform and platform.strip() in record_url: + proxy_address = proxy_addr + break + + if not proxy_address: + if extra_enable_proxy_platform_list: + for pt in extra_enable_proxy_platform_list: + if pt and pt.strip() in record_url: + proxy_address = proxy_addr_bak or None + + # print(f'\r代理地址:{proxy_address}') + # print(f'\r全局代理:{global_proxy}') + while True: + try: + port_info = [] + if record_url.find("douyin.com/") > -1: + platform = '抖音直播' + with semaphore: + if 'v.douyin.com' not in record_url and '/user/' not in record_url: + json_data = asyncio.run(spider.get_douyin_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=dy_cookie)) + else: + json_data = asyncio.run(spider.get_douyin_app_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=dy_cookie)) + port_info = asyncio.run( + stream.get_douyin_stream_url(json_data, record_quality, proxy_address)) + + elif record_url.find("https://www.tiktok.com/") > -1: + platform = 'TikTok直播' + with semaphore: + if global_proxy or proxy_address: + json_data = asyncio.run(spider.get_tiktok_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=tiktok_cookie)) + port_info = asyncio.run( + stream.get_tiktok_stream_url(json_data, record_quality, proxy_address)) + else: + logger.error("错误信息: 网络异常,请检查网络是否能正常访问TikTok平台") + + elif record_url.find("https://live.kuaishou.com/") > -1: + platform = '快手直播' + with semaphore: + json_data = asyncio.run(spider.get_kuaishou_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=ks_cookie)) + port_info = asyncio.run(stream.get_kuaishou_stream_url(json_data, record_quality)) + + elif record_url.find("https://www.huya.com/") > -1: + platform = '虎牙直播' + with semaphore: + if record_quality not in ['OD', 'BD', 'UHD']: + json_data = asyncio.run(spider.get_huya_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=hy_cookie)) + port_info = asyncio.run(stream.get_huya_stream_url(json_data, record_quality)) + else: + port_info = asyncio.run(spider.get_huya_app_stream_url( + url=record_url, + proxy_addr=proxy_address, + cookies=hy_cookie + )) + + elif record_url.find("https://www.douyu.com/") > -1: + platform = '斗鱼直播' + with semaphore: + json_data = asyncio.run(spider.get_douyu_info_data( + url=record_url, proxy_addr=proxy_address, cookies=douyu_cookie)) + port_info = asyncio.run(stream.get_douyu_stream_url( + json_data, video_quality=record_quality, cookies=douyu_cookie, proxy_addr=proxy_address + )) + + elif record_url.find("https://www.yy.com/") > -1: + platform = 'YY直播' + with semaphore: + json_data = asyncio.run(spider.get_yy_stream_data( + url=record_url, proxy_addr=proxy_address, cookies=yy_cookie)) + port_info = asyncio.run(stream.get_yy_stream_url(json_data)) + + elif record_url.find("https://live.bilibili.com/") > -1: + platform = 'B站直播' + with semaphore: + json_data = asyncio.run(spider.get_bilibili_room_info( + url=record_url, proxy_addr=proxy_address, cookies=bili_cookie)) + port_info = asyncio.run(stream.get_bilibili_stream_url( + json_data, video_quality=record_quality, cookies=bili_cookie, proxy_addr=proxy_address)) + + elif record_url.find("http://xhslink.com/") > -1 or \ + record_url.find("https://www.xiaohongshu.com/") > -1: + platform = '小红书直播' + with semaphore: + port_info = asyncio.run(spider.get_xhs_stream_url( + record_url, proxy_addr=proxy_address, cookies=xhs_cookie)) + retry += 1 + + elif record_url.find("https://www.bigo.tv/") > -1 or record_url.find("slink.bigovideo.tv/") > -1: + platform = 'Bigo直播' + with semaphore: + port_info = asyncio.run(spider.get_bigo_stream_url( + record_url, proxy_addr=proxy_address, cookies=bigo_cookie)) + + elif record_url.find("https://app.blued.cn/") > -1: + platform = 'Blued直播' + with semaphore: + port_info = asyncio.run(spider.get_blued_stream_url( + record_url, proxy_addr=proxy_address, cookies=blued_cookie)) + + elif record_url.find("sooplive.co.kr/") > -1: + platform = 'SOOP' + with semaphore: + if global_proxy or proxy_address: + json_data = asyncio.run(spider.get_sooplive_stream_data( + url=record_url, proxy_addr=proxy_address, + cookies=sooplive_cookie, + username=sooplive_username, + password=sooplive_password + )) + if json_data and json_data.get('new_cookies'): + utils.update_config( + config_file, 'Cookie', 'sooplive_cookie', json_data['new_cookies'] + ) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True)) + else: + logger.error("错误信息: 网络异常,请检查本网络是否能正常访问SOOP平台") + + elif record_url.find("cc.163.com/") > -1: + platform = '网易CC直播' + with semaphore: + json_data = asyncio.run(spider.get_netease_stream_data( + url=record_url, cookies=netease_cookie)) + port_info = asyncio.run(stream.get_netease_stream_url(json_data, record_quality)) + + elif record_url.find("qiandurebo.com/") > -1: + platform = '千度热播' + with semaphore: + port_info = asyncio.run(spider.get_qiandurebo_stream_data( + url=record_url, proxy_addr=proxy_address, cookies=qiandurebo_cookie)) + + elif record_url.find("www.pandalive.co.kr/") > -1: + platform = 'PandaTV' + with semaphore: + if global_proxy or proxy_address: + json_data = asyncio.run(spider.get_pandatv_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=pandatv_cookie + )) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True)) + else: + logger.error("错误信息: 网络异常,请检查本网络是否能正常访问PandaTV直播平台") + + elif record_url.find("fm.missevan.com/") > -1: + platform = '猫耳FM直播' + with semaphore: + port_info = asyncio.run(spider.get_maoerfm_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=maoerfm_cookie)) + + elif record_url.find("www.winktv.co.kr/") > -1: + platform = 'WinkTV' + with semaphore: + if global_proxy or proxy_address: + json_data = asyncio.run(spider.get_winktv_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=winktv_cookie)) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True)) + else: + logger.error("错误信息: 网络异常,请检查本网络是否能正常访问WinkTV直播平台") + + elif record_url.find("www.flextv.co.kr/") > -1: + platform = 'FlexTV' + with semaphore: + if global_proxy or proxy_address: + json_data = asyncio.run(spider.get_flextv_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=flextv_cookie, + username=flextv_username, + password=flextv_password + )) + if json_data and json_data.get('new_cookies'): + utils.update_config( + config_file, 'Cookie', 'flextv_cookie', json_data['new_cookies'] + ) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True)) + else: + logger.error("错误信息: 网络异常,请检查本网络是否能正常访问FlexTV直播平台") + + elif record_url.find("look.163.com/") > -1: + platform = 'Look直播' + with semaphore: + port_info = asyncio.run(spider.get_looklive_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=look_cookie + )) + + elif record_url.find("www.popkontv.com/") > -1: + platform = 'PopkonTV' + with semaphore: + if global_proxy or proxy_address: + port_info = asyncio.run(spider.get_popkontv_stream_url( + url=record_url, + proxy_addr=proxy_address, + access_token=popkontv_access_token, + username=popkontv_username, + password=popkontv_password, + partner_code=popkontv_partner_code + )) + if port_info and port_info.get('new_token'): + utils.update_config( + file_path=config_file, section='Authorization', key='popkontv_token', + new_value=port_info['new_token'] + ) + + else: + logger.error("错误信息: 网络异常,请检查本网络是否能正常访问PopkonTV直播平台") + + elif record_url.find("twitcasting.tv/") > -1: + platform = 'TwitCasting' + with semaphore: + port_info = asyncio.run(spider.get_twitcasting_stream_url( + url=record_url, + proxy_addr=proxy_address, + cookies=twitcasting_cookie, + account_type=twitcasting_account_type, + username=twitcasting_username, + password=twitcasting_password + )) + if port_info and port_info.get('new_cookies'): + utils.update_config( + file_path=config_file, section='Cookie', key='twitcasting_cookie', + new_value=port_info['new_cookies'] + ) + + elif record_url.find("live.baidu.com/") > -1: + platform = '百度直播' + with semaphore: + json_data = asyncio.run(spider.get_baidu_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=baidu_cookie)) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality)) + + elif record_url.find("weibo.com/") > -1: + platform = '微博直播' + with semaphore: + json_data = asyncio.run(spider.get_weibo_stream_data( + url=record_url, proxy_addr=proxy_address, cookies=weibo_cookie)) + port_info = asyncio.run(stream.get_stream_url( + json_data, record_quality, hls_extra_key='m3u8_url')) + + elif record_url.find("kugou.com/") > -1: + platform = '酷狗直播' + with semaphore: + port_info = asyncio.run(spider.get_kugou_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=kugou_cookie)) + + elif record_url.find("www.twitch.tv/") > -1: + platform = 'TwitchTV' + with semaphore: + if global_proxy or proxy_address: + json_data = asyncio.run(spider.get_twitchtv_stream_data( + url=record_url, + proxy_addr=proxy_address, + cookies=twitch_cookie + )) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True)) + else: + logger.error("错误信息: 网络异常,请检查本网络是否能正常访问TwitchTV直播平台") + + elif record_url.find("www.liveme.com/") > -1: + if global_proxy or proxy_address: + platform = 'LiveMe' + with semaphore: + port_info = asyncio.run(spider.get_liveme_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=liveme_cookie)) + else: + logger.error("错误信息: 网络异常,请检查本网络是否能正常访问LiveMe直播平台") + + elif record_url.find("www.huajiao.com/") > -1: + platform = '花椒直播' + with semaphore: + port_info = asyncio.run(spider.get_huajiao_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=huajiao_cookie)) + + elif record_url.find("7u66.com/") > -1: + platform = '流星直播' + with semaphore: + port_info = asyncio.run(spider.get_liuxing_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=liuxing_cookie)) + + elif record_url.find("showroom-live.com/") > -1: + platform = 'ShowRoom' + with semaphore: + json_data = asyncio.run(spider.get_showroom_stream_data( + url=record_url, proxy_addr=proxy_address, cookies=showroom_cookie)) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True)) + + elif record_url.find("live.acfun.cn/") > -1 or record_url.find("m.acfun.cn/") > -1: + platform = 'Acfun' + with semaphore: + json_data = asyncio.run(spider.get_acfun_stream_data( + url=record_url, proxy_addr=proxy_address, cookies=acfun_cookie)) + port_info = asyncio.run(stream.get_stream_url( + json_data, record_quality, url_type='flv', flv_extra_key='url')) + + elif record_url.find("live.tlclw.com/") > -1: + platform = '畅聊直播' + with semaphore: + port_info = asyncio.run(spider.get_changliao_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=changliao_cookie)) + + elif record_url.find("ybw1666.com/") > -1: + platform = '音播直播' + with semaphore: + port_info = asyncio.run(spider.get_yinbo_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=yinbo_cookie)) + + elif record_url.find("www.inke.cn/") > -1: + platform = '映客直播' + with semaphore: + port_info = asyncio.run(spider.get_yingke_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=yingke_cookie)) + + elif record_url.find("www.zhihu.com/") > -1: + platform = '知乎直播' + with semaphore: + port_info = asyncio.run(spider.get_zhihu_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=zhihu_cookie)) + + elif record_url.find("chzzk.naver.com/") > -1: + platform = 'CHZZK' + with semaphore: + json_data = asyncio.run(spider.get_chzzk_stream_data( + url=record_url, proxy_addr=proxy_address, cookies=chzzk_cookie)) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True)) + + elif record_url.find("www.haixiutv.com/") > -1: + platform = '嗨秀直播' + with semaphore: + port_info = asyncio.run(spider.get_haixiu_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=haixiu_cookie)) + + elif record_url.find("vvxqiu.com/") > -1: + platform = 'VV星球' + with semaphore: + port_info = asyncio.run(spider.get_vvxqiu_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=vvxqiu_cookie)) + + elif record_url.find("17.live/") > -1: + platform = '17Live' + with semaphore: + port_info = asyncio.run(spider.get_17live_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=yiqilive_cookie)) + + elif record_url.find("www.lang.live/") > -1: + platform = '浪Live' + with semaphore: + port_info = asyncio.run(spider.get_langlive_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=langlive_cookie)) + + elif record_url.find("m.pp.weimipopo.com/") > -1: + platform = '漂漂直播' + with semaphore: + port_info = asyncio.run(spider.get_pplive_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=pplive_cookie)) + + elif record_url.find(".6.cn/") > -1: + platform = '六间房直播' + with semaphore: + port_info = asyncio.run(spider.get_6room_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=six_room_cookie)) + + elif record_url.find("lehaitv.com/") > -1: + platform = '乐嗨直播' + with semaphore: + port_info = asyncio.run(spider.get_haixiu_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=lehaitv_cookie)) + + elif record_url.find("h.catshow168.com/") > -1: + platform = '花猫直播' + with semaphore: + port_info = asyncio.run(spider.get_pplive_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=huamao_cookie)) + + elif record_url.find("live.shopee") > -1 or record_url.find("shp.ee/") > -1: + platform = 'shopee' + with semaphore: + port_info = asyncio.run(spider.get_shopee_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=shopee_cookie)) + if port_info.get('uid'): + new_record_url = record_url.split('?')[0] + '?' + str(port_info['uid']) + + elif record_url.find("www.youtube.com/") > -1 or record_url.find("youtu.be/") > -1: + platform = 'Youtube' + with semaphore: + json_data = asyncio.run(spider.get_youtube_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=youtube_cookie)) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True)) + + elif record_url.find("tb.cn") > -1: + platform = '淘宝直播' + with semaphore: + json_data = asyncio.run(spider.get_taobao_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=taobao_cookie)) + port_info = asyncio.run(stream.get_stream_url( + json_data, record_quality, + url_type='all', hls_extra_key='hlsUrl', flv_extra_key='flvUrl' + )) + + elif record_url.find("3.cn") > -1 or record_url.find("m.jd.com") > -1: + platform = '京东直播' + with semaphore: + port_info = asyncio.run(spider.get_jd_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=jd_cookie)) + + elif record_url.find("faceit.com/") > -1: + platform = 'faceit' + with semaphore: + if global_proxy or proxy_address: + with semaphore: + json_data = asyncio.run(spider.get_faceit_stream_data( + url=record_url, proxy_addr=proxy_address, cookies=faceit_cookie)) + port_info = asyncio.run(stream.get_stream_url(json_data, record_quality, spec=True)) + else: + logger.error("错误信息: 网络异常,请检查本网络是否能正常访问faceit直播平台") + + elif record_url.find("www.miguvideo.com") > -1 or record_url.find("m.miguvideo.com") > -1: + platform = '咪咕直播' + with semaphore: + port_info = asyncio.run(spider.get_migu_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=migu_cookie)) + + elif record_url.find("show.lailianjie.com") > -1: + platform = '连接直播' + with semaphore: + port_info = asyncio.run(spider.get_lianjie_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=lianjie_cookie)) + + elif record_url.find("www.imkktv.com") > -1: + platform = '来秀直播' + with semaphore: + port_info = asyncio.run(spider.get_laixiu_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=laixiu_cookie)) + + elif record_url.find("www.picarto.tv") > -1: + platform = 'Picarto' + with semaphore: + port_info = asyncio.run(spider.get_picarto_stream_url( + url=record_url, proxy_addr=proxy_address, cookies=picarto_cookie)) + + elif record_url.find(".m3u8") > -1 or record_url.find(".flv") > -1: + platform = '自定义录制直播' + port_info = { + "anchor_name": platform + '_' + str(uuid.uuid4())[:8], + "is_live": True, + "record_url": record_url, + } + if '.flv' in record_url: + port_info['flv_url'] = record_url + else: + port_info['m3u8_url'] = record_url + + else: + logger.error(f'{record_url} {platform}直播地址') + return + + if anchor_name: + if '主播:' in anchor_name: + anchor_split: list = anchor_name.split('主播:') + if len(anchor_split) > 1 and anchor_split[1].strip(): + anchor_name = anchor_split[1].strip() + else: + anchor_name = port_info.get("anchor_name", '') + else: + anchor_name = port_info.get("anchor_name", '') + + if not port_info.get("anchor_name", ''): + print(f'序号{count_variable} 网址内容获取失败,进行重试中...获取失败的地址是:{url_data}') + with max_request_lock: + error_count += 1 + error_window.append(1) + else: + anchor_name = clean_name(anchor_name) + record_name = f'序号{count_variable} {anchor_name}' + + if record_url in url_comments: + print(f"[{anchor_name}]已被注释,本条线程将会退出") + clear_record_info(record_name, record_url) + return + + if not url_data[-1] and run_once is False: + if new_record_url: + need_update_line_list.append( + f'{record_url}|{new_record_url},主播: {anchor_name.strip()}') + not_record_list.append(new_record_url) + else: + need_update_line_list.append(f'{record_url}|{record_url},主播: {anchor_name.strip()}') + run_once = True + + push_at = datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S') + if port_info['is_live'] is False: + print(f"\r{record_name} 等待直播... ") + + if start_pushed: + if over_show_push: + push_content = "直播间状态更新:[直播间名称] 直播已结束!时间:[时间]" + if over_push_message_text: + push_content = over_push_message_text + + push_content = (push_content.replace('[直播间名称]', record_name). + replace('[时间]', push_at)) + threading.Thread( + target=push_message, + args=(record_name, record_url, push_content.replace(r'\n', '\n')), + daemon=True + ).start() + start_pushed = False + + else: + content = f"\r{record_name} 正在直播中..." + print(content) + + if live_status_push and not start_pushed: + if begin_show_push: + push_content = "直播间状态更新:[直播间名称] 正在直播中,时间:[时间]" + if begin_push_message_text: + push_content = begin_push_message_text + + push_content = (push_content.replace('[直播间名称]', record_name). + replace('[时间]', push_at)) + threading.Thread( + target=push_message, + args=(record_name, record_url, push_content.replace(r'\n', '\n')), + daemon=True + ).start() + start_pushed = True + + if disable_record: + time.sleep(push_check_seconds) + continue + + real_url = port_info.get('record_url') + full_path = f'{default_path}/{platform}' + if real_url: + now = datetime.datetime.today().strftime("%Y-%m-%d_%H-%M-%S") + live_title = port_info.get('title') + title_in_name = '' + if live_title: + live_title = clean_name(live_title) + title_in_name = live_title + '_' if filename_by_title else '' + + try: + if len(video_save_path) > 0: + if not video_save_path.endswith(('/', '\\')): + full_path = f'{video_save_path}/{platform}' + else: + full_path = f'{video_save_path}{platform}' + + full_path = full_path.replace("\\", '/') + if folder_by_author: + full_path = f'{full_path}/{anchor_name}' + if folder_by_time: + full_path = f'{full_path}/{now[:10]}' + if folder_by_title and port_info.get('title'): + if folder_by_time: + full_path = f'{full_path}/{live_title}_{anchor_name}' + else: + full_path = f'{full_path}/{now[:10]}_{live_title}' + if not os.path.exists(full_path): + os.makedirs(full_path) + except Exception as e: + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + + if platform != '自定义录制直播': + if enable_https_recording and real_url.startswith("http://"): + real_url = real_url.replace("http://", "https://") + + http_record_list = ['shopee'] + if platform in http_record_list: + real_url = real_url.replace("https://", "http://") + + start_record_and_push(real_url, youtube_key) + + user_agent = ("Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (" + "KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile " + "Safari/537.36") + + rw_timeout = "15000000" + analyzeduration = "20000000" + probesize = "10000000" + bufsize = "8000k" + max_muxing_queue_size = "1024" + for pt_host in overseas_platform_host: + if pt_host in record_url: + rw_timeout = "50000000" + analyzeduration = "40000000" + probesize = "20000000" + bufsize = "15000k" + max_muxing_queue_size = "2048" + break + + ffmpeg_command = [ + 'ffmpeg', "-y", + "-v", "verbose", + "-rw_timeout", rw_timeout, + "-loglevel", "error", + "-hide_banner", + "-user_agent", user_agent, + "-protocol_whitelist", "rtmp,crypto,file,http,https,tcp,tls,udp,rtp,httpproxy", + "-thread_queue_size", "1024", + "-analyzeduration", analyzeduration, + "-probesize", probesize, + "-fflags", "+discardcorrupt", + "-re", "-i", real_url, + # 视频编码 & 音频编码 + # "-c:v", "libx264", "-preset", "veryfast", + # "-c:a", "aac", + + # 输出到 YouTube + # "-f", "flv", "rtmp://a.rtmp.youtube.com/live2/9km5-20gc-9db9-mp7p-ecsg", + "-bufsize", bufsize, + "-sn", "-dn", + "-reconnect_delay_max", "60", + "-reconnect_streamed", "-reconnect_at_eof", + "-max_muxing_queue_size", max_muxing_queue_size, + "-correct_ts_overflow", "1", + "-avoid_negative_ts", "1" + ] + + + + # ffmpeg_command = [ + # "ffmpeg", "-re", "-i", real_url, + + # # 视频编码 + # "-c:v", "libx264", + # "-preset", "veryfast", + # "-b:v", "5000k", + # "-maxrate", "5000k", + # "-bufsize", "10000k", + + # # 音频编码 + # "-c:a", "aac", + # "-b:a", "192k", + + # # 容错 & 时间戳修复 + # "-fflags", "+genpts+discardcorrupt", + # "-avoid_negative_ts", "1", + + # # HLS 重连 + # "-rw_timeout", "10000000", + # "-reconnect", "1", + # "-reconnect_at_eof", "1", + # "-reconnect_streamed", "1", + # "-thread_queue_size", "1024", + # "-analyzeduration", "1000000", + # "-probesize", "5000000", + + # # 输出到 YouTube + # "-f", "flv", + # "rtmp://a.rtmp.youtube.com/live2/9km5-20gc-9db9-mp7p-ecsg" + # ] + + + + + record_headers = { + 'PandaTV': 'origin:https://www.pandalive.co.kr', + 'WinkTV': 'origin:https://www.winktv.co.kr', + 'PopkonTV': 'origin:https://www.popkontv.com', + 'FlexTV': 'origin:https://www.flextv.co.kr', + '千度热播': 'referer:https://qiandurebo.com', + '17Live': 'referer:https://17.live/en/live/6302408', + '浪Live': 'referer:https://www.lang.live', + 'shopee': f'origin:{live_domain}', + 'Blued直播': 'referer:https://app.blued.cn' + } + + headers = record_headers.get(platform) + if headers: + ffmpeg_command.insert(11, "-headers") + ffmpeg_command.insert(12, headers) + + if proxy_address: + ffmpeg_command.insert(1, "-http_proxy") + ffmpeg_command.insert(2, proxy_address) + + recording.add(record_name) + start_record_time = datetime.datetime.now() + recording_time_list[record_name] = [start_record_time, record_quality_zh] + rec_info = f"\r{anchor_name} 准备开始录制视频: {full_path}" + if show_url: + re_plat = ('WinkTV', 'PandaTV', 'ShowRoom', 'CHZZK', 'Youtube') + if platform in re_plat: + logger.info(f"{platform} | {anchor_name} | 直播源地址: {port_info['m3u8_url']}") + else: + logger.info( + f"{platform} | {anchor_name} | 直播源地址: {real_url}") + + only_flv_record = False + only_flv_platform_list = ['shopee', '花椒直播'] + if platform in only_flv_platform_list: + logger.debug(f"提示: {platform} 将强制使用FLV格式录制") + only_flv_record = True + + only_audio_record = False + only_audio_platform_list = ['猫耳FM直播', 'Look直播'] + if platform in only_audio_platform_list: + only_audio_record = True + + if only_audio_record: + try: + now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) + extension = "mp3" if "m4a" not in video_save_type.lower () else "m4a" + name_format = "_%03d" if split_video_by_time else "" + save_file_path = (f"{full_path}/{anchor_name}_{title_in_name}{now}" + f"{name_format}.{extension}") + + if split_video_by_time: + print(f'\r{anchor_name} 准备开始录制音频: {save_file_path}') + + if "MP3" in video_save_type: + command = [ + "-map", "0:a", + "-c:a", "libmp3lame", + "-ab", "320k", + "-f", "segment", + "-segment_time", split_time, + "-reset_timestamps", "1", + save_file_path, + ] + else: + command = [ + "-map", "0:a", + "-c:a", "aac", + "-bsf:a", "aac_adtstoasc", + "-ab", "320k", + "-f", "segment", + "-segment_time", split_time, + "-segment_format", 'mpegts', + "-reset_timestamps", "1", + save_file_path, + ] + + else: + if "MP3" in video_save_type: + command = [ + "-map", "0:a", + "-c:a", "libmp3lame", + "-ab", "320k", + save_file_path, + ] + + else: + command = [ + "-map", "0:a", + "-c:a", "aac", + "-bsf:a", "aac_adtstoasc", + "-ab", "320k", + "-movflags", "+faststart", + save_file_path, + ] + + ffmpeg_command.extend(command) + comment_end = check_subprocess( + record_name, + record_url, + ffmpeg_command, + video_save_type, + custom_script + ) + if comment_end: + return + + except subprocess.CalledProcessError as e: + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + with max_request_lock: + error_count += 1 + error_window.append(1) + + if video_save_type == "FLV" or only_flv_record: + filename = anchor_name + f'_{title_in_name}' + now + '.flv' + save_file_path = f'{full_path}/{filename}' + print(f'{rec_info}/{filename}') + + subs_file_path = save_file_path.rsplit('.', maxsplit=1)[0] + subs_thread_name = f'subs_{Path(subs_file_path).name}' + if create_time_file: + create_var[subs_thread_name] = threading.Thread( + target=generate_subtitles, args=(record_name, subs_file_path) + ) + create_var[subs_thread_name].daemon = True + create_var[subs_thread_name].start() + + try: + flv_url = port_info.get('flv_url') + if flv_url: + _filepath, _ = urllib.request.urlretrieve(flv_url, save_file_path) + record_finished = True + recording.discard(record_name) + print( + f"\n{anchor_name} {time.strftime('%Y-%m-%d %H:%M:%S')} 直播录制完成\n") + else: + logger.debug("未找到FLV直播流,跳过录制") + except Exception as e: + clear_record_info(record_name, record_url) + color_obj.print_colored( + f"\n{anchor_name} {time.strftime('%Y-%m-%d %H:%M:%S')} 直播录制出错,请检查网络\n", + color_obj.RED) + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + with max_request_lock: + error_count += 1 + error_window.append(1) + + try: + if converts_to_mp4: + seg_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.mp4" + if split_video_by_time: + segment_video( + save_file_path, seg_file_path, + segment_format='mp4', segment_time=split_time, + is_original_delete=delete_origin_file + ) + else: + threading.Thread( + target=converts_mp4, + args=(save_file_path, delete_origin_file) + ).start() + + else: + seg_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.flv" + if split_video_by_time: + segment_video( + save_file_path, seg_file_path, + segment_format='flv', segment_time=split_time, + is_original_delete=delete_origin_file + ) + except Exception as e: + logger.error(f"转码失败: {e} ") + + elif video_save_type == "MKV": + filename = anchor_name + f'_{title_in_name}' + now + ".mkv" + print(f'{rec_info}/{filename}') + save_file_path = full_path + '/' + filename + + try: + if split_video_by_time: + now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) + save_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.mkv" + command = [ + "-flags", "global_header", + "-c:v", "copy", + "-c:a", "aac", + "-map", "0", + "-f", "segment", + "-segment_time", split_time, + "-segment_format", "matroska", + "-reset_timestamps", "1", + save_file_path, + ] + + else: + command = [ + "-flags", "global_header", + "-map", "0", + "-c:v", "copy", + "-c:a", "copy", + "-f", "matroska", + "{path}".format(path=save_file_path), + ] + ffmpeg_command.extend(command) + + comment_end = check_subprocess( + record_name, + record_url, + ffmpeg_command, + video_save_type, + custom_script + ) + if comment_end: + return + + except subprocess.CalledProcessError as e: + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + with max_request_lock: + error_count += 1 + error_window.append(1) + + elif video_save_type == "MP4": + filename = anchor_name + f'_{title_in_name}' + now + ".mp4" + print(f'{rec_info}/{filename}') + save_file_path = full_path + '/' + filename + + try: + if split_video_by_time: + now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) + save_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.mp4" + command = [ + "-c:v", "copy", + "-c:a", "aac", + "-map", "0", + "-f", "segment", + "-segment_time", split_time, + "-segment_format", "mp4", + "-reset_timestamps", "1", + "-movflags", "+frag_keyframe+empty_moov", + save_file_path, + ] + + else: + command = [ + "-map", "0", + "-c:v", "copy", + "-c:a", "copy", + "-f", "mp4", + save_file_path, + ] + + ffmpeg_command.extend(command) + comment_end = check_subprocess( + record_name, + record_url, + ffmpeg_command, + video_save_type, + custom_script + ) + if comment_end: + return + + except subprocess.CalledProcessError as e: + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + with max_request_lock: + error_count += 1 + error_window.append(1) + + else: + if split_video_by_time: + now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) + filename = anchor_name + f'_{title_in_name}' + now + ".ts" + print(f'{rec_info}/{filename}') + + try: + save_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.ts" + command = [ + "-c:v", "copy", + "-c:a", "copy", + "-map", "0", + "-f", "segment", + "-segment_time", split_time, + "-segment_format", 'mpegts', + "-reset_timestamps", "1", + save_file_path, + ] + + ffmpeg_command.extend(command) + comment_end = check_subprocess( + record_name, + record_url, + ffmpeg_command, + video_save_type, + custom_script + ) + if comment_end: + if converts_to_mp4: + file_paths = utils.get_file_paths(os.path.dirname(save_file_path)) + prefix = os.path.basename(save_file_path).rsplit('_', maxsplit=1)[0] + for path in file_paths: + if prefix in path: + try: + threading.Thread( + target=converts_mp4, + args=(path, delete_origin_file) + ).start() + except subprocess.CalledProcessError as e: + logger.error(f"转码失败: {e} ") + return + + except subprocess.CalledProcessError as e: + logger.error( + f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + with max_request_lock: + error_count += 1 + error_window.append(1) + + else: + filename = anchor_name + f'_{title_in_name}' + now + ".ts" + print(f'{rec_info}/{filename}') + save_file_path = full_path + '/' + filename + + try: + command = [ + "-c:v", "copy", + "-c:a", "copy", + "-map", "0", + "-f", "mpegts", + save_file_path, + ] + + ffmpeg_command.extend(command) + comment_end = check_subprocess( + record_name, + record_url, + ffmpeg_command, + video_save_type, + custom_script + ) + if comment_end: + threading.Thread( + target=converts_mp4, args=(save_file_path, delete_origin_file) + ).start() + return + + except subprocess.CalledProcessError as e: + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + with max_request_lock: + error_count += 1 + error_window.append(1) + + count_time = time.time() + + except Exception as e: + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + with max_request_lock: + error_count += 1 + error_window.append(1) + + num = random.randint(-5, 5) + delay_default + if num < 0: + num = 0 + x = num + + if error_count > 20: + x = x + 60 + color_obj.print_colored("\r瞬时错误太多,延迟加60秒", color_obj.YELLOW) + + # 这里是.如果录制结束后,循环时间会暂时变成30s后检测一遍. 这样一定程度上防止主播卡顿造成少录 + # 当30秒过后检测一遍后. 会回归正常设置的循环秒数 + if record_finished: + count_time_end = time.time() - count_time + if count_time_end < 60: + x = 30 + record_finished = False + + else: + x = num + + # 这里是正常循环 + while x: + x = x - 1 + if loop_time: + print(f'\r{anchor_name}循环等待{x}秒 ', end="") + time.sleep(1) + if loop_time: + print('\r检测直播间中...', end="") + except Exception as e: + logger.error(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}") + with max_request_lock: + error_count += 1 + error_window.append(1) + time.sleep(2) + + +def backup_file(file_path: str, backup_dir_path: str, limit_counts: int = 6) -> None: + try: + if not os.path.exists(backup_dir_path): + os.makedirs(backup_dir_path) + + timestamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M-%S') + backup_file_name = os.path.basename(file_path) + '_' + timestamp + backup_file_path = os.path.join(backup_dir_path, backup_file_name).replace("\\", "/") + shutil.copy2(file_path, backup_file_path) + + files = os.listdir(backup_dir_path) + _files = [f for f in files if f.startswith(os.path.basename(file_path))] + _files.sort(key=lambda x: os.path.getmtime(os.path.join(backup_dir_path, x))) + + while len(_files) > limit_counts: + oldest_file = _files[0] + os.remove(os.path.join(backup_dir_path, oldest_file)) + _files = _files[1:] + + except Exception as e: + logger.error(f'\r备份配置文件 {file_path} 失败:{str(e)}') + + +def backup_file_start() -> None: + config_md5 = '' + url_config_md5 = '' + + while True: + try: + if os.path.exists(config_file): + new_config_md5 = utils.check_md5(config_file) + if new_config_md5 != config_md5: + backup_file(config_file, backup_dir) + config_md5 = new_config_md5 + + if os.path.exists(url_config_file): + new_url_config_md5 = utils.check_md5(url_config_file) + if new_url_config_md5 != url_config_md5: + backup_file(url_config_file, backup_dir) + url_config_md5 = new_url_config_md5 + time.sleep(600) + except Exception as e: + logger.error(f"备份配置文件失败, 错误信息: {e}") + + +def check_ffmpeg_existence() -> bool: + try: + result = subprocess.run(['ffmpeg', '-version'], check=True, capture_output=True, text=True) + if result.returncode == 0: + lines = result.stdout.splitlines() + version_line = lines[0] + built_line = lines[1] + print(version_line) + print(built_line) + except subprocess.CalledProcessError as e: + logger.error(e) + except FileNotFoundError: + pass + finally: + if check_ffmpeg(): + time.sleep(1) + return True + return False + + +# --------------------------初始化程序------------------------------------- +print("-----------------------------------------------------") +print("| DouyinLiveRecorder |") +print("-----------------------------------------------------") + +print(f"版本号: {version}") +print("GitHub: https://github.com/ihmily/DouyinLiveRecorder") +print(f'支持平台: {platforms}') +print('.....................................................') +if not check_ffmpeg_existence(): + logger.error("缺少ffmpeg无法进行录制,程序退出") + sys.exit(1) +os.makedirs(os.path.dirname(config_file), exist_ok=True) +t3 = threading.Thread(target=backup_file_start, args=(), daemon=True) +t3.start() +utils.remove_duplicate_lines(url_config_file) + + +def read_config_value(config_parser: configparser.RawConfigParser, section: str, option: str, default_value: Any) \ + -> Any: + try: + + config_parser.read(config_file, encoding=text_encoding) + if '录制设置' not in config_parser.sections(): + config_parser.add_section('录制设置') + if '推送配置' not in config_parser.sections(): + config_parser.add_section('推送配置') + if 'Cookie' not in config_parser.sections(): + config_parser.add_section('Cookie') + if 'Authorization' not in config_parser.sections(): + config_parser.add_section('Authorization') + if '账号密码' not in config_parser.sections(): + config_parser.add_section('账号密码') + return config_parser.get(section, option) + except (configparser.NoSectionError, configparser.NoOptionError): + config_parser.set(section, option, str(default_value)) + with open(config_file, 'w', encoding=text_encoding) as f: + config_parser.write(f) + return default_value + + +options = {"是": True, "否": False} +config = configparser.RawConfigParser() +language = read_config_value(config, '录制设置', 'language(zh_cn/en)', "zh_cn") +skip_proxy_check = options.get(read_config_value(config, '录制设置', '是否跳过代理检测(是/否)', "否"), False) +if language and 'en' not in language.lower(): + from i18n import translated_print + + builtins.print = translated_print + +try: + if skip_proxy_check: + global_proxy = True + else: + print('系统代理检测中,请耐心等待...') + response_g = urllib.request.urlopen("https://www.google.com/", timeout=15) + global_proxy = True + print('\r全局/规则网络代理已开启√') + pd = ProxyDetector() + if pd.is_proxy_enabled(): + proxy_info = pd.get_proxy_info() + print("System Proxy: http://{}:{}".format(proxy_info.ip, proxy_info.port)) +except HTTPError as err: + print(f"HTTP error occurred: {err.code} - {err.reason}") +except URLError: + color_obj.print_colored("INFO:未检测到全局/规则网络代理,请检查代理配置(若无需录制海外直播请忽略此条提示)", + color_obj.YELLOW) +except Exception as err: + print("An unexpected error occurred:", err) + +while True: + + try: + if not os.path.isfile(config_file): + with open(config_file, 'w', encoding=text_encoding) as file: + pass + + ini_URL_content = '' + if os.path.isfile(url_config_file): + with open(url_config_file, 'r', encoding=text_encoding) as file: + ini_URL_content = file.read().strip() + + if not ini_URL_content.strip(): + input_url = input('请输入要录制的主播直播间网址(尽量使用PC网页端的直播间地址):\n') + with open(url_config_file, 'w', encoding=text_encoding) as file: + file.write(input_url) + except OSError as err: + logger.error(f"发生 I/O 错误: {err}") + + video_save_path = read_config_value(config, '录制设置', '直播保存路径(不填则默认)', "") + folder_by_author = options.get(read_config_value(config, '录制设置', '保存文件夹是否以作者区分', "是"), False) + folder_by_time = options.get(read_config_value(config, '录制设置', '保存文件夹是否以时间区分', "否"), False) + folder_by_title = options.get(read_config_value(config, '录制设置', '保存文件夹是否以标题区分', "否"), False) + filename_by_title = options.get(read_config_value(config, '录制设置', '保存文件名是否包含标题', "否"), False) + clean_emoji = options.get(read_config_value(config, '录制设置', '是否去除名称中的表情符号', "是"), True) + video_save_type = read_config_value(config, '录制设置', '视频保存格式ts|mkv|flv|mp4|mp3音频|m4a音频', "ts") + video_record_quality = read_config_value(config, '录制设置', '原画|超清|高清|标清|流畅', "原画") + use_proxy = options.get(read_config_value(config, '录制设置', '是否使用代理ip(是/否)', "是"), False) + proxy_addr_bak = read_config_value(config, '录制设置', '代理地址', "") + proxy_addr = None if not use_proxy else proxy_addr_bak + max_request = int(read_config_value(config, '录制设置', '同一时间访问网络的线程数', 3)) + semaphore = threading.Semaphore(max_request) + delay_default = int(read_config_value(config, '录制设置', '循环时间(秒)', 120)) + local_delay_default = int(read_config_value(config, '录制设置', '排队读取网址时间(秒)', 0)) + loop_time = options.get(read_config_value(config, '录制设置', '是否显示循环秒数', "否"), False) + show_url = options.get(read_config_value(config, '录制设置', '是否显示直播源地址', "否"), False) + split_video_by_time = options.get(read_config_value(config, '录制设置', '分段录制是否开启', "否"), False) + enable_https_recording = options.get(read_config_value(config, '录制设置', '是否强制启用https录制', "否"), False) + disk_space_limit = float(read_config_value(config, '录制设置', '录制空间剩余阈值(gb)', 1.0)) + split_time = str(read_config_value(config, '录制设置', '视频分段时间(秒)', 1800)) + converts_to_mp4 = options.get(read_config_value(config, '录制设置', '录制完成后自动转为mp4格式', "否"), False) + converts_to_h264 = options.get(read_config_value(config, '录制设置', 'mp4格式重新编码为h264', "否"), False) + delete_origin_file = options.get(read_config_value(config, '录制设置', '追加格式后删除原文件', "否"), False) + create_time_file = options.get(read_config_value(config, '录制设置', '生成时间字幕文件', "否"), False) + is_run_script = options.get(read_config_value(config, '录制设置', '是否录制完成后执行自定义脚本', "否"), False) + custom_script = read_config_value(config, '录制设置', '自定义脚本执行命令', "") if is_run_script else None + enable_proxy_platform = read_config_value( + config, '录制设置', '使用代理录制的平台(逗号分隔)', + 'tiktok, soop, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk, shopee, shp, youtu, faceit' + ) + enable_proxy_platform_list = enable_proxy_platform.replace(',', ',').split(',') if enable_proxy_platform else None + extra_enable_proxy = read_config_value(config, '录制设置', '额外使用代理录制的平台(逗号分隔)', '') + extra_enable_proxy_platform_list = extra_enable_proxy.replace(',', ',').split(',') if extra_enable_proxy else None + live_status_push = read_config_value(config, '推送配置', '直播状态推送渠道', "") + dingtalk_api_url = read_config_value(config, '推送配置', '钉钉推送接口链接', "") + xizhi_api_url = read_config_value(config, '推送配置', '微信推送接口链接', "") + bark_msg_api = read_config_value(config, '推送配置', 'bark推送接口链接', "") + bark_msg_level = read_config_value(config, '推送配置', 'bark推送中断级别', "active") + bark_msg_ring = read_config_value(config, '推送配置', 'bark推送铃声', "bell") + dingtalk_phone_num = read_config_value(config, '推送配置', '钉钉通知@对象(填手机号)', "") + dingtalk_is_atall = options.get(read_config_value(config, '推送配置', '钉钉通知@全体(是/否)', "否"), False) + tg_token = read_config_value(config, '推送配置', 'tgapi令牌', "") + tg_chat_id = read_config_value(config, '推送配置', 'tg聊天id(个人或者群组id)', "") + email_host = read_config_value(config, '推送配置', 'SMTP邮件服务器', "") + open_smtp_ssl = options.get(read_config_value(config, '推送配置', '是否使用SMTP服务SSL加密(是/否)', "是"), True) + smtp_port = read_config_value(config, '推送配置', 'SMTP邮件服务器端口', "") + login_email = read_config_value(config, '推送配置', '邮箱登录账号', "") + email_password = read_config_value(config, '推送配置', '发件人密码(授权码)', "") + sender_email = read_config_value(config, '推送配置', '发件人邮箱', "") + sender_name = read_config_value(config, '推送配置', '发件人显示昵称', "") + to_email = read_config_value(config, '推送配置', '收件人邮箱', "") + ntfy_api = read_config_value(config, '推送配置', 'ntfy推送地址', "") + ntfy_tags = read_config_value(config, '推送配置', 'ntfy推送标签', "tada") + ntfy_email = read_config_value(config, '推送配置', 'ntfy推送邮箱', "") + pushplus_token = read_config_value(config, '推送配置', 'pushplus推送token', "") + push_message_title = read_config_value(config, '推送配置', '自定义推送标题', "直播间状态更新通知") + begin_push_message_text = read_config_value(config, '推送配置', '自定义开播推送内容', "") + over_push_message_text = read_config_value(config, '推送配置', '自定义关播推送内容', "") + disable_record = options.get(read_config_value(config, '推送配置', '只推送通知不录制(是/否)', "否"), False) + push_check_seconds = int(read_config_value(config, '推送配置', '直播推送检测频率(秒)', 1800)) + begin_show_push = options.get(read_config_value(config, '推送配置', '开播推送开启(是/否)', "是"), True) + over_show_push = options.get(read_config_value(config, '推送配置', '关播推送开启(是/否)', "否"), False) + sooplive_username = read_config_value(config, '账号密码', 'sooplive账号', '') + sooplive_password = read_config_value(config, '账号密码', 'sooplive密码', '') + flextv_username = read_config_value(config, '账号密码', 'flextv账号', '') + flextv_password = read_config_value(config, '账号密码', 'flextv密码', '') + popkontv_username = read_config_value(config, '账号密码', 'popkontv账号', '') + popkontv_partner_code = read_config_value(config, '账号密码', 'partner_code', 'P-00001') + popkontv_password = read_config_value(config, '账号密码', 'popkontv密码', '') + twitcasting_account_type = read_config_value(config, '账号密码', 'twitcasting账号类型', 'normal') + twitcasting_username = read_config_value(config, '账号密码', 'twitcasting账号', '') + twitcasting_password = read_config_value(config, '账号密码', 'twitcasting密码', '') + popkontv_access_token = read_config_value(config, 'Authorization', 'popkontv_token', '') + dy_cookie = read_config_value(config, 'Cookie', '抖音cookie', '') + ks_cookie = read_config_value(config, 'Cookie', '快手cookie', '') + tiktok_cookie = read_config_value(config, 'Cookie', 'tiktok_cookie', '') + hy_cookie = read_config_value(config, 'Cookie', '虎牙cookie', '') + douyu_cookie = read_config_value(config, 'Cookie', '斗鱼cookie', '') + yy_cookie = read_config_value(config, 'Cookie', 'yy_cookie', '') + bili_cookie = read_config_value(config, 'Cookie', 'B站cookie', '') + xhs_cookie = read_config_value(config, 'Cookie', '小红书cookie', '') + bigo_cookie = read_config_value(config, 'Cookie', 'bigo_cookie', '') + blued_cookie = read_config_value(config, 'Cookie', 'blued_cookie', '') + sooplive_cookie = read_config_value(config, 'Cookie', 'sooplive_cookie', '') + netease_cookie = read_config_value(config, 'Cookie', 'netease_cookie', '') + qiandurebo_cookie = read_config_value(config, 'Cookie', '千度热播_cookie', '') + pandatv_cookie = read_config_value(config, 'Cookie', 'pandatv_cookie', '') + maoerfm_cookie = read_config_value(config, 'Cookie', '猫耳fm_cookie', '') + winktv_cookie = read_config_value(config, 'Cookie', 'winktv_cookie', '') + flextv_cookie = read_config_value(config, 'Cookie', 'flextv_cookie', '') + look_cookie = read_config_value(config, 'Cookie', 'look_cookie', '') + twitcasting_cookie = read_config_value(config, 'Cookie', 'twitcasting_cookie', '') + baidu_cookie = read_config_value(config, 'Cookie', 'baidu_cookie', '') + weibo_cookie = read_config_value(config, 'Cookie', 'weibo_cookie', '') + kugou_cookie = read_config_value(config, 'Cookie', 'kugou_cookie', '') + twitch_cookie = read_config_value(config, 'Cookie', 'twitch_cookie', '') + liveme_cookie = read_config_value(config, 'Cookie', 'liveme_cookie', '') + huajiao_cookie = read_config_value(config, 'Cookie', 'huajiao_cookie', '') + liuxing_cookie = read_config_value(config, 'Cookie', 'liuxing_cookie', '') + showroom_cookie = read_config_value(config, 'Cookie', 'showroom_cookie', '') + acfun_cookie = read_config_value(config, 'Cookie', 'acfun_cookie', '') + changliao_cookie = read_config_value(config, 'Cookie', 'changliao_cookie', '') + yinbo_cookie = read_config_value(config, 'Cookie', 'yinbo_cookie', '') + yingke_cookie = read_config_value(config, 'Cookie', 'yingke_cookie', '') + zhihu_cookie = read_config_value(config, 'Cookie', 'zhihu_cookie', '') + chzzk_cookie = read_config_value(config, 'Cookie', 'chzzk_cookie', '') + haixiu_cookie = read_config_value(config, 'Cookie', 'haixiu_cookie', '') + vvxqiu_cookie = read_config_value(config, 'Cookie', 'vvxqiu_cookie', '') + yiqilive_cookie = read_config_value(config, 'Cookie', '17live_cookie', '') + langlive_cookie = read_config_value(config, 'Cookie', 'langlive_cookie', '') + pplive_cookie = read_config_value(config, 'Cookie', 'pplive_cookie', '') + six_room_cookie = read_config_value(config, 'Cookie', '6room_cookie', '') + lehaitv_cookie = read_config_value(config, 'Cookie', 'lehaitv_cookie', '') + huamao_cookie = read_config_value(config, 'Cookie', 'huamao_cookie', '') + shopee_cookie = read_config_value(config, 'Cookie', 'shopee_cookie', '') + youtube_cookie = read_config_value(config, 'Cookie', 'youtube_cookie', '') + taobao_cookie = read_config_value(config, 'Cookie', 'taobao_cookie', '') + jd_cookie = read_config_value(config, 'Cookie', 'jd_cookie', '') + faceit_cookie = read_config_value(config, 'Cookie', 'faceit_cookie', '') + migu_cookie = read_config_value(config, 'Cookie', 'migu_cookie', '') + lianjie_cookie = read_config_value(config, 'Cookie', 'lianjie_cookie', '') + laixiu_cookie = read_config_value(config, 'Cookie', 'laixiu_cookie', '') + picarto_cookie = read_config_value(config, 'Cookie', 'picarto_cookie', '') + + video_save_type_list = ("FLV", "MKV", "TS", "MP4", "MP3音频", "M4A音频") + if video_save_type and video_save_type.upper() in video_save_type_list: + video_save_type = video_save_type.upper() + else: + video_save_type = "TS" + + check_path = video_save_path or default_path + if utils.check_disk_capacity(check_path, show=first_run) < disk_space_limit: + exit_recording = True + if not recording: + logger.warning(f"Disk space remaining is below {disk_space_limit} GB. " + f"Exiting program due to the disk space limit being reached.") + sys.exit(-1) + + + def contains_url(string: str) -> bool: + pattern = r"(https?://)?(www\.)?[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)+(:\d+)?(/.*)?" + return re.search(pattern, string) is not None + + + try: + url_comments, line_list, url_line_list = [[] for _ in range(3)] + with (open(url_config_file, "r", encoding=text_encoding, errors='ignore') as file): + for origin_line in file: + if origin_line in line_list: + delete_line(url_config_file, origin_line) + line_list.append(origin_line) + line = origin_line.strip() + if len(line) < 18: + continue + + line_spilt = line.split('主播: ') + if len(line_spilt) > 2: + line = update_file(url_config_file, line, f'{line_spilt[0]}主播: {line_spilt[-1]}') + + is_comment_line = line.startswith("#") + if is_comment_line: + line = line.lstrip('#') + + if re.search('[,,]', line): + split_line = re.split('[,,]', line) + else: + split_line = [line, ''] + + if len(split_line) == 1: + url = split_line[0] + quality, name = [video_record_quality, ''] + elif len(split_line) == 2: + if contains_url(split_line[0]): + quality = video_record_quality + url, name = split_line + else: + quality, url = split_line + name = '' + else: + quality, url, name = split_line + + if quality not in ("原画", "蓝光", "超清", "高清", "标清", "流畅"): + quality = '原画' + + if url not in url_line_list: + url_line_list.append(url) + else: + delete_line(url_config_file, origin_line) + + url = 'https://' + url if '://' not in url else url + url_host = url.split('/')[2] + + platform_host = [ + 'live.douyin.com', + 'v.douyin.com', + 'www.douyin.com', + 'live.kuaishou.com', + 'www.huya.com', + 'www.douyu.com', + 'www.yy.com', + 'live.bilibili.com', + 'www.redelight.cn', + 'www.xiaohongshu.com', + 'xhslink.com', + 'www.bigo.tv', + 'slink.bigovideo.tv', + 'app.blued.cn', + 'cc.163.com', + 'qiandurebo.com', + 'fm.missevan.com', + 'look.163.com', + 'twitcasting.tv', + 'live.baidu.com', + 'weibo.com', + 'fanxing.kugou.com', + 'fanxing2.kugou.com', + 'mfanxing.kugou.com', + 'www.huajiao.com', + 'www.7u66.com', + 'wap.7u66.com', + 'live.acfun.cn', + 'm.acfun.cn', + 'live.tlclw.com', + 'wap.tlclw.com', + 'live.ybw1666.com', + 'wap.ybw1666.com', + 'www.inke.cn', + 'www.zhihu.com', + 'www.haixiutv.com', + "h5webcdnp.vvxqiu.com", + "17.live", + 'www.lang.live', + "m.pp.weimipopo.com", + "v.6.cn", + "m.6.cn", + 'www.lehaitv.com', + 'h.catshow168.com', + 'e.tb.cn', + 'huodong.m.taobao.com', + '3.cn', + 'eco.m.jd.com', + 'www.miguvideo.com', + 'm.miguvideo.com', + 'show.lailianjie.com', + 'www.imkktv.com', + 'www.picarto.tv' + ] + overseas_platform_host = [ + 'www.tiktok.com', + 'play.sooplive.co.kr', + 'm.sooplive.co.kr', + 'www.pandalive.co.kr', + 'www.winktv.co.kr', + 'www.flextv.co.kr', + 'www.popkontv.com', + 'www.twitch.tv', + 'www.liveme.com', + 'www.showroom-live.com', + 'chzzk.naver.com', + 'm.chzzk.naver.com', + 'live.shopee.', + '.shp.ee', + 'www.youtube.com', + 'youtu.be', + 'www.faceit.com' + ] + + platform_host.extend(overseas_platform_host) + clean_url_host_list = ( + "live.douyin.com", + "live.bilibili.com", + "www.huajiao.com", + "www.zhihu.com", + "www.huya.com", + "chzzk.naver.com", + "www.liveme.com", + "www.haixiutv.com", + "v.6.cn", + "m.6.cn", + 'www.lehaitv.com' + ) + + if 'live.shopee.' in url_host or '.shp.ee' in url_host: + url_host = 'live.shopee.' if 'live.shopee.' in url_host else '.shp.ee' + + if url_host in platform_host or any(ext in url for ext in (".flv", ".m3u8")): + if url_host in clean_url_host_list: + url = update_file(url_config_file, old_str=url, new_str=url.split('?')[0]) + + if 'xiaohongshu' in url: + host_id = re.search('&host_id=(.*?)(?=&|$)', url) + if host_id: + new_url = url.split('?')[0] + f'?host_id={host_id.group(1)}' + url = update_file(url_config_file, old_str=url, new_str=new_url) + + url_comments = [i for i in url_comments if url not in i] + if is_comment_line: + url_comments.append(url) + else: + new_line = (quality, url, name) + url_tuples_list.append(new_line) + else: + if not origin_line.startswith('#'): + color_obj.print_colored(f"\r{origin_line.strip()} 本行包含未知链接.此条跳过", color_obj.YELLOW) + update_file(url_config_file, old_str=origin_line, new_str=origin_line, start_str='#') + + while len(need_update_line_list): + a = need_update_line_list.pop() + replace_words = a.split('|') + if replace_words[0] != replace_words[1]: + if replace_words[1].startswith("#"): + start_with = '#' + new_word = replace_words[1][1:] + else: + start_with = None + new_word = replace_words[1] + update_file(url_config_file, old_str=replace_words[0], new_str=new_word, start_str=start_with) + + text_no_repeat_url = list(set(url_tuples_list)) + + if len(text_no_repeat_url) > 0: + for url_tuple in text_no_repeat_url: + monitoring = len(running_list) + + if url_tuple[1] in not_record_list: + continue + + if url_tuple[1] not in running_list: + print(f"\r{'新增' if not first_start else '传入'}地址: {url_tuple[1]}") + monitoring += 1 + args = [url_tuple, monitoring] + create_var[f'thread_{monitoring}'] = threading.Thread(target=start_record, args=args) + create_var[f'thread_{monitoring}'].daemon = True + create_var[f'thread_{monitoring}'].start() + running_list.append(url_tuple[1]) + time.sleep(local_delay_default) + url_tuples_list = [] + first_start = False + + except Exception as err: + logger.error(f"错误信息: {err} 发生错误的行数: {err.__traceback__.tb_lineno}") + + if first_run: + t = threading.Thread(target=display_info, args=(), daemon=True) + t.start() + t2 = threading.Thread(target=adjust_max_request, args=(), daemon=True) + t2.start() + first_run = False + + time.sleep(3) \ No newline at end of file diff --git a/proxy.py b/proxy.py new file mode 100644 index 0000000..85385d6 --- /dev/null +++ b/proxy.py @@ -0,0 +1,92 @@ +import os +import sys +from enum import Enum, auto +from dataclasses import dataclass, field +from utils import logger + + +class ProxyType(Enum): + HTTP = auto() + HTTPS = auto() + SOCKS = auto() + + +@dataclass(frozen=True) +class ProxyInfo: + ip: str = field(default="", repr=True) + port: str = field(default="", repr=True) + + def __post_init__(self): + if (self.ip and not self.port) or (not self.ip and self.port): + raise ValueError("IP or port cannot be empty") + + if (self.ip and self.port) and (not self.port.isdigit() or not (1 <= int(self.port) <= 65535)): + raise ValueError("Port must be a digit between 1 and 65535") + + +class ProxyDetector: + def __init__(self): + if sys.platform.startswith('win'): + import winreg + self.winreg = winreg + self.__path = r'Software\Microsoft\Windows\CurrentVersion\Internet Settings' + with winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER) as key_user: + self.__INTERNET_SETTINGS = winreg.OpenKeyEx(key_user, self.__path, 0, winreg.KEY_ALL_ACCESS) + else: + self.__is_windows = False + + def get_proxy_info(self) -> ProxyInfo: + if sys.platform.startswith('win'): + ip, port = self._get_proxy_info_windows() + else: + ip, port = self._get_proxy_info_linux() + return ProxyInfo(ip, port) + + def is_proxy_enabled(self) -> bool: + if sys.platform.startswith('win'): + return self._is_proxy_enabled_windows() + else: + return self._is_proxy_enabled_linux() + + def _get_proxy_info_windows(self) -> tuple[str, str]: + ip, port = "", "" + if self._is_proxy_enabled_windows(): + try: + ip_port = self.winreg.QueryValueEx(self.__INTERNET_SETTINGS, "ProxyServer")[0] + if ip_port: + ip, port = ip_port.split(":") + except FileNotFoundError as err: + logger.warning("No proxy information found: " + str(err)) + except Exception as err: + logger.error("An error occurred: " + str(err)) + else: + logger.debug("No proxy is enabled on the system") + return ip, port + + def _is_proxy_enabled_windows(self) -> bool: + try: + if self.winreg.QueryValueEx(self.__INTERNET_SETTINGS, "ProxyEnable")[0] == 1: + return True + except FileNotFoundError as err: + print("No proxy information found: " + str(err)) + except Exception as err: + print("An error occurred: " + str(err)) + return False + + @staticmethod + def _get_proxy_info_linux() -> tuple[str, str]: + proxies = { + 'http': os.getenv('http_proxy'), + 'https': os.getenv('https_proxy'), + 'ftp': os.getenv('ftp_proxy') + } + ip = port = "" + for proto, proxy in proxies.items(): + if proxy: + ip, port = proxy.split(':') + break + return ip, port + + def _is_proxy_enabled_linux(self) -> bool: + proxies = self._get_proxy_info_linux() + return any(proxy != '' for proxy in proxies) diff --git a/room.py b/room.py new file mode 100644 index 0000000..1724a1a --- /dev/null +++ b/room.py @@ -0,0 +1,158 @@ +# -*- encoding: utf-8 -*- + +""" +Author: Hmily +GitHub:https://github.com/ihmily +Date: 2023-07-17 23:52:05 +Update: 2025-02-04 04:57:00 +Copyright (c) 2023 by Hmily, All Rights Reserved. +""" +import re +import urllib.parse +import execjs +import httpx +import urllib.request +import utils +# from . import JS_SCRIPT_PATH, utils +import os +import sys +from pathlib import Path +from initializer import check_node + +current_file_path = Path(__file__).resolve() +current_dir = current_file_path.parent +JS_SCRIPT_PATH = current_dir / 'javascript' +no_proxy_handler = urllib.request.ProxyHandler({}) +opener = urllib.request.build_opener(no_proxy_handler) + + +class UnsupportedUrlError(Exception): + pass + + +HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) ' + 'SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Cookie': 's_v_web_id=verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf' +} + +HEADERS_PC = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'Cookie': 'sessionid=7494ae59ae06784454373ce25761e864; __ac_nonce=0670497840077ee4c9eb2; ' + '__ac_signature=_02B4Z6wo00f012DZczQAAIDCJJBb3EjnINdg-XeAAL8-db; ' + 's_v_web_id=verify_m1ztgtjj_vuHnMLZD_iwZ9_4YO4_BdN1_7wLP3pyqXsf2; ' + } + + +# X-bogus算法 +async def get_xbogus(url: str, headers: dict | None = None) -> str: + if not headers or 'user-agent' not in (k.lower() for k in headers): + headers = HEADERS + query = urllib.parse.urlparse(url).query + xbogus = execjs.compile(open(f'{JS_SCRIPT_PATH}/x-bogus.js').read()).call( + 'sign', query, headers.get("User-Agent", "user-agent")) + return xbogus + + +# 获取房间ID和用户secID +async def get_sec_user_id(url: str, proxy_addr: str | None = None, headers: dict | None = None) -> tuple | None: + if not headers or all(k.lower() not in ['user-agent', 'cookie'] for k in headers): + headers = HEADERS + + try: + proxy_addr = utils.handle_proxy_addr(proxy_addr) + async with httpx.AsyncClient(proxy=proxy_addr, timeout=15) as client: + response = await client.get(url, headers=headers, follow_redirects=True) + redirect_url = response.url + if 'reflow/' in str(redirect_url): + match = re.search(r'sec_user_id=([\w_\-]+)&', str(redirect_url)) + if match: + sec_user_id = match.group(1) + room_id = str(redirect_url).split('?')[0].rsplit('/', maxsplit=1)[1] + return room_id, sec_user_id + else: + raise RuntimeError("Could not find sec_user_id in the URL.") + else: + raise UnsupportedUrlError("The redirect URL does not contain 'reflow/'.") + except UnsupportedUrlError as e: + raise e + except Exception as e: + raise RuntimeError(f"An error occurred: {e}") + + +# 获取抖音号 +async def get_unique_id(url: str, proxy_addr: str | None = None, headers: dict | None = None) -> str | None: + if not headers or all(k.lower() not in ['user-agent', 'cookie'] for k in headers): + headers = HEADERS + + try: + proxy_addr = utils.handle_proxy_addr(proxy_addr) + async with httpx.AsyncClient(proxy=proxy_addr, timeout=15) as client: + response = await client.get(url, headers=headers, follow_redirects=True) + redirect_url = str(response.url) + if 'reflow/' in str(redirect_url): + raise UnsupportedUrlError("Unsupported URL") + sec_user_id = redirect_url.split('?')[0].rsplit('/', maxsplit=1)[1] + headers['Cookie'] = ('ttwid=1%7C4ejCkU2bKY76IySQENJwvGhg1IQZrgGEupSyTKKfuyk%7C1740470403%7Cbc9a' + 'd2ee341f1a162f9e27f4641778030d1ae91e31f9df6553a8f2efa3bdb7b4; __ac_nonce=06' + '83e59f3009cc48fbab0; __ac_signature=_02B4Z6wo00f01mG6waQAAIDB9JUCzFb6.TZhmsU' + 'AAPBf34; __ac_referer=__ac_blank') + user_page_response = await client.get(f'https://www.iesdouyin.com/share/user/{sec_user_id}', + headers=headers, follow_redirects=True) + matches = re.findall(r'unique_id":"(.*?)","verification_type', user_page_response.text) + if matches: + unique_id = matches[-1] + return unique_id + else: + raise RuntimeError("Could not find unique_id in the response.") + except UnsupportedUrlError as e: + raise e + except Exception as e: + raise RuntimeError(f"An error occurred: {e}") + + +# 获取直播间webID +async def get_live_room_id(room_id: str, sec_user_id: str, proxy_addr: str | None = None, params: dict | None = None, + headers: dict | None = None) -> str: + if not headers or all(k.lower() not in ['user-agent', 'cookie'] for k in headers): + headers = HEADERS + + if not params: + params = { + "verifyFp": "verify_lk07kv74_QZYCUApD_xhiB_405x_Ax51_GYO9bUIyZQVf", + "type_id": "0", + "live_id": "1", + "room_id": room_id, + "sec_user_id": sec_user_id, + "app_id": "1128", + "msToken": "wrqzbEaTlsxt52-vxyZo_mIoL0RjNi1ZdDe7gzEGMUTVh_HvmbLLkQrA_1HKVOa2C6gkxb6IiY6TY2z8enAkPEwGq--gM" + "-me3Yudck2ailla5Q4osnYIHxd9dI4WtQ==", + } + + api = f'https://webcast.amemv.com/webcast/room/reflow/info/?{urllib.parse.urlencode(params)}' + xbogus = await get_xbogus(api) + api = api + "&X-Bogus=" + xbogus + + try: + proxy_addr = utils.handle_proxy_addr(proxy_addr) + async with httpx.AsyncClient(proxy=proxy_addr, + timeout=15) as client: + response = await client.get(api, headers=headers) + response.raise_for_status() + json_data = response.json() + return json_data['data']['room']['owner']['web_rid'] + except httpx.HTTPStatusError as e: + print(f"HTTP status error occurred: {e.response.status_code}") + raise + except Exception as e: + print(f"An exception occurred during get_live_room_id: {e}") + raise + + +if __name__ == '__main__': + room_url = "https://v.douyin.com/iQLgKSj/" + _room_id, sec_uid = get_sec_user_id(room_url) + web_rid = get_live_room_id(_room_id, sec_uid) + print("return web_rid:", web_rid) diff --git a/spider.py b/spider.py new file mode 100644 index 0000000..36e645d --- /dev/null +++ b/spider.py @@ -0,0 +1,3232 @@ +# -*- encoding: utf-8 -*- + +""" +Author: Hmily +GitHub: https://github.com/ihmily +Date: 2023-07-15 23:15:00 +Update: 2025-07-19 17:43:00 +Copyright (c) 2023-2025 by Hmily, All Rights Reserved. +Function: Get live stream data. +""" + +import hashlib +import random +import subprocess +import time +import uuid +from operator import itemgetter +import urllib.parse +import urllib.error +from typing import List +import httpx +import ssl +import re +import json +import execjs +import urllib.request +# from . import JS_SCRIPT_PATH +import utils + +from utils import trace_error_decorator, generate_random_string +from logger import script_path +from room import get_sec_user_id, get_unique_id, UnsupportedUrlError +from http_clients.async_http import async_req +import os +import sys +from pathlib import Path +from initializer import check_node + +current_file_path = Path(__file__).resolve() +current_dir = current_file_path.parent +JS_SCRIPT_PATH = current_dir / 'javascript' +ssl_context = ssl.create_default_context() +ssl_context.check_hostname = False +ssl_context.verify_mode = ssl.CERT_NONE +OptionalStr = str | None +OptionalDict = dict | None + + +def get_params(url: str, params: str) -> OptionalStr: + parsed_url = urllib.parse.urlparse(url) + query_params = urllib.parse.parse_qs(parsed_url.query) + + if params in query_params: + return query_params[params][0] + + +async def get_play_url_list(m3u8: str, proxy: OptionalStr = None, header: OptionalDict = None, + abroad: bool = False) -> List[str]: + resp = await async_req(url=m3u8, proxy_addr=proxy, headers=header, abroad=abroad) + play_url_list = [] + for i in resp.split('\n'): + if i.startswith('https://'): + play_url_list.append(i.strip()) + if not play_url_list: + for i in resp.split('\n'): + if i.strip().endswith('m3u8'): + play_url_list.append(i.strip()) + bandwidth_pattern = re.compile(r'BANDWIDTH=(\d+)') + bandwidth_list = bandwidth_pattern.findall(resp) + url_to_bandwidth = {url: int(bandwidth) for bandwidth, url in zip(bandwidth_list, play_url_list)} + play_url_list = sorted(play_url_list, key=lambda url: url_to_bandwidth[url], reverse=True) + return play_url_list + + +@trace_error_decorator +async def get_douyin_app_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Referer': 'https://live.douyin.com/', + 'Cookie': 'ttwid=1%7CB1qls3GdnZhUov9o2NxOMxxYS2ff6OSvEWbv0ytbES4%7C1680522049%7C280d802d6d478e3e78d0c807f7c487e7ffec0ae4e5fdd6a0fe74c3c6af149511; my_rd=1; passport_csrf_token=3ab34460fa656183fccfb904b16ff742; passport_csrf_token_default=3ab34460fa656183fccfb904b16ff742; d_ticket=9f562383ac0547d0b561904513229d76c9c21; n_mh=hvnJEQ4Q5eiH74-84kTFUyv4VK8xtSrpRZG1AhCeFNI; store-region=cn-fj; store-region-src=uid; LOGIN_STATUS=1; __security_server_data_status=1; FORCE_LOGIN=%7B%22videoConsumedRemainSeconds%22%3A180%7D; pwa2=%223%7C0%7C3%7C0%22; download_guide=%223%2F20230729%2F0%22; volume_info=%7B%22isUserMute%22%3Afalse%2C%22isMute%22%3Afalse%2C%22volume%22%3A0.6%7D; strategyABtestKey=%221690824679.923%22; stream_recommend_feed_params=%22%7B%5C%22cookie_enabled%5C%22%3Atrue%2C%5C%22screen_width%5C%22%3A1536%2C%5C%22screen_height%5C%22%3A864%2C%5C%22browser_online%5C%22%3Atrue%2C%5C%22cpu_core_num%5C%22%3A8%2C%5C%22device_memory%5C%22%3A8%2C%5C%22downlink%5C%22%3A10%2C%5C%22effective_type%5C%22%3A%5C%224g%5C%22%2C%5C%22round_trip_time%5C%22%3A150%7D%22; VIDEO_FILTER_MEMO_SELECT=%7B%22expireTime%22%3A1691443863751%2C%22type%22%3Anull%7D; home_can_add_dy_2_desktop=%221%22; __live_version__=%221.1.1.2169%22; device_web_cpu_core=8; device_web_memory_size=8; xgplayer_user_id=346045893336; csrf_session_id=2e00356b5cd8544d17a0e66484946f28; odin_tt=724eb4dd23bc6ffaed9a1571ac4c757ef597768a70c75fef695b95845b7ffcd8b1524278c2ac31c2587996d058e03414595f0a4e856c53bd0d5e5f56dc6d82e24004dc77773e6b83ced6f80f1bb70627; __ac_nonce=064caded4009deafd8b89; __ac_signature=_02B4Z6wo00f01HLUuwwAAIDBh6tRkVLvBQBy9L-AAHiHf7; ttcid=2e9619ebbb8449eaa3d5a42d8ce88ec835; webcast_leading_last_show_time=1691016922379; webcast_leading_total_show_times=1; webcast_local_quality=sd; live_can_add_dy_2_desktop=%221%22; msToken=1JDHnVPw_9yTvzIrwb7cQj8dCMNOoesXbA_IooV8cezcOdpe4pzusZE7NB7tZn9TBXPr0ylxmv-KMs5rqbNUBHP4P7VBFUu0ZAht_BEylqrLpzgt3y5ne_38hXDOX8o=; msToken=jV_yeN1IQKUd9PlNtpL7k5vthGKcHo0dEh_QPUQhr8G3cuYv-Jbb4NnIxGDmhVOkZOCSihNpA2kvYtHiTW25XNNX_yrsv5FN8O6zm3qmCIXcEe0LywLn7oBO2gITEeg=; tt_scid=mYfqpfbDjqXrIGJuQ7q-DlQJfUSG51qG.KUdzztuGP83OjuVLXnQHjsz-BRHRJu4e986' + } + if cookies: + headers['Cookie'] = cookies + + async def get_app_data(room_id: str, sec_uid: str) -> dict: + app_params = { + "verifyFp": "verify_lxj5zv70_7szNlAB7_pxNY_48Vh_ALKF_GA1Uf3yteoOY", + "type_id": "0", + "live_id": "1", + "room_id": room_id, + "sec_user_id": sec_uid, + "version_code": "99.99.99", + "app_id": "1128" + } + api2 = f'https://webcast.amemv.com/webcast/room/reflow/info/?{urllib.parse.urlencode(app_params)}' + json_str2 = await async_req(url=api2, proxy_addr=proxy_addr, headers=headers) + json_data2 = json.loads(json_str2)['data'] + room_data2 = json_data2['room'] + room_data2['anchor_name'] = room_data2['owner']['nickname'] + return room_data2 + + try: + web_rid = url.split('?')[0].split('live.douyin.com/') + if len(web_rid) > 1: + web_rid = web_rid[1] + params = { + "aid": "6383", + "app_name": "douyin_web", + "live_id": "1", + "device_platform": "web", + "language": "zh-CN", + "browser_language": "zh-CN", + "browser_platform": "Win32", + "browser_name": "Chrome", + "browser_version": "116.0.0.0", + "web_rid": web_rid + + } + api = f'https://live.douyin.com/webcast/room/web/enter/?{urllib.parse.urlencode(params)}' + json_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str)['data'] + room_data = json_data['data'][0] + room_data['anchor_name'] = json_data['user']['nickname'] + else: + try: + data = await get_sec_user_id(url, proxy_addr=proxy_addr) + _room_id, _sec_uid = data + room_data = await get_app_data(_room_id, _sec_uid) + except UnsupportedUrlError: + unique_id = await get_unique_id(url, proxy_addr=proxy_addr) + return await get_douyin_stream_data(f'https://live.douyin.com/{unique_id}') + + if room_data['status'] == 2: + if 'stream_url' not in room_data: + raise RuntimeError( + "The live streaming type or gameplay is not supported on the computer side yet, please use the " + "app to share the link for recording." + ) + live_core_sdk_data = room_data['stream_url']['live_core_sdk_data'] + pull_datas = room_data['stream_url']['pull_datas'] + if live_core_sdk_data: + if pull_datas: + key = list(pull_datas.keys())[0] + json_str = pull_datas[key]['stream_data'] + else: + json_str = live_core_sdk_data['pull_data']['stream_data'] + json_data = json.loads(json_str) + if 'origin' in json_data['data']: + stream_data = live_core_sdk_data['pull_data']['stream_data'] + origin_data = json.loads(stream_data)['data']['origin']['main'] + sdk_params = json.loads(origin_data['sdk_params']) + origin_hls_codec = sdk_params.get('VCodec') or '' + + origin_url_list = json_data['data']['origin']['main'] + origin_m3u8 = {'ORIGIN': origin_url_list["hls"] + '&codec=' + origin_hls_codec} + origin_flv = {'ORIGIN': origin_url_list["flv"] + '&codec=' + origin_hls_codec} + hls_pull_url_map = room_data['stream_url']['hls_pull_url_map'] + flv_pull_url = room_data['stream_url']['flv_pull_url'] + room_data['stream_url']['hls_pull_url_map'] = {**origin_m3u8, **hls_pull_url_map} + room_data['stream_url']['flv_pull_url'] = {**origin_flv, **flv_pull_url} + except Exception as e: + print(f"Error message: {e} Error line: {e.__traceback__.tb_lineno}") + room_data = {'anchor_name': ""} + return room_data + + +@trace_error_decorator +async def get_douyin_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Referer': 'https://live.douyin.com/', + 'Cookie': 'ttwid=1%7CB1qls3GdnZhUov9o2NxOMxxYS2ff6OSvEWbv0ytbES4%7C1680522049%7C280d802d6d478e3e78d0c807f7c487e7ffec0ae4e5fdd6a0fe74c3c6af149511; my_rd=1; passport_csrf_token=3ab34460fa656183fccfb904b16ff742; passport_csrf_token_default=3ab34460fa656183fccfb904b16ff742; d_ticket=9f562383ac0547d0b561904513229d76c9c21; n_mh=hvnJEQ4Q5eiH74-84kTFUyv4VK8xtSrpRZG1AhCeFNI; store-region=cn-fj; store-region-src=uid; LOGIN_STATUS=1; __security_server_data_status=1; FORCE_LOGIN=%7B%22videoConsumedRemainSeconds%22%3A180%7D; pwa2=%223%7C0%7C3%7C0%22; download_guide=%223%2F20230729%2F0%22; volume_info=%7B%22isUserMute%22%3Afalse%2C%22isMute%22%3Afalse%2C%22volume%22%3A0.6%7D; strategyABtestKey=%221690824679.923%22; stream_recommend_feed_params=%22%7B%5C%22cookie_enabled%5C%22%3Atrue%2C%5C%22screen_width%5C%22%3A1536%2C%5C%22screen_height%5C%22%3A864%2C%5C%22browser_online%5C%22%3Atrue%2C%5C%22cpu_core_num%5C%22%3A8%2C%5C%22device_memory%5C%22%3A8%2C%5C%22downlink%5C%22%3A10%2C%5C%22effective_type%5C%22%3A%5C%224g%5C%22%2C%5C%22round_trip_time%5C%22%3A150%7D%22; VIDEO_FILTER_MEMO_SELECT=%7B%22expireTime%22%3A1691443863751%2C%22type%22%3Anull%7D; home_can_add_dy_2_desktop=%221%22; __live_version__=%221.1.1.2169%22; device_web_cpu_core=8; device_web_memory_size=8; xgplayer_user_id=346045893336; csrf_session_id=2e00356b5cd8544d17a0e66484946f28; odin_tt=724eb4dd23bc6ffaed9a1571ac4c757ef597768a70c75fef695b95845b7ffcd8b1524278c2ac31c2587996d058e03414595f0a4e856c53bd0d5e5f56dc6d82e24004dc77773e6b83ced6f80f1bb70627; __ac_nonce=064caded4009deafd8b89; __ac_signature=_02B4Z6wo00f01HLUuwwAAIDBh6tRkVLvBQBy9L-AAHiHf7; ttcid=2e9619ebbb8449eaa3d5a42d8ce88ec835; webcast_leading_last_show_time=1691016922379; webcast_leading_total_show_times=1; webcast_local_quality=sd; live_can_add_dy_2_desktop=%221%22; msToken=1JDHnVPw_9yTvzIrwb7cQj8dCMNOoesXbA_IooV8cezcOdpe4pzusZE7NB7tZn9TBXPr0ylxmv-KMs5rqbNUBHP4P7VBFUu0ZAht_BEylqrLpzgt3y5ne_38hXDOX8o=; msToken=jV_yeN1IQKUd9PlNtpL7k5vthGKcHo0dEh_QPUQhr8G3cuYv-Jbb4NnIxGDmhVOkZOCSihNpA2kvYtHiTW25XNNX_yrsv5FN8O6zm3qmCIXcEe0LywLn7oBO2gITEeg=; tt_scid=mYfqpfbDjqXrIGJuQ7q-DlQJfUSG51qG.KUdzztuGP83OjuVLXnQHjsz-BRHRJu4e986' + } + if cookies: + headers['Cookie'] = cookies + + try: + origin_url_list = None + html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers) + match_json_str = re.search(r'(\{\\"state\\":.*?)]\\n"]\)', html_str) + if not match_json_str: + match_json_str = re.search(r'(\{\\"common\\":.*?)]\\n"]\)

', + html_str, re.DOTALL)[0] + except Exception: + raise ConnectionError("Please check if your network can access the TikTok website normally") + json_data = json.loads(json_str) + return json_data + + +@trace_error_decorator +async def get_kuaishou_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + } + if cookies: + headers['Cookie'] = cookies + try: + html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers) + except Exception as e: + print(f"Failed to fetch data from {url}.{e}") + return {"type": 1, "is_live": False} + + try: + json_str = re.search('', html_str)[0] + json_data = json.loads(json_str) + rid = json_data['pageProps']['room']['roomInfo']['roomInfo']['rid'] + + headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0' + url2 = f'https://www.douyu.com/betard/{rid}' + json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + result = { + "anchor_name": json_data['room']['nickname'], + "is_live": False + } + if json_data['room']['videoLoop'] == 0 and json_data['room']['show_status'] == 1: + result["title"] = json_data['room']['room_name'].replace(' ', '') + result["is_live"] = True + result["room_id"] = json_data['room']['room_id'] + return result + + +@trace_error_decorator +async def get_douyu_stream_data(rid: str, rate: str = '-1', proxy_addr: OptionalStr = None, + cookies: OptionalStr = None) -> dict: + did = '10000000000000000000000000003306' + params_list = await get_token_js(rid, did, proxy_addr=proxy_addr) + headers = { + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + 'Referer': 'https://m.douyu.com/3125893?rid=3125893&dyshid=0-96003918aa5365bc6dcb4933000316p1&dyshci=181', + 'Cookie': 'dy_did=413b835d2ae00270f0c69f6400031601; acf_did=413b835d2ae00270f0c69f6400031601; Hm_lvt_e99aee90ec1b2106afe7ec3b199020a7=1692068308,1694003758; m_did=96003918aa5365bc6dcb4933000316p1; dy_teen_mode=%7B%22uid%22%3A%22472647365%22%2C%22status%22%3A0%2C%22birthday%22%3A%22%22%2C%22password%22%3A%22%22%7D; PHPSESSID=td59qi2fu2gepngb8mlehbeme3; acf_auth=94fc9s%2FeNj%2BKlpU%2Br8tZC3Jo9sZ0wz9ClcHQ1akL2Nhb6ZyCmfjVWSlR3LFFPuePWHRAMo0dt9vPSCoezkFPOeNy4mYcdVOM1a8CbW0ZAee4ipyNB%2Bflr58; dy_auth=bec5yzM8bUFYe%2FnVAjmUAljyrsX%2FcwRW%2FyMHaoArYb5qi8FS9tWR%2B96iCzSnmAryLOjB3Qbeu%2BBD42clnI7CR9vNAo9mva5HyyL41HGsbksx1tEYFOEwxSI; wan_auth37wan=5fd69ed5b27fGM%2FGoswWwDo%2BL%2FRMtnEa4Ix9a%2FsH26qF0sR4iddKMqfnPIhgfHZUqkAk%2FA1d8TX%2B6F7SNp7l6buIxAVf3t9YxmSso8bvHY0%2Fa6RUiv8; acf_uid=472647365; acf_username=472647365; acf_nickname=%E7%94%A8%E6%88%B776576662; acf_own_room=0; acf_groupid=1; acf_phonestatus=1; acf_avatar=https%3A%2F%2Fapic.douyucdn.cn%2Fupload%2Favatar%2Fdefault%2F24_; acf_ct=0; acf_ltkid=25305099; acf_biz=1; acf_stk=90754f8ed18f0c24; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=1694003778' + } + if cookies: + headers['Cookie'] = cookies + + data = { + 'v': params_list[0], + 'did': params_list[1], + 'tt': params_list[2], + 'sign': params_list[3], # 10分钟有效期 + 'ver': '22011191', + 'rid': rid, + 'rate': rate, # 0蓝光、3超清、2高清、-1默认 + } + + # app_api = 'https://m.douyu.com/hgapi/livenc/room/getStreamUrl' + app_api = f'https://www.douyu.com/lapi/live/getH5Play/{rid}' + json_str = await async_req(url=app_api, proxy_addr=proxy_addr, headers=headers, data=data) + json_data = json.loads(json_str) + return json_data + + +@trace_error_decorator +async def get_yy_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Referer': 'https://www.yy.com/', + 'Cookie': 'hd_newui=0.2103068903976506; hdjs_session_id=0.4929014850884579; hdjs_session_time=1694004002636; hiido_ui=0.923076230899782' + } + if cookies: + headers['Cookie'] = cookies + + html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers) + anchor_name = re.search('nick: "(.*?)",\n\\s+logo', html_str).group(1) + cid = re.search('sid : "(.*?)",\n\\s+ssid', html_str, re.DOTALL).group(1) + + data = '{"head":{"seq":1701869217590,"appidstr":"0","bidstr":"121","cidstr":"' + cid + '","sidstr":"' + cid + '","uid64":0,"client_type":108,"client_ver":"5.17.0","stream_sys_ver":1,"app":"yylive_web","playersdk_ver":"5.17.0","thundersdk_ver":"0","streamsdk_ver":"5.17.0"},"client_attribute":{"client":"web","model":"web0","cpu":"","graphics_card":"","os":"chrome","osversion":"0","vsdk_version":"","app_identify":"","app_version":"","business":"","width":"1920","height":"1080","scale":"","client_type":8,"h265":0},"avp_parameter":{"version":1,"client_type":8,"service_type":0,"imsi":0,"send_time":1701869217,"line_seq":-1,"gear":4,"ssl":1,"stream_format":0}}' + data_bytes = data.encode('utf-8') + params = { + "uid": "0", + "cid": cid, + "sid": cid, + "appid": "0", + "sequence": "1701869217590", + "encode": "json" + } + url2 = f'https://stream-manager.yy.com/v3/channel/streams?{urllib.parse.urlencode(params)}' + json_str = await async_req(url=url2, data=data_bytes, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + json_data['anchor_name'] = anchor_name + + params = { + 'uid': '', + 'sid': cid, + 'ssid': cid, + '_': int(time.time() * 1000), + } + detail_api = f'https://www.yy.com/live/detail?{urllib.parse.urlencode(params)}' + json_str2 = await async_req(detail_api, proxy_addr=proxy_addr, headers=headers) + json_data2 = json.loads(json_str2) + json_data['title'] = json_data2['data']['roomName'] + return json_data + + +@trace_error_decorator +async def get_bilibili_room_info_h5(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> str: + headers = { + 'user-agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) ' + 'SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', + 'accept-language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'cookie': '', + 'origin': 'https://live.bilibili.com', + 'referer': 'https://live.bilibili.com/26066074', + } + if cookies: + headers['cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + api = f'https://api.live.bilibili.com/xlive/web-room/v1/index/getH5InfoByRoom?room_id={room_id}' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + room_info = json.loads(json_str) + title = room_info['data']['room_info']['title'] if room_info.get('data') else '' + return title + + +@trace_error_decorator +async def get_bilibili_room_info(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + } + if cookies: + headers['Cookie'] = cookies + + try: + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + json_str = await async_req(f'https://api.live.bilibili.com/room/v1/Room/room_init?id={room_id}', + proxy_addr=proxy_addr, headers=headers) + room_info = json.loads(json_str) + uid = room_info['data']['uid'] + live_status = True if room_info['data']['live_status'] == 1 else False + + api = f'https://api.live.bilibili.com/live_user/v1/Master/info?uid={uid}' + json_str2 = await async_req(url=api, proxy_addr=proxy_addr, headers=headers) + anchor_info = json.loads(json_str2) + anchor_name = anchor_info['data']['info']['uname'] + + title = await get_bilibili_room_info_h5(url, proxy_addr, cookies) + return {"anchor_name": anchor_name, "live_status": live_status, "room_url": url, "title": title} + except Exception as e: + print(e) + return {"anchor_name": '', "live_status": False, "room_url": url} + + +@trace_error_decorator +async def get_bilibili_stream_data(url: str, qn: str = '10000', platform: str = 'web', proxy_addr: OptionalStr = None, + cookies: OptionalStr = None) -> OptionalStr: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'origin': 'https://live.bilibili.com', + 'referer': 'https://live.bilibili.com/26066074', + } + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + params = { + 'cid': room_id, + 'qn': qn, + 'platform': platform, + } + play_api = f'https://api.live.bilibili.com/room/v1/Room/playUrl?{urllib.parse.urlencode(params)}' + json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + if json_data and json_data['code'] == 0: + for i in json_data['data']['durl']: + if 'd1--cn-gotcha' in i['url']: + return i['url'] + return json_data['data']['durl'][-1]['url'] + else: + params = { + "room_id": room_id, + "protocol": "0,1", + "format": "0,1,2", + "codec": "0,1,2", + "qn": qn, + "platform": "web", + "ptype": "8", + "dolby": "5", + "panorama": "1", + "hdr_type": "0,1" + } + + # 此接口因网页上有限制, 需要配置登录后的cookie才能获取最高画质 + api = f'https://api.live.bilibili.com/xlive/web-room/v2/index/getRoomPlayInfo?{urllib.parse.urlencode(params)}' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + if json_data['data']['live_status'] == 0: + print("The anchor did not start broadcasting.") + return + playurl_info = json_data['data']['playurl_info'] + format_list = playurl_info['playurl']['stream'][0]['format'] + stream_data_list = format_list[0]['codec'] + sorted_stream_list = sorted(stream_data_list, key=itemgetter("current_qn"), reverse=True) + # qn: 30000=杜比 20000=4K 10000=原画 400=蓝光 250=超清 150=高清 80=流畅 + video_quality_options = {'10000': 0, '400': 1, '250': 2, '150': 3, '80': 4} + qn_count = len(sorted_stream_list) + select_stream_index = min(video_quality_options[qn], qn_count - 1) + stream_data: dict = sorted_stream_list[select_stream_index] + base_url = stream_data['base_url'] + host = stream_data['url_info'][0]['host'] + extra = stream_data['url_info'][0]['extra'] + m3u8_url = host + base_url + extra + return m3u8_url + +@trace_error_decorator +async def get_xhs_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + 'xy-common-params': 'platform=iOS&sid=session.1722166379345546829388', + 'referer': 'https://app.xhs.cn/', + } + if cookies: + headers['Cookie'] = cookies + + if "xhslink.com" in url: + url = await async_req(url, proxy_addr=proxy_addr, headers=headers, redirect_url=True) + + host_id = get_params(url, "host_id") + user_id = re.search("/user/profile/(.*?)(?=/|\\?|$)", url) + user_id = user_id.group(1) if user_id else host_id + result = {"anchor_name": '', "is_live": False} + html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers) + match_data = re.search("", html_str) + + if match_data: + json_str = match_data.group(1).replace("undefined", "null") + json_data = json.loads(json_str) + + if json_data.get("liveStream"): + stream_data = json_data["liveStream"] + if stream_data.get("liveStatus") == "success": + room_info = stream_data["roomData"]["roomInfo"] + title = room_info.get("roomTitle") + if title and "回放" not in title: + live_link = room_info["deeplink"] + anchor_name = get_params(live_link, "host_nickname") + flv_url = get_params(live_link, "flvUrl") + room_id = flv_url.split('live/')[1].split('.')[0] + flv_url = f"http://live-source-play.xhscdn.com/live/{room_id}.flv" + m3u8_url = flv_url.replace('.flv', '.m3u8') + result |= { + "anchor_name": anchor_name, + "is_live": True, + "title": title, + "flv_url": flv_url, + "m3u8_url": m3u8_url, + 'record_url': flv_url + } + return result + + profile_url = f"https://www.xiaohongshu.com/user/profile/{user_id}" + html_str = await async_req(profile_url, proxy_addr=proxy_addr, headers=headers) + anchor_name = re.search("@(.*?) 的个人主页", html_str) + if anchor_name: + result["anchor_name"] = anchor_name.group(1) + + return result + + +@trace_error_decorator +async def get_bigo_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'Referer': 'https://www.bigo.tv/', + } + if cookies: + headers['Cookie'] = cookies + + if 'bigo.tv' not in url: + html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers) + web_url = re.search( + '', + html_str).group(1) + room_id = web_url.split('&h=')[-1] + else: + if '&h=' in url: + room_id = url.split('&h=')[-1] + else: + room_id = re.search('www.bigo.tv/cn/(\\w+)', url).group(1) + + data = {'siteId': room_id} # roomId + url2 = 'https://ta.bigo.tv/official_website/studio/getInternalStudioInfo' + json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers, data=data) + json_data = json.loads(json_str) + anchor_name = json_data['data']['nick_name'] + live_status = json_data['data']['alive'] + result = {"anchor_name": anchor_name, "is_live": False} + + if live_status == 1: + live_title = json_data['data']['roomTopic'] + m3u8_url = json_data['data']['hls_src'] + result['m3u8_url'] = m3u8_url + result['record_url'] = m3u8_url + result |= {"title": live_title, "is_live": True, "m3u8_url": m3u8_url, 'record_url': m3u8_url} + elif result['anchor_name'] == '': + html_str = await async_req(url=f'https://www.bigo.tv/cn/{room_id}', proxy_addr=proxy_addr, headers=headers) + result['anchor_name'] = re.search('欢迎来到(.*?)的直播间', html_str, re.DOTALL).group(1) + + return result + + +@trace_error_decorator +async def get_blued_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + } + if cookies: + headers['Cookie'] = cookies + + html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers) + json_str = re.search('decodeURIComponent\\(\"(.*?)\"\\)\\),window\\.Promise', html_str, re.DOTALL).group(1) + json_str = urllib.parse.unquote(json_str) + json_data = json.loads(json_str) + anchor_name = json_data['userInfo']['name'] + live_status = json_data['userInfo']['onLive'] + result = {"anchor_name": anchor_name, "is_live": False} + + if live_status: + m3u8_url = json_data['liveInfo']['liveUrl'] + result |= {"is_live": True, "m3u8_url": m3u8_url, 'record_url': m3u8_url} + return result + + +@trace_error_decorator +async def login_sooplive(username: str, password: str, proxy_addr: OptionalStr = None) -> OptionalStr: + if len(username) < 6 or len(password) < 10: + raise RuntimeError("sooplive login failed! Please enter the correct account and password for the sooplive " + "platform in the config.ini file.") + + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'Origin': 'https://play.sooplive.co.kr', + 'Referer': 'https://play.sooplive.co.kr/superbsw123/277837074', + } + + data = { + 'szWork': 'login', + 'szType': 'json', + 'szUid': username, + 'szPassword': password, + 'isSaveId': 'true', + 'isSavePw': 'true', + 'isSaveJoin': 'true', + 'isLoginRetain': 'Y', + } + + url = 'https://login.sooplive.co.kr/app/LoginAction.php' + + try: + cookie_dict = await async_req(url, proxy_addr=proxy_addr, headers=headers, + data=data, return_cookies=True, timeout=20) + cookie_str = '; '.join([f"{k}={v}" for k, v in cookie_dict.items()]) + return cookie_str + except Exception as e: + print(f"An error occurred during login: {e}") + raise Exception( + "sooplive login failed, please check if the account password in the configuration file is correct." + ) + + +@trace_error_decorator +async def get_sooplive_cdn_url(broad_no: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Origin': 'https://play.sooplive.co.kr', + 'Referer': 'https://play.sooplive.co.kr/oul282/249469582', + 'Content-Type': 'application/x-www-form-urlencoded', + } + if cookies: + headers['Cookie'] = cookies + + params = { + 'return_type': 'gcp_cdn', + 'use_cors': 'false', + 'cors_origin_url': 'play.sooplive.co.kr', + 'broad_key': f'{broad_no}-common-master-hls', + 'time': '8361.086329376785', + } + + url2 = 'http://livestream-manager.sooplive.co.kr/broad_stream_assign.html?' + urllib.parse.urlencode(params) + json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_data = json.loads(json_str) + + return json_data + + +@trace_error_decorator +async def get_sooplive_tk(url: str, rtype: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> str | tuple: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0', + 'Origin': 'https://play.sooplive.co.kr', + 'Referer': 'https://play.sooplive.co.kr/secretx/250989857', + 'Content-Type': 'application/x-www-form-urlencoded', + } + + if cookies: + headers['Cookie'] = cookies + + split_url = url.split('/') + bj_id = split_url[3] if len(split_url) < 6 else split_url[5] + room_password = get_params(url, "pwd") + if not room_password: + room_password = '' + data = { + 'bid': bj_id, + 'bno': '', + 'type': rtype, + 'pwd': room_password, + 'player_type': 'html5', + 'stream_type': 'common', + 'quality': 'master', + 'mode': 'landing', + 'from_api': '0', + 'is_revive': 'false', + } + + url2 = f'https://live.sooplive.co.kr/afreeca/player_live_api.php?bjid={bj_id}' + json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers, data=data, abroad=True) + json_data = json.loads(json_str) + + if rtype == 'aid': + token = json_data["CHANNEL"]["AID"] + return token + else: + bj_name = json_data['CHANNEL']['BJNICK'] + bj_id = json_data['CHANNEL']['BJID'] + return f"{bj_name}-{bj_id}", json_data['CHANNEL']['BNO'] + + +@trace_error_decorator +async def get_sooplive_stream_data( + url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None, + username: OptionalStr = None, password: OptionalStr = None +) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Referer': 'https://m.sooplive.co.kr/', + 'Content-Type': 'application/x-www-form-urlencoded', + } + if cookies: + headers['Cookie'] = cookies + + split_url = url.split('/') + bj_id = split_url[3] if len(split_url) < 6 else split_url[5] + + data = { + 'bj_id': bj_id, + 'broad_no': '', + 'agent': 'web', + 'confirm_adult': 'true', + 'player_type': 'webm', + 'mode': 'live', + } + + url2 = 'http://api.m.sooplive.co.kr/broad/a/watch' + + json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers, data=data, abroad=True) + json_data = json.loads(json_str) + + if 'user_nick' in json_data['data']: + anchor_name = json_data['data']['user_nick'] + if "bj_id" in json_data['data']: + anchor_name = f"{anchor_name}-{json_data['data']['bj_id']}" + else: + anchor_name = '' + + result = {"anchor_name": anchor_name or '' ,"is_live": False} + + async def get_url_list(m3u8: str) -> List[str]: + resp = await async_req(url=m3u8, proxy_addr=proxy_addr, headers=headers, abroad=True) + play_url_list = [] + url_prefix = m3u8.rsplit('/', maxsplit=1)[0] + '/' + for i in resp.split('\n'): + if i.startswith('auth_playlist'): + play_url_list.append(url_prefix + i.strip()) + bandwidth_pattern = re.compile(r'BANDWIDTH=(\d+)') + bandwidth_list = bandwidth_pattern.findall(resp) + url_to_bandwidth = {purl: int(bandwidth) for bandwidth, purl in zip(bandwidth_list, play_url_list)} + play_url_list = sorted(play_url_list, key=lambda purl: url_to_bandwidth[purl], reverse=True) + return play_url_list + + if not anchor_name: + async def handle_login() -> OptionalStr: + cookie = await login_sooplive(username, password, proxy_addr=proxy_addr) + if 'AuthTicket=' in cookie: + print("sooplive platform login successful! Starting to fetch live streaming data...") + return cookie + + async def fetch_data(cookie, _result) -> dict: + aid_token = await get_sooplive_tk(url, rtype='aid', proxy_addr=proxy_addr, cookies=cookie) + _anchor_name, _broad_no = await get_sooplive_tk(url, rtype='info', proxy_addr=proxy_addr, cookies=cookie) + _view_url_data = await get_sooplive_cdn_url(_broad_no, proxy_addr=proxy_addr) + _view_url = _view_url_data['view_url'] + _m3u8_url = _view_url + '?aid=' + aid_token + _result |= { + "anchor_name": _anchor_name, + "is_live": True, + "m3u8_url": _m3u8_url, + 'play_url_list': await get_url_list(_m3u8_url), + 'new_cookies': cookie + } + return _result + + if json_data['data']['code'] == -3001: + print("sooplive live stream failed to retrieve, the live stream just ended.") + return result + + elif json_data['data']['code'] == -3002: + print("sooplive live stream retrieval failed, the live needs 19+, you are not logged in.") + print("Attempting to log in to the sooplive live streaming platform with your account and password, " + "please ensure it is configured.") + new_cookie = await handle_login() + if new_cookie and len(new_cookie) > 0: + return await fetch_data(new_cookie, result) + raise RuntimeError("sooplive login failed, please check if the account and password are correct") + + elif json_data['data']['code'] == -3004: + if cookies and len(cookies) > 0: + return await fetch_data(cookies, result) + else: + raise RuntimeError("sooplive login failed, please check if the account and password are correct") + elif json_data['data']['code'] == -6001: + print("error message:Please check if the input sooplive live room address " + "is correct.") + return result + if json_data['result'] == 1 and anchor_name: + broad_no = json_data['data']['broad_no'] + hls_authentication_key = json_data['data']['hls_authentication_key'] + view_url_data = await get_sooplive_cdn_url(broad_no, proxy_addr=proxy_addr) + view_url = view_url_data['view_url'] + m3u8_url = view_url + '?aid=' + hls_authentication_key + result |= {'is_live': True, 'm3u8_url': m3u8_url, 'play_url_list': await get_url_list(m3u8_url)} + result['new_cookies'] = None + return result + + +@trace_error_decorator +async def get_netease_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://cc.163.com/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + if cookies: + headers['Cookie'] = cookies + url = url + '/' if url[-1] != '/' else url + + html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers) + json_str = re.search('', + html_str, re.DOTALL).group(1) + json_data = json.loads(json_str) + room_data = json_data['props']['pageProps']['roomInfoInitData'] + live_data = room_data['live'] + result = {"is_live": False} + live_status = live_data.get('status') == 1 + result["anchor_name"] = live_data.get('nickname', room_data.get('nickname')) + if live_status: + result |= { + 'is_live': True, + 'title': live_data['title'], + 'stream_list': live_data.get('quickplay'), + 'm3u8_url': live_data.get('sharefile') + } + return result + + +@trace_error_decorator +async def get_qiandurebo_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,' + 'application/signed-exchange;v=b3;q=0.7', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://qiandurebo.com/web/index.php', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + if cookies: + headers['Cookie'] = cookies + + html_str = await async_req(url=url, proxy_addr=proxy_addr, headers=headers) + data = re.search('var user = (.*?)\r\n\\s+user\\.play_url', html_str, re.DOTALL).group(1) + anchor_name = re.findall('"zb_nickname": "(.*?)",\r\n', data) + + result = {"anchor_name": "", "is_live": False} + if len(anchor_name) > 0: + result['anchor_name'] = anchor_name[0] + play_url = re.findall('"play_url": "(.*?)",\r\n', data) + + if len(play_url) > 0 and 'common-text-center" style="display:block' not in html_str: + result |= { + 'anchor_name': anchor_name[0], + 'is_live': True, + 'flv_url': play_url[0], + 'record_url': play_url[0] + } + return result + + +@trace_error_decorator +async def get_pandatv_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'origin': 'https://www.pandalive.co.kr', + 'referer': 'https://www.pandalive.co.kr/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + if cookies: + headers['Cookie'] = cookies + + user_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + url2 = 'https://api.pandalive.co.kr/v1/live/play' + data = { + 'userId': user_id, + 'info': 'media fanGrade', + } + room_password = get_params(url, "pwd") + if not room_password: + room_password = '' + data2 = { + 'action': 'watch', + 'userId': user_id, + 'password': room_password, + 'shareLinkType': '', + } + + result = {"anchor_name": "", "is_live": False} + json_str = await async_req('https://api.pandalive.co.kr/v1/member/bj', + proxy_addr=proxy_addr, headers=headers, data=data, abroad=True) + json_data = json.loads(json_str) + if "bjInfo" not in json_data: + raise RuntimeError(json_data.get("message", 'Unknown error')) + anchor_id = json_data['bjInfo']['id'] + anchor_name = f"{json_data['bjInfo']['nick']}-{anchor_id}" + result['anchor_name'] = anchor_name + live_status = 'media' in json_data + + if live_status: + json_str = await async_req(url2, proxy_addr=proxy_addr, headers=headers, data=data2, abroad=True) + json_data = json.loads(json_str) + if 'errorData' in json_data: + if json_data['errorData']['code'] == 'needAdult': + raise RuntimeError(f"{url} The live room requires login and is only accessible to adults. Please " + f"correctly fill in the login cookie in the configuration file.") + else: + raise RuntimeError(json_data['errorData']['code'], json_data['message']) + play_url = json_data['PlayList']['hls'][0]['url'] + play_url_list = await get_play_url_list(m3u8=play_url, proxy=proxy_addr, header=headers, abroad=True) + result |= {'is_live': True, 'm3u8_url': play_url, 'play_url_list': play_url_list} + return result + + +@trace_error_decorator +async def get_maoerfm_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://fm.missevan.com/live/868895007', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + url2 = f'https://fm.missevan.com/api/v2/live/{room_id}' + + json_str = await async_req(url=url2, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + + anchor_name = json_data['info']['creator']['username'] + live_status = False + if 'room' in json_data['info']: + live_status = json_data['info']['room']['status']['broadcasting'] + + result = {"anchor_name": anchor_name, "is_live": live_status} + if live_status: + stream_list = json_data['info']['room']['channel'] + m3u8_url = stream_list['hls_pull_url'] + flv_url = stream_list['flv_pull_url'] + title = json_data['info']['room']['name'] + result |= {'is_live': True, 'title': title, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_winktv_bj_info(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> tuple: + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'content-type': 'application/x-www-form-urlencoded', + 'referer': 'https://www.winktv.co.kr/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + if cookies: + headers['Cookie'] = cookies + user_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + data = { + 'userId': user_id, + 'info': 'media', + } + + info_api = 'https://api.winktv.co.kr/v1/member/bj' + json_str = await async_req(url=info_api, proxy_addr=proxy_addr, headers=headers, data=data, abroad=True) + json_data = json.loads(json_str) + live_status = 'media' in json_data + anchor_id = json_data['bjInfo']['id'] + anchor_name = f"{json_data['bjInfo']['nick']}-{anchor_id}" + return anchor_name, live_status + + +@trace_error_decorator +async def get_winktv_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'content-type': 'application/x-www-form-urlencoded', + 'referer': 'https://www.winktv.co.kr', + 'origin': 'https://www.winktv.co.kr', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + + } + if cookies: + headers['Cookie'] = cookies + user_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + room_password = get_params(url, "pwd") + if not room_password: + room_password = '' + data = { + 'action': 'watch', + 'userId': user_id, + 'password': room_password, + 'shareLinkType': '', + } + + anchor_name, live_status = await get_winktv_bj_info(url=url, proxy_addr=proxy_addr, cookies=cookies) + result = {"anchor_name": anchor_name, "is_live": live_status} + if live_status: + play_api = 'https://api.winktv.co.kr/v1/live/play' + json_str = await async_req(url=play_api, proxy_addr=proxy_addr, headers=headers, data=data, abroad=True) + if '403: Forbidden' in json_str: + raise ConnectionError(f"Your network has been banned from accessing WinkTV ({json_str})") + json_data = json.loads(json_str) + if 'errorData' in json_data: + if json_data['errorData']['code'] == 'needAdult': + raise RuntimeError(f"{url} The live stream is only accessible to logged-in adults. Please ensure that " + f"the cookie is correctly filled in the configuration file after logging in.") + else: + raise RuntimeError(json_data['errorData']['code'], json_data['message']) + m3u8_url = json_data['PlayList']['hls'][0]['url'] + play_url_list = await get_play_url_list(m3u8=m3u8_url, proxy=proxy_addr, header=headers, abroad=True) + result['m3u8_url'] = m3u8_url + result['play_url_list'] = play_url_list + return result + + +@trace_error_decorator +async def login_flextv(username: str, password: str, proxy_addr: OptionalStr = None) -> OptionalStr: + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'content-type': 'application/json;charset=UTF-8', + 'referer': 'https://www.flextv.co.kr/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + + data = { + 'loginId': username, + 'password': password, + 'loginKeep': True, + 'saveId': True, + 'device': 'PCWEB', + } + + url = 'https://api.flextv.co.kr/v2/api/auth/signin' + + try: + print("Logging into FlexTV platform...") + cookie_dict = await async_req(url, proxy_addr=proxy_addr, headers=headers, json_data=data, + return_cookies=True, timeout=20) + + if cookie_dict and 'flx_oauth_access' in cookie_dict: + cookie_str = '; '.join([f"{k}={v}" for k, v in cookie_dict.items()]) + return cookie_str + else: + print("Please check if the FlexTV account and password in the configuration file are correct.") + return None + + except Exception as e: + print(f"FlexTV login request exception: {e}") + raise Exception( + "FlexTV login failed, please check if the account and password in the configuration file are correct." + ) + + +async def get_flextv_stream_url( + url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None +) -> str: + async def fetch_data(cookie) -> dict: + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://www.flextv.co.kr/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + user_id = url.split('/live')[0].rsplit('/', maxsplit=1)[-1] + if cookie: + headers['Cookie'] = cookie + play_api = f'https://api.flextv.co.kr/api/channels/{user_id}/stream?option=all' + json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers, abroad=True) + if 'HTTP Error 400: Bad Request' in json_str: + raise ConnectionError( + "Failed to retrieve FlexTV live streaming data, please switch to a different proxy and try again." + ) + return json.loads(json_str) + + json_data = await fetch_data(cookies) + if 'sources' in json_data and len(json_data['sources']) > 0: + play_url = json_data['sources'][0]['url'] + return play_url + + +@trace_error_decorator +async def get_flextv_stream_data( + url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None, + username: OptionalStr = None, password: OptionalStr = None +) -> dict: + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://www.flextv.co.kr/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + if cookies: + headers['Cookie'] = cookies + user_id = url.split('/live')[0].rsplit('/', maxsplit=1)[-1] + result = {"anchor_name": '', "is_live": False} + new_cookies = None + try: + url2 = f'https://www.flextv.co.kr/channels/{user_id}/live' + html_str = await async_req(url2, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_str = re.search('', html_str).group(1) + json_data = json.loads(json_str) + channel_data = json_data['props']['pageProps']['channel'] + login_need = 'message' in channel_data and '로그인후 이용이 가능합니다.' in channel_data.get('message') + if login_need: + print("FlexTV live stream retrieval failed [not logged in]: 19+ live streams are only available for " + "logged-in adults.") + print("Attempting to log in to the FlexTV live streaming platform, please ensure your account and " + "password are correctly filled in the configuration file.") + if len(username) < 6 or len(password) < 8: + raise RuntimeError("FlexTV登录失败!请在config.ini配置文件中填写正确的FlexTV平台的账号和密码") + new_cookies = await login_flextv(username, password, proxy_addr=proxy_addr) + if new_cookies: + print("Logged into FlexTV platform successfully! Starting to fetch live streaming data...") + else: + raise RuntimeError("FlexTV login failed") + cookies = new_cookies if new_cookies else cookies + headers['Cookie'] = cookies + html_str = await async_req(url2, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_str = re.search('', html_str).group(1) + json_data = json.loads(json_str) + channel_data = json_data['props']['pageProps']['channel'] + + live_status = 'message' not in channel_data + if live_status: + anchor_id = channel_data['owner']['loginId'] + anchor_name = f"{channel_data['owner']['nickname']}-{anchor_id}" + result["anchor_name"] = anchor_name + play_url = await get_flextv_stream_url(url=url, proxy_addr=proxy_addr, cookies=cookies) + if play_url: + play_url_list = await get_play_url_list(m3u8=play_url, proxy=proxy_addr, header=headers, abroad=True) + if play_url_list: + result['m3u8_url'] = play_url + result['play_url_list'] = play_url_list + result['is_live'] = True + else: + url2 = f'https://www.flextv.co.kr/channels/{user_id}' + html_str = await async_req(url2, proxy_addr=proxy_addr, headers=headers, abroad=True) + anchor_name = re.search(' tuple: + # 本算法参考项目:https://github.com/785415581/MusicBox/blob/b8f716d43d/doc/analysis/analyze_captured_data.md + + modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee' \ + '341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe487' \ + '5d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7' + nonce = b'0CoJUm6Qyw8W8jud' + public_key = '010001' + from Crypto.Cipher import AES + from Crypto.Util.Padding import pad + import base64 + import binascii + import secrets + + def create_secret_key(size: int) -> bytes: + charset = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?' + return ''.join(secrets.choice(charset) for _ in range(size)).encode('utf-8') + + def aes_encrypt(_text: str | bytes, _sec_key: str | bytes) -> bytes: + if isinstance(_text, str): + _text = _text.encode('utf-8') + if isinstance(_sec_key, str): + _sec_key = _sec_key.encode('utf-8') + _sec_key = _sec_key[:16] # 16 (AES-128), 24 (AES-192), or 32 (AES-256) bytes + iv = bytes('0102030405060708', 'utf-8') + encryptor = AES.new(_sec_key, AES.MODE_CBC, iv) + padded_text = pad(_text, AES.block_size) + ciphertext = encryptor.encrypt(padded_text) + encoded_ciphertext = base64.b64encode(ciphertext) + return encoded_ciphertext + + def rsa_encrypt(_text: str | bytes, pub_key: str, mod: str) -> str: + if isinstance(_text, str): + _text = _text.encode('utf-8') + text_reversed = _text[::-1] + text_int = int(binascii.hexlify(text_reversed), 16) + encrypted_int = pow(text_int, int(pub_key, 16), int(mod, 16)) + return format(encrypted_int, 'x').zfill(256) + + sec_key = create_secret_key(16) + enc_text = aes_encrypt(aes_encrypt(json.dumps(text), nonce), sec_key) + enc_sec_key = rsa_encrypt(sec_key, public_key, modulus) + return enc_text.decode(), enc_sec_key + + +async def get_looklive_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + """ + 通过PC网页端的接口获取完整直播源,只有params和encSecKey这两个加密请求参数。 + params: 由两次AES加密完成 + ncSecKey: 由一次自写的加密函数完成,值可固定 + """ + + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0', + 'Accept': 'application/json, text/javascript', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Referer': 'https://look.163.com/', + } + + if cookies: + headers['Cookie'] = cookies + + room_id = re.search('live\\?id=(.*?)&', url).group(1) + params, secretkey = get_looklive_secret_data({"liveRoomNo": room_id}) + request_data = {'params': params, 'encSecKey': secretkey} + api = 'https://api.look.163.com/weapi/livestream/room/get/v3' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers, data=request_data) + json_data = json.loads(json_str) + anchor_name = json_data['data']['anchor']['nickName'] + live_status = json_data['data']['liveStatus'] + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 1: + result["is_live"] = True + if json_data['data']['roomInfo']['liveType'] == 1: + print("Look live currently only supports audio live streaming, not video live streaming!") + else: + play_url_list = json_data['data']['roomInfo']['liveUrl'] + live_title = json_data['data']['roomInfo']['title'] + result |= { + "title": live_title, + "flv_url": play_url_list['httpPullUrl'], + "m3u8_url": play_url_list['hlsPullUrl'], + "record_url": play_url_list['hlsPullUrl'], + } + return result + + +@trace_error_decorator +async def login_popkontv( + username: str, password: str, proxy_addr: OptionalStr = None, code: OptionalStr = 'P-00001' +) -> tuple: + headers = { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Authorization': 'Basic FpAhe6mh8Qtz116OENBmRddbYVirNKasktdXQiuHfm88zRaFydTsFy63tzkdZY0u', + 'Content-Type': 'application/json', + 'Origin': 'https://www.popkontv.com', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + + data = { + 'partnerCode': code, + 'signId': username, + 'signPwd': password, + } + + url = 'https://www.popkontv.com/api/proxy/member/v1/login' + + try: + proxy_addr = utils.handle_proxy_addr(proxy_addr) + async with httpx.AsyncClient(proxy=proxy_addr, timeout=20, verify=False) as client: + response = await client.post(url, json=data, headers=headers) + response.raise_for_status() + + json_data = response.json() + login_status_code = json_data.get("statusCd") + + if login_status_code == 'E4010': + raise Exception("popkontv login failed, please reconfigure the correct login account or password!") + elif login_status_code == 'S2000': + token = json_data['data'].get("token") + partner_code = json_data['data'].get("partnerCode") + return token, partner_code + else: + raise Exception(f"popkontv login failed, {json_data.get('statusMsg', 'unknown error')}") + except httpx.HTTPStatusError as e: + print(f"HTTP status error occurred during login: {e.response.status_code}") + raise + except Exception as e: + print(f"An exception occurred during popkontv login: {e}") + raise + + +@trace_error_decorator +async def get_popkontv_stream_data( + url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None, + username: OptionalStr = None, code: OptionalStr = 'P-00001' +) -> tuple: + headers = { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Content-Type': 'application/json', + 'Origin': 'https://www.popkontv.com', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + if cookies: + headers['Cookie'] = cookies + if 'mcid' in url: + anchor_id = re.search('mcid=(.*?)&', url).group(1) + else: + anchor_id = re.search('castId=(.*?)(?=&|$)', url).group(1) + + data = { + 'partnerCode': code, + 'searchKeyword': anchor_id, + 'signId': username, + } + + api = 'https://www.popkontv.com/api/proxy/broadcast/v1/search/all' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers, json_data=data, abroad=True) + json_data = json.loads(json_str) + + partner_code = '' + anchor_name = 'Unknown' + for item in json_data['data']['broadCastList']: + if item['mcSignId'] == anchor_id: + mc_name = item['nickName'] + anchor_name = f"{mc_name}-{anchor_id}" + partner_code = item['mcPartnerCode'] + break + + if not partner_code: + if 'mcPartnerCode' in url: + regex_result = re.search('mcPartnerCode=(P-\\d+)', url) + else: + regex_result = re.search('partnerCode=(P-\\d+)', url) + partner_code = regex_result.group(1) if regex_result else code + notices_url = f'https://www.popkontv.com/channel/notices?mcid={anchor_id}&mcPartnerCode={partner_code}' + notices_response = await async_req(notices_url, proxy_addr=proxy_addr, headers=headers, abroad=True) + mc_name_match = re.search(r'"mcNickName":"([^"]+)"', notices_response) + mc_name = mc_name_match.group(1) if mc_name_match else 'Unknown' + anchor_name = f"{anchor_id}-{mc_name}" + + live_url = f"https://www.popkontv.com/live/view?castId={anchor_id}&partnerCode={partner_code}" + html_str2 = await async_req(live_url, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_str2 = re.search('', html_str2).group(1) + json_data2 = json.loads(json_str2) + if 'mcData' in json_data2['props']['pageProps']: + room_data = json_data2['props']['pageProps']['mcData']['data'] + is_private = room_data['mc_isPrivate'] + cast_start_date_code = room_data['mc_castStartDate'] + mc_sign_id = room_data['mc_signId'] + cast_type = room_data['castType'] + return anchor_name, [cast_start_date_code, partner_code, mc_sign_id, cast_type, is_private] + else: + return anchor_name, None + + +@trace_error_decorator +async def get_popkontv_stream_url( + url: str, + proxy_addr: OptionalStr = None, + access_token: OptionalStr = None, + username: OptionalStr = None, + password: OptionalStr = None, + partner_code: OptionalStr = 'P-00001' +) -> dict: + headers = { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'ClientKey': 'Client FpAhe6mh8Qtz116OENBmRddbYVirNKasktdXQiuHfm88zRaFydTsFy63tzkdZY0u', + 'Content-Type': 'application/json', + 'Origin': 'https://www.popkontv.com', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + + if access_token: + headers['Authorization'] = f'Bearer {access_token}' + + anchor_name, room_info = await get_popkontv_stream_data( + url, proxy_addr=proxy_addr, code=partner_code, username=username) + result = {"anchor_name": anchor_name, "is_live": False} + new_token = None + if room_info: + cast_start_date_code, cast_partner_code, mc_sign_id, cast_type, is_private = room_info + result["is_live"] = True + room_password = get_params(url, "pwd") + if int(is_private) != 0 and not room_password: + raise RuntimeError(f"Failed to retrieve live room data because {anchor_name}'s room is a private room. " + f"Please configure the room password and try again.") + + async def fetch_data(header: dict = None, code: str = None) -> str: + data = { + 'androidStore': 0, + 'castCode': f'{mc_sign_id}-{cast_start_date_code}', + 'castPartnerCode': cast_partner_code, + 'castSignId': mc_sign_id, + 'castType': cast_type, + 'commandType': 0, + 'exePath': 5, + 'isSecret': is_private, + 'partnerCode': code, + 'password': room_password, + 'signId': username, + 'version': '4.6.2', + } + play_api = 'https://www.popkontv.com/api/proxy/broadcast/v1/castwatchonoffguest' + return await async_req(play_api, proxy_addr=proxy_addr, json_data=data, headers=header, abroad=True) + + json_str = await fetch_data(headers, partner_code) + + if 'HTTP Error 400' in json_str or 'statusCd":"E5000' in json_str: + print("Failed to retrieve popkontv live stream [token does not exist or has expired]: Please log in to " + "watch.") + print("Attempting to log in to the popkontv live streaming platform, please ensure your account " + "and password are correctly filled in the configuration file.") + if len(username) < 4 or len(password) < 10: + raise RuntimeError("popkontv login failed! Please enter the correct account and password for the " + "popkontv platform in the config.ini file.") + print("Logging into popkontv platform...") + new_access_token, new_partner_code = await login_popkontv( + username=username, password=password, proxy_addr=proxy_addr, code=partner_code + ) + if new_access_token and len(new_access_token) == 640: + print("Logged into popkontv platform successfully! Starting to fetch live streaming data...") + headers['Authorization'] = f'Bearer {new_access_token}' + new_token = f'Bearer {new_access_token}' + json_str = await fetch_data(headers, new_partner_code) + else: + raise RuntimeError("popkontv login failed, please check if the account and password are correct") + json_data = json.loads(json_str) + status_msg = json_data["statusMsg"] + if json_data['statusCd'] == "L000A": + print("Failed to retrieve live stream source,", status_msg) + raise RuntimeError("You are an unverified member. After logging into the popkontv official website, " + "please verify your mobile phone at the bottom of the 'My Page' > 'Edit My " + "Information' to use the service.") + elif json_data['statusCd'] == "L0001": + cast_start_date_code = int(cast_start_date_code) - 1 + json_str = await fetch_data(headers, partner_code) + json_data = json.loads(json_str) + m3u8_url = json_data['data']['castHlsUrl'] + result |= {"m3u8_url": m3u8_url, "record_url": m3u8_url} + elif json_data['statusCd'] == "L0000": + m3u8_url = json_data['data']['castHlsUrl'] + result |= {"m3u8_url": m3u8_url, "record_url": m3u8_url} + else: + raise RuntimeError("Failed to retrieve live stream source,", status_msg) + result['new_token'] = new_token + return result + + +@trace_error_decorator +async def login_twitcasting( + account_type: str, username: str, password: str, proxy_addr: OptionalStr = None, + cookies: OptionalStr = None +) -> OptionalStr: + headers = { + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Referer': 'https://twitcasting.tv/indexcaslogin.php?redir=%2Findexloginwindow.php%3Fnext%3D%252F&keep=1', + 'Cookie': 'hl=zh; did=04fb08f1b15d248644f1dfa82816d323; _ga=GA1.1.1021187740.1709706998; keep=1; mfadid=yrQiEB26ruRg7mlMavABMBZWdOddzojW; _ga_X8R46Y30YM=GS1.1.1709706998.1.1.1709712274.0.0.0', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + + if cookies: + headers['Cookie'] = cookies + + if account_type == "twitter": + login_url = 'https://twitcasting.tv/indexpasswordlogin.php' + login_api = 'https://twitcasting.tv/indexpasswordlogin.php?redir=/indexloginwindow.php?next=%2F&keep=1' + else: + login_url = 'https://twitcasting.tv/indexcaslogin.php?redir=%2F&keep=1' + login_api = 'https://twitcasting.tv/indexcaslogin.php?redir=/indexloginwindow.php?next=%2F&keep=1' + + html_str = await async_req(login_url, proxy_addr=proxy_addr, headers=headers) + cs_session_id = re.search('', html_str).group(1) + + data = { + 'username': username, + 'password': password, + 'action': 'login', + 'cs_session_id': cs_session_id, + } + try: + cookie_dict = await async_req(login_api, proxy_addr=proxy_addr, headers=headers, + json_data=data, return_cookies=True, timeout=20) + if 'tc_ss' in cookie_dict: + cookie = utils.dict_to_cookie_str(cookie_dict) + return cookie + except Exception as e: + print("TwitCasting login error,", e) + + +@trace_error_decorator +async def get_twitcasting_stream_url( + url: str, + proxy_addr: OptionalStr = None, + cookies: OptionalStr = None, + account_type: OptionalStr = None, + username: OptionalStr = None, + password: OptionalStr = None, +) -> dict: + headers = { + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Referer': 'https://twitcasting.tv/?ch0', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + } + + anchor_id = url.split('/')[3] + if cookies: + headers['Cookie'] = cookies + + async def get_data(header) -> tuple: + html_str = await async_req(url, proxy_addr=proxy_addr, headers=header) + anchor = re.search("(.*?) \\(@(.*?)\\) 的直播 - Twit", html_str) + title = re.search('<meta name="twitter:title" content="(.*?)">\n\\s+<meta', html_str) + status = re.search('data-is-onlive="(.*?)"\n\\s+data-view-mode', html_str) + movie_id = re.search('data-movie-id="(.*?)" data-audience-id', html_str) + return f'{anchor.group(1).strip()}-{anchor.group(2)}-{movie_id.group(1)}', status.group(1), title.group(1) + + result = {"anchor_name": '', "is_live": False} + new_cookie = None + try: + to_login = get_params(url, "login") + if to_login == 'true': + print("Attempting to log in to TwitCasting...") + new_cookie = await login_twitcasting( + account_type=account_type, username=username, password=password, proxy_addr=proxy_addr, cookies=cookies) + if not new_cookie: + raise RuntimeError("TwitCasting login failed, please check if the account password in the " + "configuration file is correct") + print("TwitCasting login successful! Starting to fetch data...") + headers['Cookie'] = new_cookie + anchor_name, live_status, live_title = get_data(headers) + except AttributeError: + print("Failed to retrieve TwitCasting data, attempting to log in...") + new_cookie = await login_twitcasting( + account_type=account_type, username=username, password=password, proxy_addr=proxy_addr, cookies=cookies) + if not new_cookie: + raise RuntimeError("TwitCasting login failed, please check if the account and password in the " + "configuration file are correct") + print("TwitCasting login successful! Starting to fetch data...") + headers['Cookie'] = new_cookie + anchor_name, live_status, live_title = await get_data(headers) + + result["anchor_name"] = anchor_name + if live_status == 'true': + play_url = f'https://twitcasting.tv/{anchor_id}/metastream.m3u8/?video=1&mode=source' + result |= {'title': live_title, 'is_live': True, "m3u8_url": play_url, "record_url": play_url} + result['new_cookies'] = new_cookie + return result + + +@trace_error_decorator +async def get_baidu_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Connection': 'keep-alive', + 'Referer': 'https://live.baidu.com/', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + if cookies: + headers['Cookie'] = cookies + + uid = random.choice([ + 'h5-683e85bdf741bf2492586f7ca39bf465', + 'h5-c7c6dc14064a136be4215b452fab9eea', + 'h5-4581281f80bb8968bd9a9dfba6050d3a' + ]) + room_id = re.search('room_id=(.*?)&', url).group(1) + params = { + 'cmd': '371', + 'action': 'star', + 'service': 'bdbox', + 'osname': 'baiduboxapp', + 'data': '{"data":{"room_id":"' + room_id + '","device_id":"h5-683e85bdf741bf2492586f7ca39bf465",' + '"source_type":0,"osname":"baiduboxapp"},"replay_slice":0,' + '"nid":"","schemeParams":{"src_pre":"pc","src_suf":"other",' + '"bd_vid":"","share_uid":"","share_cuk":"","share_ecid":"",' + '"zb_tag":"","shareTaskInfo":"{\\"room_id\\":\\"9175031377\\"}",' + '"share_from":"","ext_params":"","nid":""}}', + 'ua': '360_740_ANDROID_0', + 'bd_vid': '', + 'uid': uid, + '_': str(int(time.time() * 1000)), + } + app_api = f'https://mbd.baidu.com/searchbox?{urllib.parse.urlencode(params)}' + json_str = await async_req(url=app_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + key = list(json_data['data'].keys())[0] + data = json_data['data'][key] + anchor_name = data['host']['name'] + result = {"anchor_name": anchor_name, "is_live": False} + if data['status'] == "0": + result["is_live"] = True + live_title = data['video']['title'] + play_url_list = data['video']['url_clarity_list'] + url_list = [] + prefix = 'https://hls.liveshow.bdstatic.com/live/' + if play_url_list: + for i in play_url_list: + url_list.append( + prefix + i['urls']['flv'].rsplit('.', maxsplit=1)[0].rsplit('/', maxsplit=1)[1] + '.m3u8') + else: + play_url_list = data['video']['url_list'] + for i in play_url_list: + url_list.append(prefix + i['urls'][0]['hls'].rsplit('?', maxsplit=1)[0].rsplit('/', maxsplit=1)[1]) + + if url_list: + result |= {"is_live": True, "title": live_title, 'play_url_list': url_list} + return result + + +@trace_error_decorator +async def get_weibo_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Cookie': 'XSRF-TOKEN=qAP-pIY5V4tO6blNOhA4IIOD; SUB=_2AkMRNMCwf8NxqwFRmfwWymPrbI9-zgzEieKnaDFrJRMxHRl-yT9kqmkhtRB6OrTuX5z9N_7qk9C3xxEmNR-8WLcyo2PM; SUBP=0033WrSXqPxfM72-Ws9jqgMF55529P9D9WWemwcqkukCduUO11o9sBqA; WBPSESS=Wk6CxkYDejV3DDBcnx2LOXN9V1LjdSTNQPMbBDWe4lO2HbPmXG_coMffJ30T-Avn_ccQWtEYFcq9fab1p5RR6PEI6w661JcW7-56BszujMlaiAhLX-9vT4Zjboy1yf2l', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + 'Referer': 'https://weibo.com/u/5885340893' + } + if cookies: + headers['Cookie'] = cookies + + room_id = '' + if 'show/' in url: + room_id = url.split('?')[0].split('show/')[1] + else: + uid = url.split('?')[0].rsplit('/u/', maxsplit=1)[1] + web_api = f'https://weibo.com/ajax/statuses/mymblog?uid={uid}&page=1&feature=0' + json_str = await async_req(web_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + for i in json_data['data']['list']: + if 'page_info' in i and i['page_info']['object_type'] == 'live': + room_id = i['page_info']['object_id'] + break + + result = {"anchor_name": '', "is_live": False} + if room_id: + app_api = f'https://weibo.com/l/pc/anchor/live?live_id={room_id}' + # app_api = f'https://weibo.com/l/!/2/wblive/room/show_pc_live.json?live_id={room_id}' + json_str = await async_req(url=app_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + anchor_name = json_data['data']['user_info']['name'] + result["anchor_name"] = anchor_name + live_status = json_data['data']['item']['status'] + if live_status == 1: + result["is_live"] = True + live_title = json_data['data']['item']['desc'] + play_url_list = json_data['data']['item']['stream_info']['pull'] + m3u8_url = play_url_list['live_origin_hls_url'] + flv_url = play_url_list['live_origin_flv_url'] + result['title'] = live_title + result['play_url_list'] = [ + {"m3u8_url": m3u8_url, "flv_url": flv_url}, + {"m3u8_url": m3u8_url.split('_')[0] + '.m3u8', "flv_url": flv_url.split('_')[0] + '.flv'} + ] + return result + + +@trace_error_decorator +async def get_kugou_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + 'Accept': 'application/json', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Referer': 'https://fanxing2.kugou.com/', + } + if cookies: + headers['Cookie'] = cookies + + if 'roomId' in url: + room_id = re.search('roomId=(\\d+)', url).group(1) + else: + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + + app_api = f'https://service2.fanxing.kugou.com/roomcen/room/web/cdn/getEnterRoomInfo?roomId={room_id}' + json_str = await async_req(url=app_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + anchor_name = json_data['data']['normalRoomInfo']['nickName'] + result = {"anchor_name": anchor_name, "is_live": False} + if not anchor_name: + raise RuntimeError( + "Music channel live rooms are not supported for recording, please switch to a different live room." + ) + live_status = json_data['data']['liveType'] + if live_status != -1: + params = { + 'std_rid': room_id, + 'std_plat': '7', + 'std_kid': '0', + 'streamType': '1-2-4-5-8', + 'ua': 'fx-flash', + 'targetLiveTypes': '1-5-6', + 'version': '1000', + 'supportEncryptMode': '1', + 'appid': '1010', + '_': str(int(time.time() * 1000)), + } + api = f'https://fx1.service.kugou.com/video/pc/live/pull/mutiline/streamaddr?{urllib.parse.urlencode(params)}' + json_str2 = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data2 = json.loads(json_str2) + stream_data = json_data2['data']['lines'] + if stream_data: + flv_url = stream_data[-1]['streamProfiles'][0]['httpsFlv'][0] + result |= {"is_live": True, "flv_url": flv_url, "record_url": flv_url} + return result + + +async def get_twitchtv_room_info(url: str, token: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> tuple: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', + 'Accept-Language': 'zh-CN', + 'Referer': 'https://www.twitch.tv/', + 'Client-Id': 'kimne78kx3ncx6brgo4mv6wki5h1ko', + 'Client-Integrity': token, + 'Content-Type': 'text/plain;charset=UTF-8', + } + if cookies: + headers['Cookie'] = cookies + uid = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + + data = [ + { + "operationName": "ChannelShell", + "variables": { + "login": uid + }, + "extensions": { + "persistedQuery": { + "version": 1, + "sha256Hash": "580ab410bcd0c1ad194224957ae2241e5d252b2c5173d8e0cce9d32d5bb14efe" + } + } + }, + ] + + json_str = await async_req('https://gql.twitch.tv/gql', proxy_addr=proxy_addr, headers=headers, + json_data=data, abroad=True) + json_data = json.loads(json_str) + user_data = json_data[0]['data']['userOrError'] + login_name = user_data["login"] + nickname = f"{user_data['displayName']}-{login_name}" + status = True if user_data['stream'] else False + return nickname, status + + +@trace_error_decorator +async def get_twitchtv_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Accept-Language': 'en-US', + 'Referer': 'https://www.twitch.tv/', + 'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko', + 'device-id': generate_random_string(16).lower(), + } + + if cookies: + headers['Cookie'] = cookies + uid = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + + data = { + "operationName": "PlaybackAccessToken_Template", + "query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, " + "$isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, " + "params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: " + "$isLive) { value signature authorization { isForbidden forbiddenReasonCode } __typename " + "} videoPlaybackAccessToken(id: $vodID, params: {platform: \"web\", playerBackend: \"mediaplayer\", " + "playerType: $playerType}) @include(if: $isVod) { value signature __typename }}", + "variables": { + "isLive": True, + "login": uid, + "isVod": False, + "vodID": "", + "playerType": "site" + } + } + + json_str = await async_req('https://gql.twitch.tv/gql', proxy_addr=proxy_addr, headers=headers, + json_data=data, abroad=True) + json_data = json.loads(json_str) + token = json_data['data']['streamPlaybackAccessToken']['value'] + sign = json_data['data']['streamPlaybackAccessToken']['signature'] + + anchor_name, live_status = await get_twitchtv_room_info(url=url, token=token, proxy_addr=proxy_addr, cookies=cookies) + result = {"anchor_name": anchor_name, "is_live": live_status} + if live_status: + play_session_id = random.choice(["bdd22331a986c7f1073628f2fc5b19da", "064bc3ff1722b6f53b0b5b8c01e46ca5"]) + params = { + "acmb": "e30=", + "allow_source": "true", + "browser_family": "firefox", + "browser_version": "124.0", + "cdm": "wv", + "fast_bread": "true", + "os_name": "Windows", + "os_version": "NT%2010.0", + "p": "3553732", + "platform": "web", + "play_session_id": play_session_id, + "player_backend": "mediaplayer", + "player_version": "1.28.0-rc.1", + "playlist_include_framerate": "true", + "reassignments_supported": "true", + "sig": sign, + "token": token, + "transcode_mode": "cbr_v1" + } + access_key = urllib.parse.urlencode(params) + m3u8_url = f'https://usher.ttvnw.net/api/channel/hls/{uid}.m3u8?{access_key}' + play_url_list = await get_play_url_list(m3u8=m3u8_url, proxy=proxy_addr, header=headers, abroad=True) + result |= {'m3u8_url': m3u8_url, 'play_url_list': play_url_list} + return result + + +@trace_error_decorator +async def get_liveme_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'origin': 'https://www.liveme.com', + 'referer': 'https://www.liveme.com', + 'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + if cookies: + headers['Cookie'] = cookies + + room_id = url.split("/index.html")[0].rsplit('/', maxsplit=1)[-1] + sign_data = execjs.compile(open(f'{JS_SCRIPT_PATH}/liveme.js').read()).call('sign', room_id, + f'{JS_SCRIPT_PATH}/crypto-js.min.js') + lm_s_sign = sign_data.pop("lm_s_sign") + tongdun_black_box = sign_data.pop("tongdun_black_box") + platform = sign_data.pop("os") + headers['lm-s-sign'] = lm_s_sign + + params = { + 'alias': 'liveme', + 'tongdun_black_box': tongdun_black_box, + 'os': platform, + } + + api = f'https://live.liveme.com/live/queryinfosimple?{urllib.parse.urlencode(params)}' + json_str = await async_req(api, data=sign_data, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_data = json.loads(json_str) + stream_data = json_data['data']['video_info'] + anchor_name = stream_data['uname'] + live_status = stream_data['status'] + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == "0": + m3u8_url = stream_data['hlsvideosource'] + flv_url = stream_data['videosource'] + result |= { + 'is_live': True, + 'm3u8_url': m3u8_url, + 'flv_url': flv_url, + 'record_url': m3u8_url or flv_url + } + return result + + +async def get_huajiao_sn(url: str, cookies: OptionalStr = None, proxy_addr: OptionalStr = None) -> tuple | None: + headers = { + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://www.huajiao.com/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + + if cookies: + headers['Cookie'] = cookies + + live_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + api = f'https://www.huajiao.com/l/{live_id}' + try: + html_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers) + json_str = re.search('var feed = (.*?});', html_str).group(1) + json_data = json.loads(json_str) + sn = json_data['feed']['sn'] + uid = json_data['author']['uid'] + nickname = json_data['author']['nickname'] + live_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + return nickname, sn, uid, live_id + except Exception: + utils.replace_url(f'{script_path}/config/URL_config.ini', old=url, new='#' + url) + raise RuntimeError("Failed to retrieve live room data, the Huajiao live room address is not fixed, please use " + "the anchor's homepage address for recording.") + + +async def get_huajiao_user_info(url: str, cookies: OptionalStr = None, proxy_addr: OptionalStr = None) -> OptionalDict: + headers = { + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://www.huajiao.com/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + + if cookies: + headers['Cookie'] = cookies + + if 'user' in url: + uid = url.split('?')[0].split('user/')[1] + params = { + 'uid': uid, + 'fmt': 'json', + '_': str(int(time.time() * 1000)), + } + + api = f'https://webh.huajiao.com/User/getUserFeeds?{urllib.parse.urlencode(params)}' + json_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + + html_str = await async_req(url=f'https://www.huajiao.com/user/{uid}', proxy_addr=proxy_addr, headers=headers) + anchor_name = re.search('<title>(.*?)的主页.*', html_str).group(1) + if json_data['data'] and 'sn' in json_data['data']['feeds'][0]['feed']: + feed = json_data['data']['feeds'][0]['feed'] + return { + "anchor_name": anchor_name, + "title": feed['title'], + "is_live": True, + "sn": feed['sn'], + "liveid": feed['relateid'], + "uid": uid + } + else: + return {"anchor_name": anchor_name, "is_live": False} + + +async def get_huajiao_stream_url_app(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> OptionalDict: + headers = { + 'User-Agent': 'living/9.4.0 (com.huajiao.seeding; build:2410231746; iOS 17.0.0) Alamofire/9.4.0', + 'accept-language': 'zh-Hans-US;q=1.0', + 'sdk_version': '1', + } + if cookies: + headers['Cookie'] = cookies + room_id = url.rsplit('/', maxsplit=1)[1] + api = f'https://live.huajiao.com/feed/getFeedInfo?relateid={room_id}' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + + if json_data['errmsg'] or not json_data['data'].get('creatime'): + print("Failed to retrieve live room data, the Huajiao live room address is not fixed, please manually change " + "the address for recording.") + return + data = json_data['data'] + return { + "anchor_name": data['author']['nickname'], + "title": data['feed']['title'], + "is_live": True, + "sn": data['feed']['sn'], + "liveid": data['feed']['relateid'], + "uid": data['author']['uid'] + } + + +@trace_error_decorator +async def get_huajiao_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://www.huajiao.com/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + if cookies: + headers['Cookie'] = cookies + + result = {"anchor_name": "", "is_live": False} + + if 'user/' in url: + if not cookies: + return result + room_data = await get_huajiao_user_info(url, cookies, proxy_addr) + else: + url = await async_req(url, proxy_addr=proxy_addr, headers=headers, redirect_url=True) + if url.rstrip('/') == 'https://www.huajiao.com': + print( + "Failed to retrieve live room data, the Huajiao live room address is not fixed, please manually change " + "the address for recording.") + return result + room_data = await get_huajiao_stream_url_app(url, proxy_addr) + + if room_data: + result["anchor_name"] = room_data.pop("anchor_name") + live_status = room_data.pop("is_live") + + if live_status: + result["title"] = room_data.pop("title") + params = { + "time": int(time.time() * 1000), + "version": "1.0.0", + **room_data, + "encode": "h265" + } + + api = f'https://live.huajiao.com/live/substream?{urllib.parse.urlencode(params)}' + json_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + result |= { + 'is_live': True, + 'flv_url': json_data['data']['h264_url'], + 'record_url': json_data['data']['h264_url'], + } + return result + + +@trace_error_decorator +async def get_liuxing_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'Referer': 'https://wap.7u66.com/198189?promoters=0', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + + } + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + params = { + "promoters": "0", + "roomidx": room_id, + "currentUrl": f"https://www.7u66.com/{room_id}?promoters=0" + } + api = f'https://wap.7u66.com/api/ui/room/v1.0.0/live.ashx?{urllib.parse.urlencode(params)}' + json_str = await async_req(url=api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + room_info = json_data['data']['roomInfo'] + anchor_name = room_info['nickname'] + live_status = room_info["live_stat"] + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 1: + idx = room_info['idx'] + live_id = room_info['liveId1'] + flv_url = f'https://txpull1.5see.com/live/{idx}/{live_id}.flv' + result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_showroom_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + if cookies: + headers['Cookie'] = cookies + + if '/room/profile' in url: + room_id = url.split('room_id=')[-1] + else: + html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers, abroad=True) + room_id = re.search('href="/room/profile\\?room_id=(.*?)"', html_str).group(1) + info_api = f'https://www.showroom-live.com/api/live/live_info?room_id={room_id}' + json_str = await async_req(info_api, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_data = json.loads(json_str) + anchor_name = json_data['room_name'] + result = {"anchor_name": anchor_name, "is_live": False} + live_status = json_data['live_status'] + if live_status == 2: + result["is_live"] = True + web_api = f'https://www.showroom-live.com/api/live/streaming_url?room_id={room_id}&abr_available=1' + json_str = await async_req(web_api, proxy_addr=proxy_addr, headers=headers, abroad=True) + if json_str: + json_data = json.loads(json_str) + streaming_url_list = json_data['streaming_url_list'] + + for i in streaming_url_list: + if i['type'] == 'hls_all': + m3u8_url = i['url'] + result['m3u8_url'] = m3u8_url + if m3u8_url: + m3u8_url_list = await get_play_url_list(m3u8_url, proxy=proxy_addr, header=headers, abroad=True) + if m3u8_url_list: + result['play_url_list'] = [f"{m3u8_url.rsplit('/', maxsplit=1)[0]}/{i}" for i in + m3u8_url_list] + else: + result['play_url_list'] = [m3u8_url] + result['play_url_list'] = [i.replace('https://', 'http://') for i in result['play_url_list']] + break + return result + + +@trace_error_decorator +async def get_acfun_sign_params(proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> tuple: + did = f'web_{utils.generate_random_string(16)}' + headers = { + 'referer': 'https://live.acfun.cn/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'cookie': f'_did={did};', + } + if cookies: + headers['Cookie'] = cookies + data = { + 'sid': 'acfun.api.visitor', + } + api = 'https://id.app.acfun.cn/rest/app/visitor/login' + json_str = await async_req(api, data=data, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + user_id = json_data["userId"] + visitor_st = json_data["acfun.api.visitor_st"] + return user_id, did, visitor_st + + +@trace_error_decorator +async def get_acfun_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'referer': 'https://live.acfun.cn/live/17912421', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + if cookies: + headers['Cookie'] = cookies + + author_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + user_info_api = f'https://live.acfun.cn/rest/pc-direct/user/userInfo?userId={author_id}' + json_str = await async_req(user_info_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + anchor_name = json_data['profile']['name'] + status = 'liveId' in json_data['profile'] + result = {"anchor_name": anchor_name, "is_live": False} + if status: + result["is_live"] = True + user_id, did, visitor_st = await get_acfun_sign_params(proxy_addr=proxy_addr, cookies=cookies) + params = { + 'subBiz': 'mainApp', + 'kpn': 'ACFUN_APP', + 'kpf': 'PC_WEB', + 'userId': user_id, + 'did': did, + 'acfun.api.visitor_st': visitor_st, + } + + data = { + 'authorId': author_id, + 'pullStreamType': 'FLV', + } + play_api = f'https://api.kuaishouzt.com/rest/zt/live/web/startPlay?{urllib.parse.urlencode(params)}' + json_str = await async_req(play_api, data=data, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + live_title = json_data['data']['caption'] + videoPlayRes = json_data['data']['videoPlayRes'] + play_url_list = json.loads(videoPlayRes)['liveAdaptiveManifest'][0]['adaptationSet']['representation'] + play_url_list = sorted(play_url_list, key=itemgetter('bitrate'), reverse=True) + result |= {'play_url_list': play_url_list, 'title': live_title} + return result + + +@trace_error_decorator +async def get_changliao_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Referer': 'https://wap.tlclw.com/phone/15777?promoters=0', + } + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + params = { + 'roomidx': room_id, + 'currentUrl': f'https://wap.tlclw.com/{room_id}', + } + play_api = f'https://wap.tlclw.com/api/ui/room/v1.0.0/live.ashx?{urllib.parse.urlencode(params)}' + json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + anchor_name = json_data['data']['roomInfo']['nickname'] + live_status = json_data['data']['roomInfo']['live_stat'] + + async def get_live_domain(page_url): + html_str = await async_req(page_url, proxy_addr=proxy_addr, headers=headers) + config_json_str = re.findall("var config = (.*?)config.webskins", + html_str, re.DOTALL)[0].rsplit(";", maxsplit=1)[0].strip() + config_json_data = json.loads(config_json_str) + stream_flv_domain = config_json_data['domainpullstream_flv'] + stream_hls_domain = config_json_data['domainpullstream_hls'] + return stream_flv_domain, stream_hls_domain + + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 1: + flv_domain, hls_domain = await get_live_domain(url) + live_id = json_data['data']['roomInfo']['liveID'] + flv_url = f'{flv_domain}/{live_id}.flv' + m3u8_url = f'{hls_domain}/{live_id}.m3u8' + result |= {'is_live': True, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_yingke_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'Referer': 'https://www.inke.cn/', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + if cookies: + headers['Cookie'] = cookies + + parsed_url = urllib.parse.urlparse(url) + query_params = urllib.parse.parse_qs(parsed_url.query) + uid = query_params['uid'][0] + live_id = query_params['id'][0] + params = { + 'uid': uid, + 'id': live_id, + '_t': str(int(time.time())), + } + + api = f'https://webapi.busi.inke.cn/web/live_share_pc?{urllib.parse.urlencode(params)}' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + anchor_name = json_data['data']['media_info']['nick'] + live_status = json_data['data']['status'] + + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 1: + m3u8_url = json_data['data']['live_addr'][0]['hls_stream_addr'] + flv_url = json_data['data']['live_addr'][0]['stream_addr'] + result |= {'is_live': True, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': m3u8_url} + return result + + +@trace_error_decorator +async def get_yinbo_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + 'Accept': 'application/json, text/plain, */*', + 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + 'Referer': 'https://live.ybw1666.com/800005143?promoters=0', + } + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + params = { + 'roomidx': room_id, + 'currentUrl': f'https://wap.ybw1666.com/{room_id}', + } + play_api = f'https://wap.ybw1666.com/api/ui/room/v1.0.0/live.ashx?{urllib.parse.urlencode(params)}' + json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + room_data = json_data['data']['roomInfo'] + anchor_name = room_data['nickname'] + live_status = room_data['live_stat'] + + async def get_live_domain(page_url): + html_str = await async_req(page_url, proxy_addr=proxy_addr, headers=headers) + config_json_str = re.findall("var config = (.*?)config.webskins", + html_str, re.DOTALL)[0].rsplit(";", maxsplit=1)[0].strip() + config_json_data = json.loads(config_json_str) + stream_flv_domain = config_json_data['domainpullstream_flv'] + stream_hls_domain = config_json_data['domainpullstream_hls'] + return stream_flv_domain, stream_hls_domain + + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 1: + flv_domain, hls_domain = await get_live_domain(url) + live_id = room_data['liveID'] + flv_url = f'{flv_domain}/{live_id}.flv' + m3u8_url = f'{hls_domain}/{live_id}.m3u8' + result |= {'is_live': True, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_zhihu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + if cookies: + headers['Cookie'] = cookies + + if 'people/' in url: + user_id = url.split('people/')[1] + api = f'https://api.zhihu.com/people/{user_id}/profile?profile_new_version=' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + live_page_url = json_data['drama']['living_theater']['theater_url'] + else: + live_page_url = url + + web_id = live_page_url.split('?')[0].rsplit('/', maxsplit=1)[-1] + html_str = await async_req(live_page_url, proxy_addr=proxy_addr, headers=headers) + json_str2 = re.findall('', html_str)[0] + json_data2 = json.loads(json_str2) + live_data = json_data2['initialState']['theater']['theaters'][web_id] + anchor_name = live_data['actor']['name'] + live_status = live_data['drama']['status'] + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 1: + live_title = live_data['theme'] + play_url = live_data['drama']['playInfo'] + result |= { + 'is_live': True, + 'title': live_title, + 'm3u8_url': play_url['hlsUrl'], + 'flv_url': play_url['playUrl'], + 'record_url': play_url['hlsUrl'] + } + return result + + +@trace_error_decorator +async def get_chzzk_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'origin': 'https://chzzk.naver.com', + 'referer': 'https://chzzk.naver.com/live/458f6ec20b034f49e0fc6d03921646d2', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + play_api = f'https://api.chzzk.naver.com/service/v3/channels/{room_id}/live-detail' + json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_data = json.loads(json_str) + live_data = json_data['content'] + anchor_name = live_data['channel']['channelName'] + live_status = live_data['status'] + + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 'OPEN': + play_data = json.loads(live_data['livePlaybackJson']) + m3u8_url = play_data['media'][0]['path'] + m3u8_url_list = await get_play_url_list(m3u8_url, proxy=proxy_addr, header=headers, abroad=True) + prefix = m3u8_url.split('?')[0].rsplit('/', maxsplit=1)[0] + m3u8_url_list = [prefix + '/' + i for i in m3u8_url_list] + result |= {"is_live": True, "m3u8_url": m3u8_url, "play_url_list": m3u8_url_list} + return result + + +@trace_error_decorator +async def get_haixiu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'origin': 'https://www.haixiutv.com', + 'referer': 'https://www.haixiutv.com/', + 'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + if cookies: + headers['Cookie'] = cookies + + room_id = url.split("?")[0].rsplit('/', maxsplit=1)[-1] + if 'haixiutv' in url: + access_token = "pLXSC%252FXJ0asc1I21tVL5FYZhNJn2Zg6d7m94umCnpgL%252BuVm31GQvyw%253D%253D" + else: + access_token = "s7FUbTJ%252BjILrR7kicJUg8qr025ZVjd07DAnUQd8c7g%252Fo4OH9pdSX6w%253D%253D" + + params = { + "accessToken": access_token, + "tku": "3000006", + "c": "10138100100000", + "_st1": int(time.time() * 1000) + } + ajax_data = execjs.compile(open(f'{JS_SCRIPT_PATH}/haixiu.js').read()).call('sign', params, + f'{JS_SCRIPT_PATH}/crypto-js.min.js') + + params["accessToken"] = urllib.parse.unquote(urllib.parse.unquote(access_token)) + params['_ajaxData1'] = ajax_data + params['_'] = int(time.time() * 1000) + + if 'haixiutv' in url: + api = f'https://service.haixiutv.com/v2/room/{room_id}/media/advanceInfoRoom?{urllib.parse.urlencode(params)}' + else: + headers['origin'] = 'https://www.lehaitv.com' + headers['referer'] = 'https://www.lehaitv.com' + api = f'https://service.lehaitv.com/v2/room/{room_id}/media/advanceInfoRoom?{urllib.parse.urlencode(params)}' + + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_data = json.loads(json_str) + + stream_data = json_data['data'] + anchor_name = stream_data['nickname'] + live_status = stream_data['live_status'] + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 1: + flv_url = stream_data['media_url_web'] + result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_vvxqiu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + 'Access-Control-Request-Method': 'GET', + 'Origin': 'https://h5webcdn-pro.vvxqiu.com', + 'Referer': 'https://h5webcdn-pro.vvxqiu.com/', + } + + if cookies: + headers['Cookie'] = cookies + + room_id = get_params(url, "roomId") + api_1 = f'https://h5p.vvxqiu.com/activity-center/fanclub/activity/captain/banner?roomId={room_id}&product=vvstar' + json_str = await async_req(api_1, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + anchor_name = json_data['data']['anchorName'] + if not anchor_name: + params = { + 'sessionId': '', + 'userId': '', + 'product': 'vvstar', + 'tickToken': '', + 'roomId': room_id, + } + json_str = await async_req( + f'https://h5p.vvxqiu.com/activity-center/halloween2023/banner?{urllib.parse.urlencode(params)}', + proxy_addr=proxy_addr, headers=headers + ) + json_data = json.loads(json_str) + anchor_name = json_data['data']['memberVO']['memberName'] + + result = {"anchor_name": anchor_name, "is_live": False} + m3u8_url = f'https://liveplay-pro.wasaixiu.com/live/1400442770_{room_id}_{room_id[2:]}_single.m3u8' + resp = await async_req(m3u8_url, proxy_addr=proxy_addr, headers=headers) + if 'Not Found' not in resp: + result |= {'is_live': True, 'm3u8_url': m3u8_url, 'record_url': m3u8_url} + return result + + +@trace_error_decorator +async def get_17live_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'origin': 'https://17.live', + 'referer': 'https://17.live/', + 'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + api_1 = f'https://wap-api.17app.co/api/v1/user/room/{room_id}' + json_str = await async_req(api_1, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + anchor_name = json_data["displayName"] + result = {"anchor_name": anchor_name, "is_live": False} + json_data = { + 'liveStreamID': room_id, + } + api_1 = f'https://wap-api.17app.co/api/v1/lives/{room_id}/viewers/alive' + json_str = await async_req(api_1, json_data=json_data, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + live_status = json_data.get("status") + if live_status and live_status == 2: + flv_url = json_data['pullURLsInfo']['rtmpURLs'][0]['urlHighQuality'] + result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_langlive_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'origin': 'https://www.lang.live', + 'referer': 'https://www.lang.live/', + 'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + api_1 = f'https://api.lang.live/langweb/v1/room/liveinfo?room_id={room_id}' + json_str = await async_req(api_1, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_data = json.loads(json_str) + live_info = json_data['data']['live_info'] + anchor_name = live_info['nickname'] + live_status = live_info['live_status'] + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 1: + flv_url = json_data['data']['live_info']['liveurl'] + m3u8_url = json_data['data']['live_info']['liveurl_hls'] + result |= {'is_live': True, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': m3u8_url} + return result + + +@trace_error_decorator +async def get_pplive_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'Content-Type': 'application/json', + 'Origin': 'https://m.pp.weimipopo.com', + 'Referer': 'https://m.pp.weimipopo.com/', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + + if cookies: + headers['Cookie'] = cookies + + room_id = get_params(url, 'anchorUid') + json_data = { + 'inviteUuid': '', + 'anchorUuid': room_id, + } + + if 'catshow' in url: + api = 'https://api.catshow168.com/live/preview' + headers['Origin'] = 'https://h.catshow168.com' + headers['Referer'] = 'https://h.catshow168.com' + else: + api = 'https://api.pp.weimipopo.com/live/preview' + json_str = await async_req(api, json_data=json_data, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + live_info = json_data['data'] + anchor_name = live_info['name'] + live_status = live_info['living'] + result = {"anchor_name": anchor_name, "is_live": False} + if live_status: + m3u8_url = live_info['pullUrl'] + result |= {'is_live': True, 'm3u8_url': m3u8_url, 'record_url': m3u8_url} + return result + + +@trace_error_decorator +async def get_6room_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://ios.6.cn/?ver=8.0.3&build=4', + 'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + html_str = await async_req(f'https://v.6.cn/{room_id}', proxy_addr=proxy_addr, headers=headers) + room_id = re.search('rid: \'(.*?)\',\n\\s+roomid', html_str).group(1) + data = { + 'av': '3.1', + 'encpass': '', + 'logiuid': '', + 'project': 'v6iphone', + 'rate': '1', + 'rid': '', + 'ruid': room_id, + } + api = 'https://v.6.cn/coop/mobile/index.php?padapi=coop-mobile-inroom.php' + json_str = await async_req(api, data=data, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + flv_title = json_data['content']['liveinfo']['flvtitle'] + anchor_name = json_data['content']['roominfo']['alias'] + result = {"anchor_name": anchor_name, "is_live": False} + if flv_title: + flv_url = f'https://wlive.6rooms.com/httpflv/{flv_title}.flv' + result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_shopee_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept': 'application/json, text/plain, */*', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'referer': 'https://live.shopee.sg/share?from=live&session=802458&share_user_id=', + 'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + + if cookies: + headers['Cookie'] = cookies + + result = {"anchor_name": "", "is_live": False} + is_living = False + + if 'live.shopee' not in url and 'uid' not in url: + url = await async_req(url, proxy_addr=proxy_addr, headers=headers, redirect_url=True, abroad=True) + + if 'live.shopee' in url: + host_suffix = url.split('/')[2].rsplit('.', maxsplit=1)[1] + is_living = get_params(url, 'uid') is None + else: + host_suffix = url.split('/')[2].split('.', maxsplit=1)[0] + + uid = get_params(url, 'uid') + api_host = f'https://live.shopee.{host_suffix}' + session_id = get_params(url, 'session') + if uid: + json_str = await async_req(f'{api_host}/api/v1/shop_page/live/ongoing?uid={uid}', + proxy_addr=proxy_addr, headers=headers, abroad=True) + json_data = json.loads(json_str) + if json_data['data']['ongoing_live']: + session_id = json_data['data']['ongoing_live']['session_id'] + is_living = True + else: + json_str = await async_req(f'{api_host}/api/v1/shop_page/live/replay_list?offset=0&limit=1&uid={uid}', + proxy_addr=proxy_addr, headers=headers, abroad=True) + json_data = json.loads(json_str) + if json_data['data']['replay']: + result['anchor_name'] = json_data['data']['replay'][0]['nick_name'] + return result + + json_str = await async_req(f'{api_host}/api/v1/session/{session_id}', proxy_addr=proxy_addr, headers=headers, abroad=True) + json_data = json.loads(json_str) + if not json_data.get('data'): + print("Fetch shopee live data failed, please update the address of the live broadcast room and try again.") + return result + uid = json_data['data']['session']['uid'] + anchor_name = json_data['data']['session']['nickname'] + live_status = json_data['data']['session']['status'] + result["anchor_name"] = anchor_name + result['uid'] = f'uid={uid}&session={session_id}' + if live_status == 1 and is_living: + flv_url = json_data['data']['session']['play_url'] + title = json_data['data']['session']['title'] + result |= {'is_live': True, 'title': title, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_youtube_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + + if cookies: + headers['Cookie'] = cookies + + html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers, abroad=True) + json_str = re.search('var ytInitialPlayerResponse = (.*?);var meta = document\\.createElement', html_str).group(1) + json_data = json.loads(json_str) + result = {"anchor_name": "", "is_live": False} + if 'videoDetails' not in json_data: + print("Error: Please log in to YouTube on your device's webpage and configure cookies in the config.ini") + return result + result['anchor_name'] = json_data['videoDetails']['author'] + live_status = json_data['videoDetails'].get('isLive') + if live_status: + live_title = json_data['videoDetails']['title'] + m3u8_url = json_data['streamingData']["hlsManifestUrl"] + play_url_list = await get_play_url_list(m3u8_url, proxy=proxy_addr, header=headers, abroad=True) + result |= {"is_live": True, "title": live_title, "m3u8_url": m3u8_url, "play_url_list": play_url_list} + return result + + +@trace_error_decorator +async def get_taobao_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'Referer': 'https://huodong.m.taobao.com/', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + 'Cookie': '', + } + + if cookies: + headers['Cookie'] = cookies + + if '_m_h5_tk' not in headers['Cookie']: + print('Error: Cookies is empty! please input correct cookies') + + live_id = get_params(url, 'id') + if not live_id: + html_str = await async_req(url, proxy_addr=proxy_addr, headers=headers) + redirect_url = re.findall("var url = '(.*?)';", html_str)[0] + live_id = get_params(redirect_url, 'id') + + params = { + 'jsv': '2.7.0', + 'appKey': '12574478', + 't': '1733104933120', + 'sign': '', + 'AntiFlood': 'true', + 'AntiCreep': 'true', + 'api': 'mtop.mediaplatform.live.livedetail', + 'v': '4.0', + 'preventFallback': 'true', + 'type': 'jsonp', + 'dataType': 'jsonp', + 'callback': 'mtopjsonp1', + 'data': '{"liveId":"' + live_id + '","creatorId":null}', + } + + for i in range(2): + app_key = '12574478' + _m_h5_tk = re.findall('_m_h5_tk=(.*?);', headers['Cookie'])[0] + t13 = int(time.time() * 1000) + pre_sign_str = f'{_m_h5_tk.split("_")[0]}&{t13}&{app_key}&' + params['data'] + sign = execjs.compile(open(f'{JS_SCRIPT_PATH}/taobao-sign.js').read()).call('sign', pre_sign_str) + params |= {'sign': sign, 't': t13} + api = f'https://h5api.m.taobao.com/h5/mtop.mediaplatform.live.livedetail/4.0/?{urllib.parse.urlencode(params)}' + jsonp_str, new_cookie = await async_req(url=api, proxy_addr=proxy_addr, headers=headers, timeout=20, + return_cookies=True, include_cookies=True) + json_data = utils.jsonp_to_json(jsonp_str) + + ret_msg = json_data['ret'] + if ret_msg == ['SUCCESS::调用成功']: + anchor_name = json_data['data']['broadCaster']['accountName'] + result = {"anchor_name": anchor_name, "is_live": False} + live_status = json_data['data']['streamStatus'] + if live_status == '1': + live_title = json_data['data']['title'] + play_url_list = json_data['data']['liveUrlList'] + + def get_sort_key(item): + definition_priority = { + "lld": 0, "ld": 1, "md": 2, "hd": 3, "ud": 4 + } + def_value = item.get('definition') or item.get('newDefinition') + priority = definition_priority.get(def_value, -1) + return priority + + play_url_list = sorted(play_url_list, key=get_sort_key, reverse=True) + result |= {"is_live": True, "title": live_title, "play_url_list": play_url_list, 'live_id': live_id} + + return result + else: + print(f'Error: Taobao live data fetch failed, {ret_msg[0]}') + + if '_m_h5_tk' in new_cookie and '_m_h5_tk_enc' in new_cookie: + headers['Cookie'] = re.sub('_m_h5_tk=(.*?);', new_cookie['_m_h5_tk'], headers['Cookie']) + headers['Cookie'] = re.sub('_m_h5_tk_enc=(.*?);', new_cookie['_m_h5_tk_enc'], headers['Cookie']) + else: + print('Error: Try to update cookie failed, please update the cookies in the configuration file') + + +@trace_error_decorator +async def get_jd_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + 'origin': 'https://lives.jd.com', + 'referer': 'https://lives.jd.com/', + 'x-referer-page': 'https://lives.jd.com/', + } + + if cookies: + headers['Cookie'] = cookies + + redirect_url = await async_req(url, proxy_addr=proxy_addr, headers=headers, redirect_url=True) + author_id = get_params(redirect_url, 'authorId') + result = {"anchor_name": '', "is_live": False} + if not author_id: + live_id = re.search('#/(.*?)\\?origin', redirect_url) + if not live_id: + return result + live_id = live_id.group(1) + result['anchor_name'] = f'jd_{live_id}' + else: + data = { + 'functionId': 'talent_head_findTalentMsg', + 'appid': 'dr_detail', + 'body': '{"authorId":"' + author_id + '","monitorSource":"1","userId":""}', + } + info_api = 'https://api.m.jd.com/talent_head_findTalentMsg' + json_str = await async_req(info_api, data=data, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + anchor_name = json_data['result']['talentName'] + result['anchor_name'] = anchor_name + if 'livingRoomJump' not in json_data['result']: + return result + live_id = json_data['result']['livingRoomJump']['params']['id'] + params = { + "body": '{"liveId": "' + live_id + '"}', + "functionId": "getImmediatePlayToM", + "appid": "h5-live" + } + + api = f'https://api.m.jd.com/client.action?{urllib.parse.urlencode(params)}' + # backup_api: https://api.m.jd.com/api + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + live_status = json_data['data']['status'] + if live_status == 1: + if author_id: + data = { + 'functionId': 'jdTalentContentList', + 'appid': 'dr_detail', + 'body': '{"authorId":"' + author_id + '","type":1,"userId":"","page":1,"offset":"-1",' + '"monitorSource":"1","pageSize":1}', + } + json_str2 = await async_req('https://api.m.jd.com/jdTalentContentList', data=data, + proxy_addr=proxy_addr, headers=headers) + json_data2 = json.loads(json_str2) + result['title'] = json_data2['result']['content'][0]['title'] + + flv_url = json_data['data']['videoUrl'] + m3u8_url = json_data['data']['h5VideoUrl'] + result |= {"is_live": True, "m3u8_url": m3u8_url, "flv_url": flv_url, "record_url": m3u8_url} + return result + + +@trace_error_decorator +async def get_faceit_stream_data(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'Referer': 'https://www.faceit.com/zh/players/qpjzz/stream', + 'faceit-referer': 'web-next', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', + } + + if cookies: + headers['Cookie'] = cookies + nickname = re.findall('/players/(.*?)/stream', url)[0] + api = f'https://www.faceit.com/api/users/v1/nicknames/{nickname}' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + user_id = json_data['payload']['id'] + api2 = f'https://www.faceit.com/api/stream/v1/streamings?userId={user_id}' + json_str2 = await async_req(api2, proxy_addr=proxy_addr, headers=headers) + json_data2 = json.loads(json_str2) + platform_info = json_data2['payload'][0] + anchor_name = platform_info.get('userNickname') + anchor_id = platform_info.get('platformId') + platform = platform_info.get('platform') + if platform == 'twitch': + result = await get_twitchtv_stream_data(f'https://www.twitch.tv/{anchor_id}') + result['anchor_name'] = anchor_name + else: + result = {'anchor_name': anchor_name, 'is_live': False} + return result + + +@trace_error_decorator +async def get_migu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'origin': 'https://www.miguvideo.com', + 'referer': 'https://www.miguvideo.com/', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'appCode': 'miguvideo_default_www', + 'appId': 'miguvideo', + 'channel': 'H5', + } + + if cookies: + headers['Cookie'] = cookies + + web_id = url.split('?')[0].rsplit('/')[-1] + api = f'https://vms-sc.miguvideo.com/vms-match/v6/staticcache/basic/basic-data/{web_id}/miguvideo' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + + anchor_name = json_data['body']['title'] + live_title = json_data['body'].get('title') + '-' + json_data['body'].get('detailPageTitle', '') + room_id = json_data['body'].get('pId') + + result = {"anchor_name": anchor_name, "is_live": False} + if not room_id: + return result + + params = { + 'contId': room_id, + 'rateType': '3', + 'clientId': str(uuid.uuid4()), + 'timestamp': int(time.time() * 1000), + 'flvEnable': 'true', + 'xh265': 'false', + 'chip': 'mgwww', + 'channelId': '', + } + + api = f'https://webapi.miguvideo.com/gateway/playurl/v3/play/playurl?{urllib.parse.urlencode(params)}' + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + live_status = json_data['body']['content']['currentLive'] + if live_status != '1': + return result + else: + result['title'] = live_title + source_url = json_data['body']['urlInfo']['url'] + + async def _get_dd_calcu(url): + try: + result = subprocess.run( + ["node", f"{JS_SCRIPT_PATH}/migu.js", url], + capture_output=True, + text=True, + check=True + ) + return result.stdout.strip() + except execjs.ProgramError: + raise execjs.ProgramError('Failed to execute JS code. Please check if the Node.js environment') + + ddCalcu = await _get_dd_calcu(source_url) + real_source_url = f'{source_url}&ddCalcu={ddCalcu}&sv=10010' + if '.m3u8' in real_source_url: + m3u8_url = await async_req( + real_source_url, proxy_addr=proxy_addr, headers=headers, redirect_url=True) + result['m3u8_url'] = m3u8_url + result['record_url'] = m3u8_url + else: + result['flv_url'] = real_source_url + result['record_url'] = real_source_url + result['is_live'] = True + return result + + +@trace_error_decorator +async def get_lianjie_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'accept-language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + } + + if cookies: + headers['cookie'] = cookies + + room_id = url.split('?')[0].rsplit('lailianjie.com/', maxsplit=1)[-1] + play_api = f'https://api.lailianjie.com/ApiServices/service/live/getRoomInfo?&_$t=&_sign=&roomNumber={room_id}' + json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + + room_data = json_data['data'] + anchor_name = room_data['nickname'] + live_status = room_data['isonline'] + + result = {"anchor_name": anchor_name, "is_live": False} + if live_status == 1: + title = room_data['defaultRoomTitle'] + webrtc_url = room_data['videoUrl'] + https_url = "https://" + webrtc_url.split('webrtc://')[1] + flv_url = https_url.replace('?', '.flv?') + m3u8_url = https_url.replace('?', '.m3u8?') + result |= {'is_live': True, 'title': title, 'm3u8_url': m3u8_url, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_laixiu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + def generate_uuid(ua_type: str): + if ua_type == "mobile": + return str(uuid.uuid4()) + return str(uuid.uuid4()).replace('-', '') + + def calculate_sign(ua_type: str = 'pc'): + a = int(time.time() * 1000) + s = generate_uuid(ua_type) + u = 'kk792f28d6ff1f34ec702c08626d454b39pro' + + input_str = f"web{s}{a}{u}" + md5_hash = hashlib.md5(input_str.encode('utf-8')).hexdigest() + + return { + 'timestamp': a, + 'imei': s, + 'requestId': md5_hash, + 'inputString': input_str + } + + sign_data = calculate_sign(ua_type='pc') + headers = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0', + 'mobileModel': 'web', + 'timestamp': str(sign_data['timestamp']), + 'loginType': '2', + 'versionCode': '10003', + 'imei': sign_data['imei'], + 'requestId': sign_data['requestId'], + 'channel': '9', + 'version': '1.0.0', + 'os': 'web', + 'platform': 'WEB', + 'Origin': 'https://www.imkktv.com', + 'Referer': 'https://www.imkktv.com/', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', + } + + if cookies: + headers['cookie'] = cookies + + pattern = r"(?:roomId|anchorId)=(.*?)(?=&|$)" + match = re.search(pattern, url) + room_id = match.group(1) if match else '' + play_api = f'https://api.imkktv.com/liveroom/getShareLiveVideo?roomId={room_id}' + json_str = await async_req(play_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + + room_data = json_data['data'] + anchor_name = room_data['nickname'] + live_status = room_data['playStatus'] == 0 + + result = {"anchor_name": anchor_name, "is_live": False} + if live_status: + flv_url = room_data['playUrl'] + result |= {'is_live': True, 'flv_url': flv_url, 'record_url': flv_url} + return result + + +@trace_error_decorator +async def get_picarto_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict: + headers = { + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' + 'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'accept-language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', + } + + if cookies: + headers['cookie'] = cookies + + anchor_id = url.split('?')[0].rsplit('/', maxsplit=1)[-1] + api = f'https://ptvintern.picarto.tv/api/channel/detail/{anchor_id}' + + json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + + anchor_name = json_data['channel']['name'] + live_status = json_data['channel']['online'] + + result = {"anchor_name": anchor_name, "is_live": live_status} + if live_status: + title = json_data['channel']['title'] + m3u8_url = f"https://1-edge1-us-newyork.picarto.tv/stream/hls/golive+{anchor_name}/index.m3u8" + result |= {'is_live': True, 'title': title, 'm3u8_url': m3u8_url, 'record_url': m3u8_url} + return result diff --git a/stream.py b/stream.py new file mode 100644 index 0000000..0860d4c --- /dev/null +++ b/stream.py @@ -0,0 +1,446 @@ +# -*- encoding: utf-8 -*- + +""" +Author: Hmily +GitHub: https://github.com/ihmily +Date: 2023-07-15 23:15:00 +Update: 2025-02-06 02:28:00 +Copyright (c) 2023-2025 by Hmily, All Rights Reserved. +Function: Get live stream data. +""" +import base64 +import hashlib +import json +import time +import random +import re +from operator import itemgetter +import urllib.parse +import urllib.request +from utils import trace_error_decorator +from spider import ( + get_douyu_stream_data, get_bilibili_stream_data +) +from http_clients.async_http import get_response_status + +QUALITY_MAPPING = {"OD": 0, "BD": 0, "UHD": 1, "HD": 2, "SD": 3, "LD": 4} + + +def get_quality_index(quality) -> tuple: + if not quality: + return list(QUALITY_MAPPING.items())[0] + + quality_str = str(quality).upper() + if quality_str.isdigit(): + quality_int = int(quality_str[0]) + quality_str = list(QUALITY_MAPPING.keys())[quality_int] + return quality_str, QUALITY_MAPPING.get(quality_str, 0) + + +@trace_error_decorator +async def get_douyin_stream_url(json_data: dict, video_quality: str, proxy_addr: str) -> dict: + anchor_name = json_data.get('anchor_name') + + result = { + "anchor_name": anchor_name, + "is_live": False, + } + + status = json_data.get("status", 4) + + if status == 2: + stream_url = json_data['stream_url'] + flv_url_dict = stream_url['flv_pull_url'] + flv_url_list: list = list(flv_url_dict.values()) + m3u8_url_dict = stream_url['hls_pull_url_map'] + m3u8_url_list: list = list(m3u8_url_dict.values()) + + while len(flv_url_list) < 5: + flv_url_list.append(flv_url_list[-1]) + m3u8_url_list.append(m3u8_url_list[-1]) + + video_quality, quality_index = get_quality_index(video_quality) + m3u8_url = m3u8_url_list[quality_index] + flv_url = flv_url_list[quality_index] + ok = await get_response_status(url=m3u8_url, proxy_addr=proxy_addr) + if not ok: + index = quality_index + 1 if quality_index < 4 else quality_index - 1 + m3u8_url = m3u8_url_list[index] + flv_url = flv_url_list[index] + result |= { + 'is_live': True, + 'title': json_data['title'], + 'quality': video_quality, + 'm3u8_url': m3u8_url, + 'flv_url': flv_url, + 'record_url': m3u8_url or flv_url, + } + return result + + +@trace_error_decorator +async def get_tiktok_stream_url(json_data: dict, video_quality: str, proxy_addr: str) -> dict: + if not json_data: + return {"anchor_name": None, "is_live": False} + + def get_video_quality_url(stream, q_key) -> list: + play_list = [] + for key in stream: + url_info = stream[key]['main'] + sdk_params = url_info['sdk_params'] + sdk_params = json.loads(sdk_params) + vbitrate = int(sdk_params['vbitrate']) + v_codec = sdk_params.get('VCodec', '') + + play_url = '' + if url_info.get(q_key): + if url_info[q_key].endswith(".flv") or url_info[q_key].endswith(".m3u8"): + play_url = url_info[q_key] + '?codec=' + v_codec + else: + play_url = url_info[q_key] + '&codec=' + v_codec + + resolution = sdk_params['resolution'] + if vbitrate != 0 and resolution: + width, height = map(int, resolution.split('x')) + play_list.append({'url': play_url, 'vbitrate': vbitrate, 'resolution': (width, height)}) + + play_list.sort(key=itemgetter('vbitrate'), reverse=True) + play_list.sort(key=lambda x: (-x['vbitrate'], -x['resolution'][0], -x['resolution'][1])) + return play_list + + live_room = json_data['LiveRoom']['liveRoomUserInfo'] + user = live_room['user'] + anchor_name = f"{user['nickname']}-{user['uniqueId']}" + status = user.get("status", 4) + + result = { + "anchor_name": anchor_name, + "is_live": False, + } + + if status == 2: + stream_data = live_room['liveRoom']['streamData']['pull_data']['stream_data'] + stream_data = json.loads(stream_data).get('data', {}) + flv_url_list = get_video_quality_url(stream_data, 'flv') + m3u8_url_list = get_video_quality_url(stream_data, 'hls') + + while len(flv_url_list) < 5: + flv_url_list.append(flv_url_list[-1]) + while len(m3u8_url_list) < 5: + m3u8_url_list.append(m3u8_url_list[-1]) + video_quality, quality_index = get_quality_index(video_quality) + flv_dict: dict = flv_url_list[quality_index] + m3u8_dict: dict = m3u8_url_list[quality_index] + + check_url = m3u8_dict.get('url') or flv_dict.get('url') + ok = await get_response_status(url=check_url, proxy_addr=proxy_addr, http2=False) + + if not ok: + index = quality_index + 1 if quality_index < 4 else quality_index - 1 + flv_dict: dict = flv_url_list[index] + m3u8_dict: dict = m3u8_url_list[index] + + flv_url = flv_dict['url'] + m3u8_url = m3u8_dict['url'] + result |= { + 'is_live': True, + 'title': live_room['liveRoom']['title'], + 'quality': video_quality, + 'm3u8_url': m3u8_url, + 'flv_url': flv_url, + 'record_url': m3u8_url or flv_url, + } + return result + + +@trace_error_decorator +async def get_kuaishou_stream_url(json_data: dict, video_quality: str) -> dict: + if json_data['type'] == 1 and not json_data["is_live"]: + return json_data + live_status = json_data['is_live'] + + result = { + "type": 2, + "anchor_name": json_data['anchor_name'], + "is_live": live_status, + } + + if live_status: + quality_mapping_bit = {'OD': 99999, 'BD': 4000, 'UHD': 2000, 'HD': 1000, 'SD': 800, 'LD': 600} + if video_quality in QUALITY_MAPPING: + + quality, quality_index = get_quality_index(video_quality) + if 'm3u8_url_list' in json_data: + m3u8_url_list = json_data['m3u8_url_list'][::-1] + while len(m3u8_url_list) < 5: + m3u8_url_list.append(m3u8_url_list[-1]) + m3u8_url = m3u8_url_list[quality_index]['url'] + result['m3u8_url'] = m3u8_url + + if 'flv_url_list' in json_data: + if 'bitrate' in json_data['flv_url_list'][0]: + flv_url_list = json_data['flv_url_list'] + flv_url_list = sorted(flv_url_list, key=lambda x: x['bitrate'], reverse=True) + quality_str = str(video_quality).upper() + if quality_str.isdigit(): + video_quality, quality_index_bitrate_value = list(quality_mapping_bit.items())[int(quality_str)] + else: + quality_index_bitrate_value = quality_mapping_bit.get(quality_str, 99999) + video_quality = quality_str + quality_index = next( + (i for i, x in enumerate(flv_url_list) if x['bitrate'] <= quality_index_bitrate_value), None) + if quality_index is None: + quality_index = len(flv_url_list) - 1 + flv_url = flv_url_list[quality_index]['url'] + + result['flv_url'] = flv_url + result['record_url'] = flv_url + else: + flv_url_list = json_data['flv_url_list'][::-1] + while len(flv_url_list) < 5: + flv_url_list.append(flv_url_list[-1]) + flv_url = flv_url_list[quality_index]['url'] + result |= {'flv_url': flv_url, 'record_url': flv_url} + result['is_live'] = True + result['quality'] = video_quality + return result + + +@trace_error_decorator +async def get_huya_stream_url(json_data: dict, video_quality: str) -> dict: + game_live_info = json_data['data'][0]['gameLiveInfo'] + live_title = game_live_info['introduction'] + stream_info_list = json_data['data'][0]['gameStreamInfoList'] + anchor_name = game_live_info.get('nick', '') + + result = { + "anchor_name": anchor_name, + "is_live": False, + } + + if stream_info_list: + select_cdn = stream_info_list[0] + flv_url = select_cdn.get('sFlvUrl') + stream_name = select_cdn.get('sStreamName') + flv_url_suffix = select_cdn.get('sFlvUrlSuffix') + hls_url = select_cdn.get('sHlsUrl') + hls_url_suffix = select_cdn.get('sHlsUrlSuffix') + flv_anti_code = select_cdn.get('sFlvAntiCode') + + def get_anti_code(old_anti_code: str) -> str: + + # js地址:https://hd.huya.com/cdn_libs/mobile/hysdk-m-202402211431.js + + params_t = 100 + sdk_version = 2403051612 + + # sdk_id是13位数毫秒级时间戳 + t13 = int(time.time()) * 1000 + sdk_sid = t13 + + # 计算uuid和uid参数值 + init_uuid = (int(t13 % 10 ** 10 * 1000) + int(1000 * random.random())) % 4294967295 # 直接初始化 + uid = random.randint(1400000000000, 1400009999999) # 经过测试uid也可以使用init_uuid代替 + seq_id = uid + sdk_sid # 移动端请求的直播流地址中包含seqId参数 + + # 计算ws_time参数值(16进制) 可以是当前毫秒时间戳,当然也可以直接使用url_query['wsTime'][0] + # 原始最大误差不得慢240000毫秒 + target_unix_time = (t13 + 110624) // 1000 + ws_time = f"{target_unix_time:x}".lower() + + # fm参数值是经过url编码然后base64编码得到的,解码结果类似 DWq8BcJ3h6DJt6TY_$0_$1_$2_$3 + # 具体细节在上面js中查看,大概在32657行代码开始,有base64混淆代码请自行替换 + url_query = urllib.parse.parse_qs(old_anti_code) + ws_secret_pf = base64.b64decode(urllib.parse.unquote(url_query['fm'][0]).encode()).decode().split("_")[0] + ws_secret_hash = hashlib.md5(f'{seq_id}|{url_query["ctype"][0]}|{params_t}'.encode()).hexdigest() + ws_secret = f'{ws_secret_pf}_{uid}_{stream_name}_{ws_secret_hash}_{ws_time}' + ws_secret_md5 = hashlib.md5(ws_secret.encode()).hexdigest() + + anti_code = ( + f'wsSecret={ws_secret_md5}&wsTime={ws_time}&seqid={seq_id}&ctype={url_query["ctype"][0]}&ver=1' + f'&fs={url_query["fs"][0]}&uuid={init_uuid}&u={uid}&t={params_t}&sv={sdk_version}' + f'&sdk_sid={sdk_sid}&codec=264' + ) + return anti_code + + new_anti_code = get_anti_code(flv_anti_code) + flv_url = f'{flv_url}/{stream_name}.{flv_url_suffix}?{new_anti_code}&ratio=' + m3u8_url = f'{hls_url}/{stream_name}.{hls_url_suffix}?{new_anti_code}&ratio=' + + quality_list = flv_anti_code.split('&exsphd=') + if len(quality_list) > 1 and video_quality not in ["OD", "BD"]: + pattern = r"(?<=264_)\d+" + quality_list = list(re.findall(pattern, quality_list[1]))[::-1] + while len(quality_list) < 5: + quality_list.append(quality_list[-1]) + + video_quality_options = { + "UHD": quality_list[0], + "HD": quality_list[1], + "SD": quality_list[2], + "LD": quality_list[3] + } + + if video_quality not in video_quality_options: + raise ValueError( + f"Invalid video quality. Available options are: {', '.join(video_quality_options.keys())}") + + flv_url = flv_url + str(video_quality_options[video_quality]) + m3u8_url = m3u8_url + str(video_quality_options[video_quality]) + + result |= { + 'is_live': True, + 'title': live_title, + 'quality': video_quality, + 'm3u8_url': m3u8_url, + 'flv_url': flv_url, + 'record_url': flv_url or m3u8_url + } + return result + + +@trace_error_decorator +async def get_douyu_stream_url(json_data: dict, video_quality: str, cookies: str, proxy_addr: str) -> dict: + if not json_data["is_live"]: + return json_data + + video_quality_options = { + "OD": '0', + "BD": '0', + "UHD": '3', + "HD": '2', + "SD": '1', + "LD": '1' + } + + rid = str(json_data["room_id"]) + json_data.pop("room_id") + rate = video_quality_options.get(video_quality, '0') + flv_data = await get_douyu_stream_data(rid, rate, cookies=cookies, proxy_addr=proxy_addr) + rtmp_url = flv_data['data'].get('rtmp_url') + rtmp_live = flv_data['data'].get('rtmp_live') + if rtmp_live: + flv_url = f'{rtmp_url}/{rtmp_live}' + json_data |= {'quality': video_quality, 'flv_url': flv_url, 'record_url': flv_url} + return json_data + + +@trace_error_decorator +async def get_yy_stream_url(json_data: dict) -> dict: + anchor_name = json_data.get('anchor_name', '') + result = { + "anchor_name": anchor_name, + "is_live": False, + } + if 'avp_info_res' in json_data: + stream_line_addr = json_data['avp_info_res']['stream_line_addr'] + cdn_info = list(stream_line_addr.values())[0] + flv_url = cdn_info['cdn_info']['url'] + result |= { + 'is_live': True, + 'title': json_data['title'], + 'quality': 'OD', + 'flv_url': flv_url, + 'record_url': flv_url + } + return result + + +@trace_error_decorator +async def get_bilibili_stream_url(json_data: dict, video_quality: str, proxy_addr: str, cookies: str) -> dict: + anchor_name = json_data["anchor_name"] + if not json_data["live_status"]: + return { + "anchor_name": anchor_name, + "is_live": False + } + + room_url = json_data['room_url'] + + video_quality_options = { + "OD": '10000', + "BD": '400', + "UHD": '250', + "HD": '150', + "SD": '80', + "LD": '80' + } + + select_quality = video_quality_options[video_quality] + play_url = await get_bilibili_stream_data( + room_url, qn=select_quality, platform='web', proxy_addr=proxy_addr, cookies=cookies) + return { + 'anchor_name': json_data['anchor_name'], + 'is_live': True, + 'title': json_data['title'], + 'quality': video_quality, + 'record_url': play_url + } + + +@trace_error_decorator +async def get_netease_stream_url(json_data: dict, video_quality: str) -> dict: + if not json_data['is_live']: + return json_data + + m3u8_url = json_data['m3u8_url'] + flv_url = None + if json_data.get('stream_list'): + stream_list = json_data['stream_list']['resolution'] + order = ['blueray', 'ultra', 'high', 'standard'] + sorted_keys = [key for key in order if key in stream_list] + while len(sorted_keys) < 5: + sorted_keys.append(sorted_keys[-1]) + video_quality, quality_index = get_quality_index(video_quality) + selected_quality = sorted_keys[quality_index] + flv_url_list = stream_list[selected_quality]['cdn'] + selected_cdn = list(flv_url_list.keys())[0] + flv_url = flv_url_list[selected_cdn] + + return { + "is_live": True, + "anchor_name": json_data['anchor_name'], + "title": json_data['title'], + 'quality': video_quality, + "m3u8_url": m3u8_url, + "flv_url": flv_url, + "record_url": flv_url or m3u8_url + } + + +async def get_stream_url(json_data: dict, video_quality: str, url_type: str = 'm3u8', spec: bool = False, + hls_extra_key: str | int = None, flv_extra_key: str | int = None) -> dict: + if not json_data['is_live']: + return json_data + + play_url_list = json_data['play_url_list'] + while len(play_url_list) < 5: + play_url_list.append(play_url_list[-1]) + + video_quality, selected_quality = get_quality_index(video_quality) + data = { + "anchor_name": json_data['anchor_name'], + "is_live": True + } + + def get_url(key): + play_url = play_url_list[selected_quality] + return play_url[key] if key else play_url + + if url_type == 'all': + m3u8_url = get_url(hls_extra_key) + flv_url = get_url(flv_extra_key) + data |= { + "m3u8_url": json_data['m3u8_url'] if spec else m3u8_url, + "flv_url": json_data['flv_url'] if spec else flv_url, + "record_url": m3u8_url + } + elif url_type == 'm3u8': + m3u8_url = get_url(hls_extra_key) + data |= {"m3u8_url": json_data['m3u8_url'] if spec else m3u8_url, "record_url": m3u8_url} + else: + flv_url = get_url(flv_extra_key) + data |= {"flv_url": flv_url, "record_url": flv_url} + data['title'] = json_data.get('title') + data['quality'] = video_quality + return data \ No newline at end of file diff --git a/test.py b/test.py new file mode 100644 index 0000000..2125df2 --- /dev/null +++ b/test.py @@ -0,0 +1,66 @@ +import os +import subprocess + +def get_git_objects(repo_path="."): + """获取 Git 仓库中所有对象的 hash 和大小""" + pack_dir = os.path.join(repo_path, ".git", "objects", "pack") + idx_files = [f for f in os.listdir(pack_dir) if f.endswith(".idx")] + + objects = [] + for idx in idx_files: + idx_path = os.path.join(pack_dir, idx) + try: + # 运行 git verify-pack + result = subprocess.check_output( + ["git", "verify-pack", "-v", idx_path], + cwd=repo_path, + text=True, + errors="ignore" + ) + for line in result.splitlines(): + parts = line.split() + # 只处理包含 "blob" 的行(文件对象) + if len(parts) >= 5 and parts[1] == "blob": + sha = parts[0] + size = int(parts[2]) # uncompressed size + objects.append((sha, size)) + except subprocess.CalledProcessError as e: + print(f"Error reading {idx_path}: {e}") + return objects + +def map_objects_to_files(repo_path="."): + """把对象 hash 映射到文件路径""" + result = subprocess.check_output( + ["git", "rev-list", "--objects", "--all"], + cwd=repo_path, + text=True, + errors="ignore" + ) + mapping = {} + for line in result.splitlines(): + parts = line.split(" ", 1) + if len(parts) == 2: + sha, path = parts + mapping[sha] = path + return mapping + +def main(): + repo_path = "." # 当前目录 + objects = get_git_objects(repo_path) + mapping = map_objects_to_files(repo_path) + + if not objects: + print("⚠️ 没找到大对象,请确认仓库里有 pack 文件。") + return + + # 按大小排序,取前 20 个 + objects.sort(key=lambda x: x[1], reverse=True) + top_n = objects[:20] + + print("Git 仓库里最大的 20 个文件:\n") + for sha, size in top_n: + path = mapping.get(sha, "(已删除或不可见文件)") + print(f"{size/1024:.2f} KB\t{sha}\t{path}") + +if __name__ == "__main__": + main() diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..01b0d0c --- /dev/null +++ b/utils.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +import json +import os +import random +import shutil +import string +from pathlib import Path +import functools +import hashlib +import re +import traceback +from typing import Any +from collections import OrderedDict +import execjs +import logger +import configparser + +OptionalStr = str | None +OptionalDict = dict | None + + +class Color: + RED = "\033[31m" + GREEN = "\033[32m" + YELLOW = "\033[33m" + BLUE = "\033[34m" + MAGENTA = "\033[35m" + CYAN = "\033[36m" + WHITE = "\033[37m" + RESET = "\033[0m" + + @staticmethod + def print_colored(text, color): + print(f"{color}{text}{Color.RESET}") + + +def trace_error_decorator(func: callable) -> callable: + @functools.wraps(func) + def wrapper(*args: list, **kwargs: dict) -> Any: + try: + return func(*args, **kwargs) + except execjs.ProgramError: + logger.warning('Failed to execute JS code. Please check if the Node.js environment') + except Exception as e: + error_line = traceback.extract_tb(e.__traceback__)[-1].lineno + error_info = f"message: type: {type(e).__name__}, {str(e)} in function {func.__name__} at line: {error_line}" + logger.error(error_info) + return [] + + return wrapper + + +def check_md5(file_path: str | Path) -> str: + with open(file_path, 'rb') as fp: + file_md5 = hashlib.md5(fp.read()).hexdigest() + return file_md5 + + +def dict_to_cookie_str(cookies_dict: dict) -> str: + cookie_str = '; '.join([f"{key}={value}" for key, value in cookies_dict.items()]) + return cookie_str + + +def read_config_value(file_path: str | Path, section: str, key: str) -> str | None: + config = configparser.ConfigParser() + + try: + config.read(file_path, encoding='utf-8-sig') + except Exception as e: + print(f"Error occurred while reading the configuration file: {e}") + return None + + if section in config: + if key in config[section]: + return config[section][key] + else: + print(f"Key [{key}] does not exist in section [{section}].") + else: + print(f"Section [{section}] does not exist in the file.") + + return None + + +def update_config(file_path: str | Path, section: str, key: str, new_value: str) -> None: + config = configparser.ConfigParser() + + try: + config.read(file_path, encoding='utf-8-sig') + except Exception as e: + print(f"An error occurred while reading the configuration file: {e}") + return + + if section not in config: + print(f"Section [{section}] does not exist in the file.") + return + + # 转义%字符 + escaped_value = new_value.replace('%', '%%') + config[section][key] = escaped_value + + try: + with open(file_path, 'w', encoding='utf-8-sig') as configfile: + config.write(configfile) + print(f"The value of {key} under [{section}] in the configuration file has been updated.") + except Exception as e: + print(f"Error occurred while writing to the configuration file: {e}") + + +def get_file_paths(directory: str) -> list: + file_paths = [] + for root, dirs, files in os.walk(directory): + for file in files: + file_paths.append(os.path.join(root, file)) + return file_paths + + +def remove_emojis(text: str, replace_text: str = '') -> str: + emoji_pattern = re.compile( + "[" + "\U0001F1E0-\U0001F1FF" # flags (iOS) + "\U0001F300-\U0001F5FF" # symbols & pictographs + "\U0001F600-\U0001F64F" # emoticons + "\U0001F680-\U0001F6FF" # transport & map symbols + "\U0001F700-\U0001F77F" # alchemical symbols + "\U0001F780-\U0001F7FF" # Geometric Shapes Extended + "\U0001F800-\U0001F8FF" # Supplemental Arrows-C + "\U0001F900-\U0001F9FF" # Supplemental Symbols and Pictographs + "\U0001FA00-\U0001FA6F" # Chess Symbols + "\U0001FA70-\U0001FAFF" # Symbols and Pictographs Extended-A + "\U00002702-\U000027B0" # Dingbats + "]+", + flags=re.UNICODE + ) + return emoji_pattern.sub(replace_text, text) + + +def remove_duplicate_lines(file_path: str | Path) -> None: + unique_lines = OrderedDict() + text_encoding = 'utf-8-sig' + with open(file_path, 'r', encoding=text_encoding) as input_file: + for line in input_file: + unique_lines[line.strip()] = None + with open(file_path, 'w', encoding=text_encoding) as output_file: + for line in unique_lines: + output_file.write(line + '\n') + + +def check_disk_capacity(file_path: str | Path, show: bool = False) -> float: + absolute_path = os.path.abspath(file_path) + directory = os.path.dirname(absolute_path) + disk_usage = shutil.disk_usage(directory) + disk_root = Path(directory).anchor + free_space_gb = disk_usage.free / (1024 ** 3) + if show: + print(f"{disk_root} Total: {disk_usage.total / (1024 ** 3):.2f} GB " + f"Used: {disk_usage.used / (1024 ** 3):.2f} GB " + f"Free: {free_space_gb:.2f} GB\n") + return free_space_gb + + +def handle_proxy_addr(proxy_addr): + if proxy_addr: + if not proxy_addr.startswith('http'): + proxy_addr = 'http://' + proxy_addr + else: + proxy_addr = None + return proxy_addr + + +def generate_random_string(length: int) -> str: + characters = string.ascii_uppercase + string.digits + random_string = ''.join(random.choices(characters, k=length)) + return random_string + + +def jsonp_to_json(jsonp_str: str) -> OptionalDict: + pattern = r'(\w+)\((.*)\);?$' + match = re.search(pattern, jsonp_str) + + if match: + _, json_str = match.groups() + json_obj = json.loads(json_str) + return json_obj + else: + raise Exception("No JSON data found in JSONP response.") + + +def replace_url(file_path: str | Path, old: str, new: str) -> None: + with open(file_path, 'r', encoding='utf-8-sig') as f: + content = f.read() + if old in content: + with open(file_path, 'w', encoding='utf-8-sig') as f: + f.write(content.replace(old, new)) diff --git a/verify.txt b/verify.txt new file mode 100644 index 0000000..e69de29