Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6760dc99f | ||
|
|
88c53c37ff | ||
|
|
64abdc03be | ||
|
|
348bc71862 | ||
|
|
64e266ecf1 | ||
|
|
49f3845b37 | ||
|
|
9c09960431 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
cache/
|
||||
__pycache__/
|
||||
14
__init__.py
Normal file
14
__init__.py
Normal file
@@ -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()
|
||||
162
back
Normal file
162
back
Normal file
@@ -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/640087893839"
|
||||
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)
|
||||
203
back2
Normal file
203
back2
Normal file
@@ -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)
|
||||
71
douyin_to_youtube.log
Normal file
71
douyin_to_youtube.log
Normal file
@@ -0,0 +1,71 @@
|
||||
2025-09-13 09:42:21,441 [INFO] httpx: HTTP Request: GET https://live.douyin.com/848698179845 "HTTP/2 200 OK"
|
||||
2025-09-13 09:42:22,592 [INFO] httpx: HTTP Request: HEAD http://pull-hls-l13.douyincdn.com/stage/stream-117958962069897656_or4.m3u8?arch_hrchy=w1&exp_hrchy=w1&expire=1758332541&major_anchor_level=common&sign=5d71c5252347afa2a3b465f133ed58f2&t_id=037-20250913094221234B680EF32A4439DD87-GEORFp&codec=h264 "HTTP/1.1 200 OK"
|
||||
2025-09-13 09:42:22,597 [INFO] __main__: 实际直播流: http://pull-hls-l13.douyincdn.com/stage/stream-117958962069897656_or4.m3u8?arch_hrchy=w1&exp_hrchy=w1&expire=1758332541&major_anchor_level=common&sign=5d71c5252347afa2a3b465f133ed58f2&t_id=037-20250913094221234B680EF32A4439DD87-GEORFp&codec=h264
|
||||
2025-09-13 09:42:22,602 [INFO] __main__: [抖音_7dbe64a7] 启动推流,目标 YouTube Key: qxvb-r47b-r5ju-6ud3-6k7z
|
||||
2025-09-13 09:42:22,604 [INFO] __main__: [抖音_7dbe64a7] 实际直播流: http://pull-hls-l13.douyincdn.com/stage/stream-117958962069897656_or4.m3u8?arch_hrchy=w1&exp_hrchy=w1&expire=1758332541&major_anchor_level=common&sign=5d71c5252347afa2a3b465f133ed58f2&t_id=037-20250913094221234B680EF32A4439DD87-GEORFp&codec=h264
|
||||
2025-09-13 09:42:25,655 [INFO] __main__: [抖音_7dbe64a7] 拉流进程启动,PID: 7728
|
||||
2025-09-13 09:42:40,372 [INFO] __main__: [抖音_7dbe64a7] 推流进程启动,PID: 19568
|
||||
2025-09-13 09:42:44,609 [INFO] __main__: [抖音_7dbe64a7] [ABR] 调整码率: 6000k -> 5500k (缓冲比例: 12.80)
|
||||
2025-09-13 09:42:49,613 [INFO] __main__: [抖音_7dbe64a7] [ABR] 调整码率: 5500k -> 5000k (缓冲比例: 13.13)
|
||||
2025-09-13 09:42:54,616 [INFO] __main__: [抖音_7dbe64a7] [ABR] 调整码率: 5000k -> 4500k (缓冲比例: 11.67)
|
||||
2025-09-13 09:42:59,619 [INFO] __main__: [抖音_7dbe64a7] [ABR] 调整码率: 4500k -> 4000k (缓冲比例: 12.93)
|
||||
2025-09-13 09:43:04,620 [INFO] __main__: [抖音_7dbe64a7] [ABR] 调整码率: 4000k -> 3500k (缓冲比例: 13.33)
|
||||
2025-09-13 09:43:09,622 [INFO] __main__: [抖音_7dbe64a7] [ABR] 调整码率: 3500k -> 3000k (缓冲比例: 13.33)
|
||||
2025-09-13 09:44:04,081 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:04,583 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:05,085 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:05,586 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:06,088 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:06,589 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:07,091 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:07,593 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:08,095 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:08,597 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:09,098 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:09,602 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:10,107 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:10,615 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:11,166 [ERROR] __main__: [抖音_7dbe64a7] 推流写入异常: [Errno 22] Invalid argument
|
||||
2025-09-13 09:44:11,725 [INFO] __main__: 接收到信号 2,停止推流
|
||||
2025-09-13 09:44:11,726 [INFO] __main__: 停止所有推流...
|
||||
2025-09-13 09:44:11,738 [INFO] __main__: 拉流进程已停止
|
||||
2025-09-13 09:46:22,098 [INFO] httpx: HTTP Request: GET https://live.douyin.com/848698179845 "HTTP/2 200 OK"
|
||||
2025-09-13 09:46:24,076 [INFO] httpx: HTTP Request: HEAD http://pull-hls-l13.douyincdn.com/stage/stream-117958962069897656_or4.m3u8?arch_hrchy=w1&exp_hrchy=w1&expire=1758332782&major_anchor_level=common&sign=4c8db99e7a0c26a419faf2a1e34eae74&t_id=037-20250913094621681A1B914C390282BA5E-Rxo2Sa&codec=h264 "HTTP/1.1 200 OK"
|
||||
2025-09-13 09:46:24,081 [INFO] __main__: 实际直播流: http://pull-hls-l13.douyincdn.com/stage/stream-117958962069897656_or4.m3u8?arch_hrchy=w1&exp_hrchy=w1&expire=1758332782&major_anchor_level=common&sign=4c8db99e7a0c26a419faf2a1e34eae74&t_id=037-20250913094621681A1B914C390282BA5E-Rxo2Sa&codec=h264
|
||||
2025-09-13 09:46:24,083 [INFO] __main__: [抖音_ea368365] 启动推流,目标 YouTube Key: qxvb-r47b-r5ju-6ud3-6k7z
|
||||
2025-09-13 09:46:24,084 [INFO] __main__: [抖音_ea368365] 实际直播流: http://pull-hls-l13.douyincdn.com/stage/stream-117958962069897656_or4.m3u8?arch_hrchy=w1&exp_hrchy=w1&expire=1758332782&major_anchor_level=common&sign=4c8db99e7a0c26a419faf2a1e34eae74&t_id=037-20250913094621681A1B914C390282BA5E-Rxo2Sa&codec=h264
|
||||
2025-09-13 09:46:26,500 [INFO] __main__: [抖音_ea368365] 拉流进程启动,PID: 9880
|
||||
2025-09-13 09:46:29,093 [WARNING] __main__: [抖音_ea368365] 初始缓冲不足,推流延迟启动
|
||||
2025-09-13 09:46:39,096 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 6000k -> 5800k (缓冲比例: 13.60)
|
||||
2025-09-13 09:46:49,098 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 5800k -> 5600k (缓冲比例: 19.27)
|
||||
2025-09-13 09:46:59,100 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 5600k -> 5400k (缓冲比例: 27.73)
|
||||
2025-09-13 09:47:09,102 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 5400k -> 5200k (缓冲比例: 33.30)
|
||||
2025-09-13 09:47:19,104 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 5200k -> 5000k (缓冲比例: 33.33)
|
||||
2025-09-13 09:47:29,105 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 5000k -> 4800k (缓冲比例: 33.33)
|
||||
2025-09-13 09:47:39,107 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 4800k -> 4600k (缓冲比例: 33.33)
|
||||
2025-09-13 09:47:49,109 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 4600k -> 4400k (缓冲比例: 33.33)
|
||||
2025-09-13 09:47:59,112 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 4400k -> 4200k (缓冲比例: 33.33)
|
||||
2025-09-13 09:48:09,115 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 4200k -> 4000k (缓冲比例: 33.33)
|
||||
2025-09-13 09:48:19,120 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 4000k -> 3800k (缓冲比例: 33.33)
|
||||
2025-09-13 09:48:29,123 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 3800k -> 3600k (缓冲比例: 33.33)
|
||||
2025-09-13 09:48:39,127 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 3600k -> 3400k (缓冲比例: 33.33)
|
||||
2025-09-13 09:48:49,129 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 3400k -> 3200k (缓冲比例: 33.33)
|
||||
2025-09-13 09:48:59,132 [INFO] __main__: [抖音_ea368365] [ABR] 调整码率: 3200k -> 3000k (缓冲比例: 33.33)
|
||||
2025-09-13 09:49:35,107 [INFO] __main__: 接收到信号 2,停止推流
|
||||
2025-09-13 09:49:35,108 [INFO] __main__: 停止所有推流...
|
||||
2025-09-13 09:49:35,119 [INFO] __main__: 拉流进程已停止
|
||||
2025-09-13 09:49:39,251 [INFO] httpx: HTTP Request: GET https://live.douyin.com/848698179845 "HTTP/2 200 OK"
|
||||
2025-09-13 09:49:40,464 [INFO] httpx: HTTP Request: HEAD http://pull-hls-l13.douyincdn.com/stage/stream-117958962069897656_or4.m3u8?arch_hrchy=w1&exp_hrchy=w1&expire=1758332979&major_anchor_level=common&sign=1b74463ce878bc0174a578edcfa719c1&t_id=037-202509130949385D15825682C4A5F8F47C-iIt4V6&codec=h264 "HTTP/1.1 200 OK"
|
||||
2025-09-13 09:49:40,469 [INFO] __main__: 实际直播流: http://pull-hls-l13.douyincdn.com/stage/stream-117958962069897656_or4.m3u8?arch_hrchy=w1&exp_hrchy=w1&expire=1758332979&major_anchor_level=common&sign=1b74463ce878bc0174a578edcfa719c1&t_id=037-202509130949385D15825682C4A5F8F47C-iIt4V6&codec=h264
|
||||
2025-09-13 09:49:40,470 [INFO] __main__: [抖音_d46e59a8] 启动推流,目标 YouTube Key: qxvb-r47b-r5ju-6ud3-6k7z
|
||||
2025-09-13 09:49:40,470 [INFO] __main__: [抖音_d46e59a8] 实际直播流: http://pull-hls-l13.douyincdn.com/stage/stream-117958962069897656_or4.m3u8?arch_hrchy=w1&exp_hrchy=w1&expire=1758332979&major_anchor_level=common&sign=1b74463ce878bc0174a578edcfa719c1&t_id=037-202509130949385D15825682C4A5F8F47C-iIt4V6&codec=h264
|
||||
2025-09-13 09:49:42,555 [INFO] __main__: [抖音_d46e59a8] 拉流进程启动,PID: 20372
|
||||
2025-09-13 09:49:50,472 [WARNING] __main__: [抖音_d46e59a8] 初始缓冲不足(队列大小: 0),推流延迟启动
|
||||
2025-09-13 09:50:00,475 [INFO] __main__: [抖音_d46e59a8] [ABR] 调整码率: 6000k -> 5900k (缓冲比例: 7.03)
|
||||
2025-09-13 09:50:10,476 [INFO] __main__: [抖音_d46e59a8] [ABR] 调整码率: 5900k -> 5800k (缓冲比例: 18.30)
|
||||
2025-09-13 09:50:20,478 [INFO] __main__: [抖音_d46e59a8] [ABR] 调整码率: 5800k -> 5700k (缓冲比例: 23.90)
|
||||
2025-09-13 09:50:30,480 [INFO] __main__: [抖音_d46e59a8] [ABR] 调整码率: 5700k -> 5600k (缓冲比例: 30.57)
|
||||
2025-09-13 09:50:40,481 [INFO] __main__: [抖音_d46e59a8] [ABR] 调整码率: 5600k -> 5500k (缓冲比例: 33.33)
|
||||
2025-09-13 09:50:42,864 [INFO] __main__: 接收到信号 2,停止推流
|
||||
2025-09-13 09:50:42,866 [INFO] __main__: 停止所有推流...
|
||||
2025-09-13 09:50:42,877 [INFO] __main__: 拉流进程已停止
|
||||
0
http_clients/__init__.py
Normal file
0
http_clients/__init__.py
Normal file
BIN
http_clients/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
http_clients/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
http_clients/__pycache__/async_http.cpython-312.pyc
Normal file
BIN
http_clients/__pycache__/async_http.cpython-312.pyc
Normal file
Binary file not shown.
59
http_clients/async_http.py
Normal file
59
http_clients/async_http.py
Normal file
@@ -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
|
||||
88
http_clients/sync_http.py
Normal file
88
http_clients/sync_http.py
Normal file
@@ -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
|
||||
220
initializer.py
Normal file
220
initializer.py
Normal file
@@ -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()
|
||||
1
javascript/crypto-js.min.js
vendored
Normal file
1
javascript/crypto-js.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
539
javascript/haixiu.js
Normal file
539
javascript/haixiu.js
Normal file
@@ -0,0 +1,539 @@
|
||||
var closeGeetest = !1, _a123 = "haija1c7", _b2x = "xiuhc2a6", _c3y = "anchc3a5", _dx34 = "famic7a2", _hf_constants1 = "sowh1e", _hf_constants2 = "1000ha", _hf_constants3 = "butr12", _hf_constants4 = "2000h5", _gf_constants1 = "lehaaj", _gf_constants2 = "1000ax", _gf_constants3 = "lehaData"
|
||||
let CryptoJS = null;
|
||||
function EnmoliParamter() {
|
||||
|
||||
this._a123 = eval("_hf_constants1"),
|
||||
this._b2x = eval("_hf_constants2"),
|
||||
this._c3y = eval("_hf_constants3"),
|
||||
this._dx34 = eval("_hf_constants4"),
|
||||
this.getA123 = function() {
|
||||
return this._a123
|
||||
}
|
||||
,
|
||||
this.getB2X = function() {
|
||||
return this._b2x
|
||||
}
|
||||
,
|
||||
this.getC3Y = function() {
|
||||
return this._c3y
|
||||
}
|
||||
,
|
||||
this.getDX34 = function() {
|
||||
return this._dx34
|
||||
}
|
||||
}
|
||||
EnmoliParamter.prototype = {
|
||||
aa: function(e, t) {
|
||||
if (e === t)
|
||||
return e;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e;
|
||||
if (e.arity === t.arity && e.string === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
ac: function(e, t) {
|
||||
if (e === t)
|
||||
return e;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e;
|
||||
if (e.id === t.id && (e = CryptoJS.MD5(e) + ""),
|
||||
e.arity1 === t.arity && e.string2 === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
ad: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e;
|
||||
if (e.arity2 === t.arity && e.string3 === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
ae: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e.number === t.number;
|
||||
if (e.arity3 === t.arity && e.string4 === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
af: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e.number === t.number;
|
||||
if (e.arity4 === t.arity && e.string5 === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
ah: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e;
|
||||
if (e.arity6 === t.arity && e.string9 === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
ai: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e;
|
||||
if (e.arity2 === t.arity5 && e.string === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
aj: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e.number === t.number;
|
||||
if (e.arity44 === t.arity42 && e.string === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
ak: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e.number === t.number;
|
||||
if (e.arity21 === t.arity322 && e.string === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
ax: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e.number === t.number;
|
||||
if (e.arity22 === t.arity32 && e.string === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
az: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e.number === t.number;
|
||||
if (e.arity42 === t.arity57 && e.string2 === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return e
|
||||
},
|
||||
are_similar: function(e, t) {
|
||||
if (e === t)
|
||||
return !0;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return !0;
|
||||
return !0
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return e;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e.number === t.number;
|
||||
if (e.arity === t.arity && e.string === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third);
|
||||
case "function":
|
||||
case "regexp":
|
||||
return e;
|
||||
default:
|
||||
return !0
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e.second.string === t.second.string && "(string)" === t.second.id;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return e.second.string === t.second.string && "(string)" === e.second.id
|
||||
}
|
||||
return !1
|
||||
},
|
||||
ayz: function(e, t) {
|
||||
if (e === t)
|
||||
return e;
|
||||
if (Array.isArray(e)) {
|
||||
if (Array.isArray(t) && e.length === t.length) {
|
||||
var i;
|
||||
for (i = 0; i < e.length; i += 1)
|
||||
if (!this.are_similar(e[i], t[i]))
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
return e
|
||||
}
|
||||
if (Array.isArray(t))
|
||||
return t;
|
||||
if ("(number)" === e.id && "(number)" === t.id)
|
||||
return e.number === t.number;
|
||||
if (e.arity42 === t.arity57 && e.string2 === t.string)
|
||||
switch (e.arity) {
|
||||
case "prefix":
|
||||
case "suffix":
|
||||
case "infix":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second);
|
||||
case "ternary":
|
||||
return this.are_similar(e.first, t.first) && this.are_similar(e.second, t.second) && this.are_similar(e.third, t.third)
|
||||
}
|
||||
else {
|
||||
if ("." === e.id && "[" === t.id && "infix" === t.arity)
|
||||
return e;
|
||||
if ("[" === e.id && "infix" === e.arity && "." === t.id)
|
||||
return t
|
||||
}
|
||||
return this.getA123().substring(4) + this.getB2X().substring(4) + this.getC3Y().substring(4) + this.getDX34().substring(4)
|
||||
}
|
||||
}
|
||||
function EnmoliSubmiter() {}
|
||||
EnmoliSubmiter.prototype = {
|
||||
bsq: function(e) {
|
||||
var t = this.pf(e)
|
||||
, i = this.as(t);
|
||||
return this.brm(i)
|
||||
},
|
||||
pf: function(e) {
|
||||
var t = {};
|
||||
for (var i in e)
|
||||
"" !== e[i] && (t[i] = e[i]);
|
||||
return t
|
||||
},
|
||||
as: function(e) {
|
||||
for (var t = {}, i = Object.keys(e).sort(), o = 0; o < i.length; o++) {
|
||||
var n = i[o];
|
||||
t[n] = e[n]
|
||||
}
|
||||
return t
|
||||
},
|
||||
brm: function(e) {
|
||||
var t = this.cls(e)
|
||||
, i = new EnmoliParamter;
|
||||
return this.pt(t, i.ayz(t, "showselfAnchorVisitorParameters"))
|
||||
},
|
||||
cls: function(e) {
|
||||
var t = "";
|
||||
for (var i in e)
|
||||
t = t + i + "=" + e[i] + "&";
|
||||
return t = t.substring(0, t.length - 1)
|
||||
},
|
||||
pt: function(e, t) {
|
||||
var i = new EnmoliParamter;
|
||||
return e += t,
|
||||
i.az(i.ax(i.ak(i.aj(i.ai(i.ah(i.af(i.ae(i.ad(i.ac(i.aa(e, e + "01" + t), e + "escape" + t), e + "same"), e + "visitor"), "anchor"), e + "person"), e + "ax" + t), "ae" + t), e + "ax" + t), e + "inspect" + t), "af" + t)
|
||||
},
|
||||
bnu: function(e, t) {
|
||||
for (var i = e.split("&"), o = 0; o < i.length; o++) {
|
||||
var n = i[o].split("=");
|
||||
2 == n.length && (t[n[0]] = encodeURIComponent($.trim(n[1])).toString())
|
||||
}
|
||||
},
|
||||
bn: function(e, t) {
|
||||
for (var i in e)
|
||||
"object" == typeof e[i] ? t[i] = encodeURIComponent($.trim(JSON.stringify(e[i]))).toString() : t[i] = encodeURIComponent($.trim(e[i])).toString()
|
||||
}
|
||||
}
|
||||
var enmoliSubmiter = new EnmoliSubmiter();
|
||||
|
||||
function sign(options, cryptoJSPath){
|
||||
CryptoJS = require(cryptoJSPath);
|
||||
return enmoliSubmiter.bsq(options);
|
||||
}
|
||||
module.exports = {
|
||||
sign
|
||||
};
|
||||
|
||||
// const options = {
|
||||
// "accessToken": "pLXSC%252FXJ0asc1I21tVL5FYZhNJn2Zg6d7m94umCnpgL%252BuVm31GQvyw%253D%253D",
|
||||
// "tku": "3000006",
|
||||
// "c": "10138100100000",
|
||||
// "_st1": "1728621076958"
|
||||
// }
|
||||
// const cryptoJSPath = './crypto-js.min.js'
|
||||
// console.log(sign(options, cryptoJSPath))
|
||||
425
javascript/liveme.js
Normal file
425
javascript/liveme.js
Normal file
@@ -0,0 +1,425 @@
|
||||
/**
|
||||
* @author Hmily
|
||||
* @createTime 2024-10-10
|
||||
*/
|
||||
|
||||
const id = 1;
|
||||
const r = `${new Date().getTime()}${id}`
|
||||
const Am = "LM6000101139961122666757";
|
||||
const rl = "undefined"
|
||||
|
||||
function createRandom(length = 32) {
|
||||
let result = "";
|
||||
const characters = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678";
|
||||
for (let i = 0; i < length; ++i) {
|
||||
const randomIndex = Math.floor(Math.random() * characters.length);
|
||||
result += characters.charAt(randomIndex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function createSignature(input = "4l4m5") {
|
||||
let signature = "";
|
||||
let number = 0;
|
||||
for (let i = 0; i < input.length; ++i) {
|
||||
const charCode = input.charCodeAt(i);
|
||||
if (charCode >= 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
|
||||
};
|
||||
143
javascript/migu.js
Normal file
143
javascript/migu.js
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Function to get the ddCalcu parameter value
|
||||
* @param {string} inputUrl - The original URL before encryption
|
||||
* @returns {Promise<string>} - 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);
|
||||
});
|
||||
78
javascript/taobao-sign.js
Normal file
78
javascript/taobao-sign.js
Normal file
@@ -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)
|
||||
564
javascript/x-bogus.js
Normal file
564
javascript/x-bogus.js
Normal file
File diff suppressed because one or more lines are too long
201
live.py
Normal file
201
live.py
Normal file
@@ -0,0 +1,201 @@
|
||||
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=2500, min_bitrate=1500, audio_bitrate=128,
|
||||
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*2}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
|
||||
|
||||
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 - 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=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)
|
||||
109
liveback
Normal file
109
liveback
Normal file
@@ -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)
|
||||
43
logger.py
Normal file
43
logger.py
Normal file
@@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import os
|
||||
import sys
|
||||
from loguru import logger
|
||||
|
||||
logger.remove()
|
||||
|
||||
custom_format = "<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> - <level>{message}</level>"
|
||||
|
||||
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'
|
||||
)
|
||||
0
logs/PlayURL.log
Normal file
0
logs/PlayURL.log
Normal file
0
logs/streamget.log
Normal file
0
logs/streamget.log
Normal file
92
proxy.py
Normal file
92
proxy.py
Normal file
@@ -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)
|
||||
158
room.py
Normal file
158
room.py
Normal file
@@ -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)
|
||||
446
stream.py
Normal file
446
stream.py
Normal file
@@ -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
|
||||
193
utils.py
Normal file
193
utils.py
Normal file
@@ -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))
|
||||
Reference in New Issue
Block a user