feat: add migu live record

This commit is contained in:
ihmily 2025-07-04 17:28:44 +08:00
parent 952eeb9b7c
commit ae8200e01c
5 changed files with 314 additions and 81 deletions

View File

@ -108,6 +108,7 @@ youtube_cookie =
taobao_cookie =
jd_cookie =
faceit_cookie =
migu_cookie =
[Authorization]
popkontv_token =

View File

@ -190,6 +190,10 @@ LIVE_STREAM_CONFIG = {
"faceit": {
"url": "https://www.faceit.com/zh/players/Compl1/stream",
"func": spider.get_faceit_stream_data,
},
"migu": {
"url": "https://www.miguvideo.com/p/live/120000541321",
"func": spider.get_migu_stream_url,
}
}
@ -209,3 +213,4 @@ def test_live_stream(platform_name: str, proxy_addr=None, cookies=None) -> None:
if __name__ == "__main__":
platform = "douyin"
test_live_stream(platform)

19
main.py
View File

@ -4,7 +4,7 @@
Author: Hmily
GitHub: https://github.com/ihmily
Date: 2023-07-17 23:52:05
Update: 2025-06-14 12:19:00
Update: 2025-07-04 17:23:00
Copyright (c) 2023-2025 by Hmily, All Rights Reserved.
Function: Record live stream video.
"""
@ -38,9 +38,9 @@ from ffmpeg_install import (
check_ffmpeg, ffmpeg_path, current_env_path
)
version = "v4.0.3"
version = "v4.0.5"
platforms = ("\n国内站点:抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo|blued|网易CC|千度热播|猫耳FM|Look|TwitCasting|百度|微博|"
"酷狗|花椒|流星|Acfun|畅聊|映客|音播|知乎|嗨秀|VV星球|17Live|浪Live|漂漂|六间房|乐嗨|花猫|淘宝|京东"
"酷狗|花椒|流星|Acfun|畅聊|映客|音播|知乎|嗨秀|VV星球|17Live|浪Live|漂漂|六间房|乐嗨|花猫|淘宝|京东|咪咕"
"\n海外站点TikTok|SOOP|PandaTV|WinkTV|FlexTV|PopkonTV|TwitchTV|LiveMe|ShowRoom|CHZZK|Shopee|"
"Youtube|Faceit")
@ -92,7 +92,7 @@ def display_info() -> None:
time.sleep(5)
while True:
try:
sys.stdout.flush() # 强制刷新输出缓冲区
sys.stdout.flush()
time.sleep(5)
if Path(sys.executable).name != 'pythonw.exe':
os.system(clear_command)
@ -925,6 +925,12 @@ def start_record(url_data: tuple, count_variable: int = -1) -> None:
else:
logger.error("错误信息: 网络异常请检查本网络是否能正常访问faceit直播平台")
elif record_url.find("www.miguvideo.com") > -1 or record_url.find("m.miguvideo.com") > -1:
platform = '咪咕直播'
with semaphore:
port_info = asyncio.run(spider.get_migu_stream_url(
url=record_url, proxy_addr=proxy_address, cookies=migu_cookie))
elif record_url.find(".m3u8") > -1 or record_url.find(".flv") > -1:
platform = '自定义录制直播'
port_info = {
@ -1765,6 +1771,7 @@ while True:
taobao_cookie = read_config_value(config, 'Cookie', 'taobao_cookie', '')
jd_cookie = read_config_value(config, 'Cookie', 'jd_cookie', '')
faceit_cookie = read_config_value(config, 'Cookie', 'faceit_cookie', '')
migu_cookie = read_config_value(config, 'Cookie', 'migu_cookie', '')
video_save_type_list = ("FLV", "MKV", "TS", "MP4", "MP3音频", "M4A音频")
if video_save_type and video_save_type.upper() in video_save_type_list:
@ -1882,7 +1889,9 @@ while True:
'e.tb.cn',
'huodong.m.taobao.com',
'3.cn',
'eco.m.jd.com'
'eco.m.jd.com',
'www.miguvideo.com',
'm.miguvideo.com'
]
overseas_platform_host = [
'www.tiktok.com',

143
src/javascript/migu.js Normal file
View 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);
});

View File

@ -4,14 +4,16 @@
Author: Hmily
GitHub: https://github.com/ihmily
Date: 2023-07-15 23:15:00
Update: 2025-06-14 12:19:00
Update: 2025-07-04 17:23:00
Copyright (c) 2023-2025 by Hmily, All Rights Reserved.
Function: Get live stream data.
"""
import hashlib
import random
import subprocess
import time
import uuid
from operator import itemgetter
import urllib.parse
import urllib.error
@ -3017,3 +3019,76 @@ async def get_faceit_stream_data(url: str, proxy_addr: OptionalStr = None, cooki
else:
result = {'anchor_name': anchor_name, 'is_live': False}
return result
@trace_error_decorator
async def get_migu_stream_url(url: str, proxy_addr: OptionalStr = None, cookies: OptionalStr = None) -> dict:
headers = {
'origin': 'https://www.miguvideo.com',
'referer': 'https://www.miguvideo.com/',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0',
'appCode': 'miguvideo_default_www',
'appId': 'miguvideo',
'channel': 'H5',
}
if cookies:
headers['Cookie'] = cookies
web_id = url.split('?')[0].rsplit('/')[-1]
api = f'https://vms-sc.miguvideo.com/vms-match/v6/staticcache/basic/basic-data/{web_id}/miguvideo'
json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)
json_data = json.loads(json_str)
room_id = json_data['body']['pId']
anchor_name = json_data['body']['title']
live_title = json_data['body'].get('title') + '-' + json_data['body'].get('detailPageTitle', '')
result = {"anchor_name": anchor_name, "is_live": False}
if not room_id:
raise RuntimeError("Room ID fetch error")
params = {
'contId': room_id,
'rateType': '3',
'clientId': str(uuid.uuid4()),
'timestamp': int(time.time() * 1000),
'flvEnable': 'true',
'xh265': 'false',
'chip': 'mgwww',
'channelId': '',
}
api = f'https://webapi.miguvideo.com/gateway/playurl/v3/play/playurl?{urllib.parse.urlencode(params)}'
json_str = await async_req(api, proxy_addr=proxy_addr, headers=headers)
json_data = json.loads(json_str)
live_status = json_data['body']['content']['currentLive']
if live_status != '1':
return result
else:
result['title'] = live_title
source_url = json_data['body']['urlInfo']['url']
async def _get_dd_calcu(url):
try:
result = subprocess.run(
["node", f"{JS_SCRIPT_PATH}/migu.js", url],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except execjs.ProgramError:
raise execjs.ProgramError('Failed to execute JS code. Please check if the Node.js environment')
ddCalcu = await _get_dd_calcu(source_url)
real_source_url = f'{source_url}&ddCalcu={ddCalcu}&sv=10010'
if '.m3u8' in real_source_url:
m3u8_url = await async_req(
real_source_url, proxy_addr=proxy_addr, headers=headers, redirect_url=True)
result['m3u8_url'] = m3u8_url
result['record_url'] = m3u8_url
else:
result['flv_url'] = real_source_url
result['record_url'] = real_source_url
result['is_live'] = True
return result