Fix(Huya): refactor anticode generation using getCdnTokenInfoEx API (#1575)
* Fix(Huya): refactor anticode generation using getCdnTokenInfoEx API * chore: cleanup * chore: get random sHuYaUA * chore: 代码复用
This commit is contained in:
1 parent
218b8bd127
commit
caf106c3f8
20 files changed
+2801
-1991
No files matched your search
@@ -191,7 +191,7 @@ const Dashboard: React.FC = () => {
|
||||
{Object.entries(plugins)
|
||||
.filter(([key]) => key !== 'Cookie')
|
||||
.map(([key, Plugin]) => (
|
||||
<Plugin entity={entity} list={list} />
|
||||
<Plugin key={key} entity={entity} list={list} />
|
||||
))}
|
||||
<plugins.Cookie entity={entity} list={list} />
|
||||
</Collapse>
|
||||
|
||||
@@ -17,14 +17,6 @@ interface LogContentProps {
|
||||
}
|
||||
|
||||
const LogContent = ({ logs, logContainerRef, isLoading }: LogContentProps) => {
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 判断滚动条是否接近底部
|
||||
const isScrolledToBottom = () => {
|
||||
const containers = document.getElementsByClassName('log-container');
|
||||
@@ -53,6 +45,14 @@ const LogContent = ({ logs, logContainerRef, isLoading }: LogContentProps) => {
|
||||
}
|
||||
}, [logs]);
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
|
||||
<Spin size="large" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className="log-container"
|
||||
@@ -151,6 +151,7 @@ export default function LogViewer() {
|
||||
wsRef.current.close()
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeTab])
|
||||
|
||||
const handleFileChange = (value: string) => {
|
||||
@@ -211,7 +212,7 @@ export default function LogViewer() {
|
||||
activeKey={activeTab}
|
||||
onChange={handleFileChange}
|
||||
tabBarExtraContent={
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center'}}>
|
||||
<div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
|
||||
<Button
|
||||
icon={<IconSave />}
|
||||
onClick={() => (window.location.href = `/static/${activeTab}.log`)}
|
||||
|
||||
+8
-7
@@ -12,9 +12,10 @@ interface PlayerConfig {
|
||||
|
||||
function playFlv(video: HTMLVideoElement, url: string, art: Artplayer) {
|
||||
if (mpegts.isSupported()) {
|
||||
if (art.flv) {
|
||||
art.flv.destroy()
|
||||
art.flv = null
|
||||
const artWithFlv = art as Artplayer & { flv?: mpegts.Player | null }
|
||||
if (artWithFlv.flv) {
|
||||
artWithFlv.flv.destroy()
|
||||
artWithFlv.flv = null
|
||||
}
|
||||
|
||||
const flv = mpegts.createPlayer({
|
||||
@@ -22,11 +23,11 @@ function playFlv(video: HTMLVideoElement, url: string, art: Artplayer) {
|
||||
url: url,
|
||||
})
|
||||
|
||||
art.flv = flv
|
||||
artWithFlv.flv = flv
|
||||
art.on('destroy', () => {
|
||||
if (art.flv) {
|
||||
art.flv.destroy()
|
||||
art.flv = null
|
||||
if (artWithFlv.flv) {
|
||||
artWithFlv.flv.destroy()
|
||||
artWithFlv.flv = null
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
+228
-258
@@ -5,7 +5,7 @@ import time
|
||||
import html
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from urllib.parse import parse_qs, unquote
|
||||
from urllib.parse import parse_qs, unquote, quote
|
||||
from async_lru import alru_cache
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -21,17 +21,18 @@ from ..engine.download import DownloadBase
|
||||
from . import logger, match1, json_loads
|
||||
|
||||
from biliup.plugins.huya_wup import Wup, DEFAULT_TICKET_NUMBER
|
||||
from biliup.plugins.huya_wup.packet import (
|
||||
HuyaGetCdnTokenReq,
|
||||
HuyaGetCdnTokenRsp
|
||||
)
|
||||
from biliup.plugins.huya_wup.packet import *
|
||||
from biliup.plugins.huya_wup.wup_struct.UserId import HuyaUserId
|
||||
|
||||
HUYA_WEB_BASE_URL = "https://www.huya.com"
|
||||
HUYA_MOBILE_BASE_URL = "https://m.huya.com"
|
||||
HUYA_MP_BASE_URL = "https://mp.huya.com"
|
||||
HUYA_WUP_BASE_URL = "https://wup.huya.com"
|
||||
HUYA_WUP_YST_URL = "https://snmhuya.yst.aisee.tv"
|
||||
HUYA_WEB_ROOM_DATA_REGEX = r"var TT_ROOM_DATA = (.*?);"
|
||||
|
||||
rotl64 = lambda t: ((t & 0xFFFFFFFF) << 8 | (t & 0xFFFFFFFF) >> 24) & 0xFFFFFFFF | (t & ~0xFFFFFFFF)
|
||||
|
||||
@Plugin.download(regexp=r'https?://(?:(?:www|m)\.)?huya\.com')
|
||||
class Huya(DownloadBase):
|
||||
def __init__(self, fname, url, config, suffix='flv'):
|
||||
@@ -45,7 +46,7 @@ class Huya(DownloadBase):
|
||||
self.huya_cdn_fallback = config.get('huya_cdn_fallback', False)
|
||||
self.huya_mobile_api = config.get('huya_mobile_api', False)
|
||||
self.huya_codec = config.get('huya_codec', '264')
|
||||
self.huya_use_wup = config.get('huya_use_wup', True)
|
||||
self.huya_use_wup = (not self.huya_mobile_api)
|
||||
|
||||
async def acheck_stream(self, is_check=False):
|
||||
|
||||
@@ -67,20 +68,18 @@ class Huya(DownloadBase):
|
||||
|
||||
# 兼容 biliup/biliup#1200
|
||||
self.room_title = room_profile['room_title']
|
||||
# 虎牙回放
|
||||
if self.room_title.startswith('【回放】'):
|
||||
logger.debug(f"{self.plugin_msg}: {self.room_title}")
|
||||
return False
|
||||
# 过滤回放
|
||||
for key in ["回放", "重播"]:
|
||||
# 前三或后三
|
||||
if key in self.room_title[:3] or key in self.room_title[-3:]:
|
||||
logger.debug(f"{self.plugin_msg}: {self.room_title}")
|
||||
return False
|
||||
|
||||
if is_check:
|
||||
return True
|
||||
|
||||
# self.room_title = room_profile['room_title']
|
||||
|
||||
# is_xingxiu = (room_profile['gid'] == 1663)
|
||||
gid_blacklist = [1663, ]
|
||||
skip_query_build = room_profile['gid'] in gid_blacklist
|
||||
stream_urls = await self.build_stream_urls(room_profile['streams_info'], skip_query_build)
|
||||
stream_urls = await self.build_stream_urls(room_profile['streams_info'])
|
||||
print(stream_urls)
|
||||
cdn_list = list(stream_urls.keys())
|
||||
if not self.huya_cdn or self.huya_cdn not in cdn_list:
|
||||
self.huya_cdn = cdn_list[0]
|
||||
@@ -116,7 +115,7 @@ class Huya(DownloadBase):
|
||||
if not room_profile['live']:
|
||||
logger.debug(f"{self.plugin_msg}: {room_profile['message']}")
|
||||
return False
|
||||
stream_urls = await self.build_stream_urls(room_profile['streams_info'], skip_query_build)
|
||||
stream_urls = await self.build_stream_urls(room_profile['streams_info'])
|
||||
|
||||
self.raw_stream_url = self.add_ratio(
|
||||
stream_urls[self.huya_cdn],
|
||||
@@ -126,10 +125,12 @@ class Huya(DownloadBase):
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def danmaku_init(self):
|
||||
if self.huya_danmaku:
|
||||
self.danmaku = DanmakuClient(self.url, self.gen_download_filename())
|
||||
|
||||
|
||||
def add_ratio(self, url: str, bitrate_info: Dict[str, Any], max_bitrate: int) -> str:
|
||||
'''
|
||||
添加码率
|
||||
@@ -159,25 +160,26 @@ class Huya(DownloadBase):
|
||||
raise KeyError(f"确定码率时发生错误") from e
|
||||
return url
|
||||
|
||||
|
||||
def get_stream_name(self, stream_name: str) -> str:
|
||||
if self.huya_imgplus:
|
||||
return stream_name
|
||||
return stream_name.replace('-imgplus', '')
|
||||
|
||||
|
||||
async def build_stream_urls(
|
||||
self,
|
||||
streams_info: List[Dict[str, Any]],
|
||||
skip_query_build: bool
|
||||
streams_info: List[Dict[str, Any]]
|
||||
) -> List[Dict[str, Any]]:
|
||||
'''
|
||||
构建流地址
|
||||
:param streams_info: 流信息
|
||||
:param skip_query_build: 跳过构建anti_code
|
||||
:return: 流地址
|
||||
'''
|
||||
proto = self.huya_protocol
|
||||
streams = {}
|
||||
weights = {} # https://cdnweb.huya.com/getUidsDomainList?anchor_uid={anchor_uid}
|
||||
cached_anticode = ""
|
||||
for stream in streams_info:
|
||||
# 优先级<0代表不可用
|
||||
priority = stream['iWebPriorityRate']
|
||||
@@ -186,27 +188,23 @@ class Huya(DownloadBase):
|
||||
stream_name = self.get_stream_name(stream['sStreamName'])
|
||||
cdn = stream['sCdnType']
|
||||
suffix = stream[f's{proto}UrlSuffix']
|
||||
# 默认不修改 anticode
|
||||
anti_code = stream[f's{proto}AntiCode']
|
||||
if (
|
||||
# 禁用 imgplus
|
||||
not self.huya_imgplus
|
||||
or
|
||||
# 禁用 wup ,流信息不来自移动端,不在分区黑名单中
|
||||
not (self.huya_use_wup or self.huya_mobile_api or skip_query_build)
|
||||
):
|
||||
logger.debug(f"{self.plugin_msg}: 构建 anticode")
|
||||
anti_code = self.build_query(stream_name, anti_code, self.get_uid(stream['lPresenterUid']))
|
||||
# 启用 imgplus、wup 且非 mobile api
|
||||
elif self.huya_use_wup and not self.huya_mobile_api:
|
||||
# 使用 Wup 获取的 anti_code,必须使用 Wup UA 进行连接
|
||||
anti_code = await self.get_true_anticode(cdn, stream_name, self.get_uid(stream['lPresenterUid']), proto)
|
||||
anti_code = f"{anti_code}&codec={self.huya_codec}"
|
||||
if not cached_anticode:
|
||||
# 小程序API不修改
|
||||
if self.huya_mobile_api and self.huya_imgplus:
|
||||
cached_anticode = stream[f's{proto}AntiCode']
|
||||
else:
|
||||
cached_anticode = self.build_anticode(
|
||||
stream_name,
|
||||
(await self.get_cdn_token_info_ex(stream_name)),
|
||||
stream['lPresenterUid']
|
||||
)
|
||||
cached_anticode += f"&codec={self.huya_codec}"
|
||||
base_url = stream[f's{proto}Url'].replace('http://', 'https://') # 强制https
|
||||
streams[cdn] = f"{base_url}/{stream_name}.{suffix}?{anti_code}"
|
||||
streams[cdn] = f"{base_url}/{stream_name}.{suffix}?{cached_anticode}"
|
||||
weights[cdn] = priority
|
||||
return self.__weight_sorting(streams, weights)
|
||||
|
||||
|
||||
def extract_room_profile(self, data: Union[str, Dict[str, Any]]) -> Dict[str, Any]:
|
||||
'''
|
||||
ON: 直播
|
||||
@@ -250,6 +248,7 @@ class Huya(DownloadBase):
|
||||
'streams_info': streams_info,
|
||||
}
|
||||
|
||||
|
||||
async def get_room_profile(self, use_api=False) -> dict:
|
||||
'''
|
||||
获取房间信息
|
||||
@@ -279,90 +278,130 @@ class Huya(DownloadBase):
|
||||
_raise_for_room_block(resp)
|
||||
return self.extract_room_profile(resp)
|
||||
|
||||
async def get_true_anticode(
|
||||
self,
|
||||
cdn: str,
|
||||
stream_name: str,
|
||||
presenter_uid: int,
|
||||
proto: str,
|
||||
) -> str:
|
||||
|
||||
async def get_cdn_token_info_ex(self, stream_name: str) -> str:
|
||||
'''
|
||||
获取 wup anti_code
|
||||
:param cdn: cdn类型
|
||||
:param stream_name: 流名称
|
||||
getCdnTokenInfoEx
|
||||
:param stream_name: stream_name
|
||||
:param presenter_uid: 主播uid
|
||||
:param proto: 协议类型
|
||||
:return: wup anti_code
|
||||
:return: sFlvToken
|
||||
'''
|
||||
proto = "hls" if proto == "Hls" else "flv"
|
||||
headers = {}
|
||||
self.update_headers(headers)
|
||||
servant = "liveui"
|
||||
func = "getCdnTokenInfoEx"
|
||||
tid = HuyaUserId()
|
||||
# Generate random sHuYaUA using UAGenerator
|
||||
tid.sHuYaUA = UAGenerator.get_random_hyapp_ua()
|
||||
print(f"sHuYaUA: {tid.sHuYaUA}")
|
||||
wup_req = Wup()
|
||||
wup_req.requestid = abs(DEFAULT_TICKET_NUMBER)
|
||||
wup_req.servant = "liveui"
|
||||
wup_req.func = "getCdnTokenInfo"
|
||||
token_info_req = HuyaGetCdnTokenReq()
|
||||
token_info_req.cdnType = cdn
|
||||
token_info_req.streamName = stream_name
|
||||
token_info_req.presenterUid = presenter_uid
|
||||
wup_req.put(HuyaGetCdnTokenReq, "tReq", token_info_req)
|
||||
wup_req.servant = servant
|
||||
wup_req.func = func
|
||||
getCdnTokenInfoExReq = HuyaGetCdnTokenExReq()
|
||||
getCdnTokenInfoExReq.sStreamName = stream_name
|
||||
# getCdnTokenInfoExReq.iLoopTime = 15 * 60 # 防盗链过期时间
|
||||
getCdnTokenInfoExReq.tId = tid
|
||||
wup_req.put(
|
||||
vtype=HuyaGetCdnTokenExReq,
|
||||
name="tReq",
|
||||
value=getCdnTokenInfoExReq
|
||||
)
|
||||
data = wup_req.encode_v3()
|
||||
rsp = await client.post(HUYA_WUP_BASE_URL, data=data, headers=headers)
|
||||
url = HUYA_WUP_BASE_URL
|
||||
if random.random() > 0.5:
|
||||
url = f"{HUYA_WUP_YST_URL}/{servant}/{func}"
|
||||
print(f"send requests to {url}")
|
||||
rsp = await client.post(url, data=data)
|
||||
rsp_bytes = rsp.content
|
||||
wup_rsp = Wup()
|
||||
wup_rsp.decode_v3(rsp.content)
|
||||
token_info_rsp = wup_rsp.get(HuyaGetCdnTokenRsp,"tRsp")
|
||||
# print(token_info_rsp.as_dict())
|
||||
token_info = token_info_rsp.as_dict()
|
||||
logger.debug(f"{self.plugin_msg}: wup token_info {token_info}")
|
||||
return token_info[f'{proto}AntiCode']
|
||||
wup_rsp.decode_v3(rsp_bytes)
|
||||
getCdnTokenInfoExRsp = wup_rsp.get(
|
||||
vtype=HuyaGetCdnTokenExRsp,
|
||||
name="tRsp"
|
||||
)
|
||||
cdn_token_info_ex = getCdnTokenInfoExRsp.as_dict()
|
||||
print(f"{self.plugin_msg}: wup token_info {cdn_token_info_ex}")
|
||||
return cdn_token_info_ex['sFlvToken']
|
||||
|
||||
def build_query(self, stream_name, anti_code, uid: int) -> str:
|
||||
|
||||
def build_anticode(
|
||||
self,
|
||||
stream_name: str,
|
||||
anti_code: str,
|
||||
uid: Union[str, int] = 0,
|
||||
random_platform: bool = False
|
||||
) -> str:
|
||||
'''
|
||||
构建anti_code
|
||||
:param stream_name: 流名称
|
||||
:param anti_code: 原始anti_code
|
||||
:param uid: 主播uid
|
||||
:return: 构建后的anti_code
|
||||
构建anticode
|
||||
:param stream_name: streamname
|
||||
:param anti_code: anticode
|
||||
:param uid: 用户uid
|
||||
:return: Parsed anticode
|
||||
'''
|
||||
url_query = parse_qs(anti_code)
|
||||
platform_id = url_query.get('t', [100])[0]
|
||||
ws_time = url_query['wsTime'][0]
|
||||
convert_uid = (uid << 8 | uid >> (32 - 8)) & 0xFFFFFFFF
|
||||
seq_id = uid + int(time.time() * 1000)
|
||||
ctype = url_query['ctype'][0]
|
||||
if not url_query.get("fm"):
|
||||
return anti_code
|
||||
|
||||
ctype = url_query.get('ctype', [])
|
||||
platform_id = url_query.get('t', [])
|
||||
if len(ctype) == 0 or random_platform:
|
||||
ctype, platform_id = PLATFORM.get_random_as_tuple()
|
||||
elif len(platform_id) == 0:
|
||||
ctype = ctype[0]
|
||||
platform_id = PLATFORM.get_platform_id(ctype)
|
||||
else:
|
||||
ctype = ctype[0]
|
||||
platform_id = platform_id[0]
|
||||
|
||||
is_wap = int(platform_id) in {103}
|
||||
clac_start_time = time.time()
|
||||
|
||||
if isinstance(uid, str):
|
||||
uid = int(uid) if uid.isdigit() else 0
|
||||
if uid == 0:
|
||||
uid = self.generate_random_uid()
|
||||
print(f"Using {uid} as uid for calculation")
|
||||
seq_id = uid + int(clac_start_time * 1000)
|
||||
secret_hash = hashlib.md5(f"{seq_id}|{ctype}|{platform_id}".encode()).hexdigest()
|
||||
convert_uid = rotl64(uid)
|
||||
clac_uid = uid if is_wap else convert_uid
|
||||
|
||||
fm = unquote(url_query['fm'][0])
|
||||
ct = int((int(ws_time, 16) + random.random()) * 1000)
|
||||
ws_secret_prefix = base64.b64decode(fm.encode()).decode().split('_')[0]
|
||||
ws_secret_hash = hashlib.md5(f"{seq_id}|{ctype}|{platform_id}".encode()).hexdigest()
|
||||
secret_str = f'{ws_secret_prefix}_{convert_uid}_{stream_name}_{ws_secret_hash}_{ws_time}'
|
||||
secret_prefix = base64.b64decode(fm.encode()).decode().split('_')[0]
|
||||
|
||||
ws_time = url_query['wsTime'][0]
|
||||
# 修复 hls m3u8 链接过期时间
|
||||
if int(ws_time, 16) - int(clac_start_time) < (20 * 60):
|
||||
# 如果过期时间小于 20 分钟,调整过期时间为 1 天
|
||||
ws_time = hex(24 * 60 * 60 + int(clac_start_time))[2:]
|
||||
secret_str = f'{secret_prefix}_{clac_uid}_{stream_name}_{secret_hash}_{ws_time}'
|
||||
ws_secret = hashlib.md5(secret_str.encode()).hexdigest()
|
||||
|
||||
# &codec=av1
|
||||
# &codec=264
|
||||
# &codec=265
|
||||
# dMod: wcs-25 / mesh-0 DecodeMod-SupportMod
|
||||
# chrome > 104 or safari = mseh, chrome = mses
|
||||
# sdkPcdn: 1_1 第一个1连接次数 第二个1是因为什么连接
|
||||
# t: 平台信息 100 web(ctype=huya_live/huya_webh5) 102 小程序(ctype=tars_mp)
|
||||
# PLATFORM_TYPE = {'adr': 2, 'huya_liveshareh5': 104, 'ios': 3, 'mini_app': 102, 'wap': 103, 'web': 100}
|
||||
# sv: 2401090219 版本
|
||||
# sdk_sid: _sessionId sdkInRoomTs 当前毫秒时间
|
||||
# return f"wsSecret={ws_secret}&wsTime={ws_time}&seqid={seq_id}&ctype={url_query['ctype'][0]}&ver=1&fs={url_query['fs'][0]}&u={convert_uid}&t={platform_id}&sv=2401090219&sdk_sid={int(time.time() * 1000)}&codec=264"
|
||||
ct = int((int(ws_time, 16) + random.random()) * 1000)
|
||||
uuid = str(int((ct % 1e10 + random.random()) * 1e3 % 0xffffffff))
|
||||
|
||||
anti_code = {
|
||||
"wsSecret": ws_secret,
|
||||
"wsTime": ws_time,
|
||||
"seqid": str(seq_id),
|
||||
"seqid": seq_id,
|
||||
"ctype": ctype,
|
||||
"ver": "1",
|
||||
"fs": url_query['fs'][0],
|
||||
"fm": quote(url_query['fm'][0], encoding='utf-8'),
|
||||
"t": platform_id,
|
||||
"u": convert_uid,
|
||||
"uuid": str(int((ct % 1e10 + random.random()) * 1e3 % 0xffffffff)),
|
||||
"sdk_sid": str(int(time.time() * 1000)),
|
||||
# "codec": self.huya_codec,
|
||||
}
|
||||
if is_wap:
|
||||
anti_code.update({
|
||||
"uid": uid,
|
||||
"uuid": uuid,
|
||||
})
|
||||
else:
|
||||
anti_code.update({
|
||||
"u": convert_uid,
|
||||
})
|
||||
|
||||
return '&'.join([f"{k}={v}" for k, v in anti_code.items()])
|
||||
|
||||
|
||||
@staticmethod
|
||||
def __weight_sorting(
|
||||
data: Dict[str, Any], weights: Dict[str, Any]
|
||||
@@ -372,197 +411,128 @@ class Huya(DownloadBase):
|
||||
return dict(sorted(data.items(), key=lambda x: weights[x[0]], reverse=True))
|
||||
return {}
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_uid(uid: Union[str, int, None] = None) -> int:
|
||||
try:
|
||||
if isinstance(uid, str):
|
||||
uid = int(uid)
|
||||
except ValueError:
|
||||
pass
|
||||
return uid or random.randint(1400000000000, 1499999999999)
|
||||
def generate_random_uid() -> int:
|
||||
return int(f"1234{random.randint(0, 9999):04d}") \
|
||||
if random.random() > 0.5 else \
|
||||
int(f"140000{random.randint(0, 9999999):07d}")
|
||||
|
||||
async def get_anonymous_uid(self) -> int:
|
||||
try:
|
||||
rsp = await client.post(
|
||||
url='https://udblgn.huya.com/web/anonymousLogin',
|
||||
headers=self.fake_headers,
|
||||
json={
|
||||
"appId": 5002,
|
||||
"byPass": 3,
|
||||
"context": "",
|
||||
"version": "2.4",
|
||||
"data": {},
|
||||
}
|
||||
)
|
||||
rsp = json_loads(rsp.text)
|
||||
except:
|
||||
rsp = {}
|
||||
return rsp.get('data', {}).get('uid', self.get_uid())
|
||||
|
||||
def update_headers(self, headers: dict):
|
||||
if self.huya_use_wup:
|
||||
user_agent = UAGenerator.build_user_agent(UAType.HYSDK, Platform.WINDOWS)
|
||||
# user_agent = f"{Huya.get_hysdk_ua()}_APP({Huya.get_hyapp_ua()})_SDK({Huya.get_hy_trans_mod_ua()})"
|
||||
headers['user-agent'] = user_agent
|
||||
headers['origin'] = HUYA_WEB_BASE_URL
|
||||
# async def get_anonymous_uid(self) -> int:
|
||||
# try:
|
||||
# rsp = await client.post(
|
||||
# url='https://udblgn.huya.com/web/anonymousLogin',
|
||||
# headers=self.fake_headers,
|
||||
# json={
|
||||
# "appId": 5002,
|
||||
# "byPass": 3,
|
||||
# "context": "",
|
||||
# "version": "2.4",
|
||||
# "data": {},
|
||||
# }
|
||||
# )
|
||||
# rsp = json_loads(rsp.text)
|
||||
# except:
|
||||
# rsp = {}
|
||||
# return rsp.get('data', {}).get('uid', self.get_uid())
|
||||
|
||||
class UAType(Enum):
|
||||
MEDIA_PLAYER = 'media_player'
|
||||
HYSDK = 'hysdk'
|
||||
# def update_headers(self, headers: dict):
|
||||
# if self.huya_use_wup:
|
||||
# user_agent = UAGenerator.build_user_agent(UAType.HYSDK, Platform.WINDOWS)
|
||||
# # user_agent = f"{Huya.get_hysdk_ua()}_APP({Huya.get_hyapp_ua()})_SDK({Huya.get_hy_trans_mod_ua()})"
|
||||
# headers['user-agent'] = user_agent
|
||||
# headers['origin'] = HUYA_WEB_BASE_URL
|
||||
|
||||
class PLATFORM(Enum):
|
||||
HUYA_PC_EXE = 0
|
||||
HUYA_ADR = 2
|
||||
HUYA_IOS = 3
|
||||
TV_HUYA_NFTV = 10
|
||||
HUYA_WEBH5 = 100
|
||||
HUYA_LIVE = 100
|
||||
TARS_MP = 102
|
||||
TARS_MOBILE = 103
|
||||
HUYA_LIVESHAREH5 = 104
|
||||
|
||||
@classmethod
|
||||
def get_random_as_tuple(cls):
|
||||
_ = random.choice(list(cls))
|
||||
return _.name.lower(), _.value
|
||||
|
||||
@classmethod
|
||||
def get_platform_id(cls, platform: str) -> int:
|
||||
return cls[platform.upper()].value if platform.upper() in cls.__members__ else 100
|
||||
|
||||
@property
|
||||
def short_name(self) -> str:
|
||||
"""获取平台短名称"""
|
||||
name = self.name.lower()
|
||||
idx = name.find('_')
|
||||
return name[idx + 1:] if idx != -1 else name
|
||||
|
||||
class Platform(Enum):
|
||||
ANDROID = 'adr'
|
||||
HUYA_NFTV = 'huya_nftv'
|
||||
WEBSOCKET = 'webh5'
|
||||
WINDOWS = 'pc_exe'
|
||||
|
||||
class UAGenerator:
|
||||
# 配置字典
|
||||
# Configuration dictionary mapping PLATFORM enum to UA components
|
||||
HYAPP_CONFIGS = {
|
||||
Platform.ANDROID: {
|
||||
'platform': Platform.ANDROID,
|
||||
'version': '0.0.0', # LocalVersion or "0.0.0" + hotfix_version
|
||||
'channel': 'live'
|
||||
PLATFORM.HUYA_ADR: {
|
||||
'version': '13.1.0', # LocalVersion or "0.0.0" + hotfix_version
|
||||
},
|
||||
Platform.HUYA_NFTV: {
|
||||
'platform': Platform.HUYA_NFTV,
|
||||
'version': '2.5.1.3141',
|
||||
'channel': 'official'
|
||||
PLATFORM.HUYA_IOS: {
|
||||
'version': '13.1.0',
|
||||
},
|
||||
Platform.WINDOWS: {
|
||||
'platform': Platform.WINDOWS,
|
||||
'version': '6100301',
|
||||
'channel': 'official'
|
||||
PLATFORM.TV_HUYA_NFTV: {
|
||||
'version': '2.6.10',
|
||||
},
|
||||
Platform.WEBSOCKET: { # UnUsed
|
||||
'platform': Platform.WEBSOCKET,
|
||||
'version': '2505091506',
|
||||
'channel': 'websocket'
|
||||
}
|
||||
PLATFORM.HUYA_PC_EXE: {
|
||||
'version': '7000000',
|
||||
},
|
||||
# PLATFORM.HUYA_WEBH5: { # 星秀区不可用
|
||||
# 'version': '%y%m%d%H%M', # 2410101630
|
||||
# 'channel': 'websocket'
|
||||
# }
|
||||
}
|
||||
|
||||
HYSDK_CONFIGS = {
|
||||
Platform.ANDROID: {
|
||||
'platform': 'Android',
|
||||
'version': '30000002'
|
||||
},
|
||||
Platform.WINDOWS: {
|
||||
'platform': 'Windows',
|
||||
'version': '30000002'
|
||||
}
|
||||
}
|
||||
|
||||
TRANS_MOD_CONFIGS = {
|
||||
Platform.HUYA_NFTV: {
|
||||
'name': 'trans',
|
||||
'version': '1.24.99-rel-tv'
|
||||
},
|
||||
Platform.ANDROID: {
|
||||
'name': 'trans',
|
||||
'version': '2.22.13-rel'
|
||||
},
|
||||
Platform.WINDOWS: {
|
||||
'name': 'trans',
|
||||
'version': '2.24.0.5157'
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def get_hyapp_ua(platform: Platform = Platform.WINDOWS) -> str:
|
||||
def generate_hyapp_ua(platform: PLATFORM) -> str:
|
||||
'''
|
||||
生成 hyapp 用户代理字符串
|
||||
:param platform: 平台类型
|
||||
:return: 用户代理字符串
|
||||
Generate hyapp user agent string
|
||||
:param platform: Platform type from PLATFORM enum
|
||||
:return: User agent string
|
||||
'''
|
||||
config = UAGenerator.HYAPP_CONFIGS.get(platform)
|
||||
if not config:
|
||||
raise ValueError(f"不支持的平台: {platform}")
|
||||
# Fallback if platform not supported
|
||||
platform = random.choice(list(UAGenerator.HYAPP_CONFIGS.keys()))
|
||||
config = UAGenerator.HYAPP_CONFIGS[platform]
|
||||
|
||||
hyapp_platform = config['platform']
|
||||
hyapp_version = config['version']
|
||||
hyapp_channel = config['channel']
|
||||
hyapp_platform = platform.short_name
|
||||
hyapp_version = config.get("version", "0.0.0") # TODO: 日期格式化
|
||||
hyapp_channel = config.get("channel", "official")
|
||||
|
||||
# Add random build number for version
|
||||
if platform in {PLATFORM.HUYA_ADR, PLATFORM.TV_HUYA_NFTV}:
|
||||
hyapp_version += f".{random.randint(3000, 5000)}"
|
||||
|
||||
ua = f"{hyapp_platform}&{hyapp_version}&{hyapp_channel}"
|
||||
# windows 和 websocket 不需要添加 android_api_level
|
||||
if platform not in {Platform.WINDOWS, Platform.WEBSOCKET}:
|
||||
android_api_level = random.randint(28, 35)
|
||||
|
||||
# Add android_api_level for android platforms
|
||||
if platform in {PLATFORM.HUYA_ADR, PLATFORM.TV_HUYA_NFTV}:
|
||||
android_api_level = random.randint(28, 36)
|
||||
ua = f"{ua}&{android_api_level}"
|
||||
|
||||
return ua
|
||||
|
||||
@staticmethod
|
||||
def get_hysdk_ua(platform: Platform = Platform.WINDOWS) -> str:
|
||||
'''
|
||||
生成 hysdk 用户代理字符串
|
||||
:param platform: 平台类型 (Android 或 Windows)
|
||||
:return: 用户代理字符串
|
||||
'''
|
||||
config = UAGenerator.HYSDK_CONFIGS.get(platform)
|
||||
if not config:
|
||||
raise ValueError(f"HYSDK 不支持的平台: {platform}")
|
||||
|
||||
hysdk_platform = config['platform']
|
||||
hysdk_version = config['version']
|
||||
|
||||
return f"HYSDK({hysdk_platform}, {hysdk_version})"
|
||||
|
||||
@staticmethod
|
||||
def get_hy_media_player_ua(platform: Platform = Platform.WINDOWS) -> str:
|
||||
def get_random_hyapp_ua() -> str:
|
||||
'''
|
||||
生成 hy_media_player 用户代理字符串
|
||||
:param platform: 平台类型
|
||||
:return: 用户代理字符串
|
||||
Generate random hyapp user agent string by randomly selecting a platform
|
||||
:return: User agent string
|
||||
'''
|
||||
# 目前只支持 android 平台
|
||||
hy_mp_platform = 'android'
|
||||
hy_mp_version = '20000313'
|
||||
|
||||
return f"{hy_mp_platform}, {hy_mp_version}"
|
||||
|
||||
@staticmethod
|
||||
def get_hy_trans_mod_ua(platform: Platform = Platform.WINDOWS) -> str:
|
||||
'''
|
||||
生成 hy_trans_mod 用户代理字符串
|
||||
:param platform: 平台类型
|
||||
:return: 用户代理字符串
|
||||
'''
|
||||
config = UAGenerator.TRANS_MOD_CONFIGS.get(platform)
|
||||
if not config:
|
||||
raise ValueError(f"Trans mod 不支持的平台: {platform}")
|
||||
|
||||
hy_trans_mod_name = config['name']
|
||||
hy_trans_mod_version = config['version']
|
||||
|
||||
return f"{hy_trans_mod_name}&{hy_trans_mod_version}"
|
||||
|
||||
@staticmethod
|
||||
def build_user_agent(
|
||||
ua_type: UAType = UAType.HYSDK,
|
||||
platform: Platform = Platform.WINDOWS
|
||||
) -> str:
|
||||
'''
|
||||
构建完整的用户代理字符串
|
||||
:param ua_type: UA 类型 (MEDIA_PLAYER 或 HYSDK)
|
||||
:param platform: 平台类型
|
||||
:return: 完整的用户代理字符串
|
||||
'''
|
||||
|
||||
# 获取各个组件的 UA
|
||||
hyapp_ua = UAGenerator.get_hyapp_ua(platform)
|
||||
|
||||
trans_mod_ua = UAGenerator.get_hy_trans_mod_ua(platform)
|
||||
|
||||
if ua_type == UAType.MEDIA_PLAYER:
|
||||
media_player_ua = UAGenerator.get_hy_media_player_ua(platform)
|
||||
return f"{media_player_ua}_APP({hyapp_ua})_SDK({trans_mod_ua})"
|
||||
|
||||
elif ua_type == UAType.HYSDK:
|
||||
sdk_platform = platform if platform in {Platform.ANDROID, Platform.HUYA_NFTV} else Platform.WINDOWS
|
||||
hysdk_ua = UAGenerator.get_hysdk_ua(sdk_platform)
|
||||
return f"{hysdk_ua}_APP({hyapp_ua})_SDK({trans_mod_ua})"
|
||||
|
||||
else:
|
||||
raise ValueError(f"不支持的 UA 类型: {ua_type}")
|
||||
random_platform = random.choice(list(PLATFORM))
|
||||
return UAGenerator.generate_hyapp_ua(random_platform)
|
||||
|
||||
|
||||
def _raise_for_room_block(text: str):
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
|
||||
from .getCdnTokenInfo import (
|
||||
HuyaGetCdnTokenReq,
|
||||
HuyaGetCdnTokenRsp
|
||||
from .getCdnTokenEx import (
|
||||
HuyaGetCdnTokenExReq,
|
||||
HuyaGetCdnTokenExRsp
|
||||
)
|
||||
|
||||
__all__ = ['HuyaGetCdnTokenReq', 'HuyaGetCdnTokenRsp']
|
||||
|
||||
__all__ = [
|
||||
'HuyaGetCdnTokenExReq',
|
||||
'HuyaGetCdnTokenExRsp'
|
||||
]
|
||||
@@ -6,19 +6,49 @@ STANDARD_CHARSET = 'utf-8'
|
||||
|
||||
|
||||
def auto_decode_fields(cls):
|
||||
"""自动解码类中的bytes类型数据"""
|
||||
"""自动解码类中的bytes类型数据,包括vector和map中的字符串"""
|
||||
original_read_from = cls.readFrom
|
||||
|
||||
def _decode_recursive(obj):
|
||||
"""递归解码对象中的bytes字段"""
|
||||
if isinstance(obj, bytes):
|
||||
try:
|
||||
return obj.decode(STANDARD_CHARSET)
|
||||
except UnicodeDecodeError:
|
||||
# 如果解码失败,返回原始bytes
|
||||
return obj
|
||||
elif isinstance(obj, list):
|
||||
# 处理vector类型(继承自list)- 必须在 hasattr(__dict__) 之前检查
|
||||
for i in range(len(obj)):
|
||||
obj[i] = _decode_recursive(obj[i])
|
||||
elif isinstance(obj, dict):
|
||||
# 处理map类型(继承自dict)- 必须在 hasattr(__dict__) 之前检查
|
||||
keys_to_update = []
|
||||
for key in obj.keys():
|
||||
decoded_key = _decode_recursive(key)
|
||||
decoded_value = _decode_recursive(obj[key])
|
||||
keys_to_update.append((key, decoded_key, decoded_value))
|
||||
|
||||
# 更新字典
|
||||
for old_key, new_key, new_value in keys_to_update:
|
||||
if old_key != new_key:
|
||||
del obj[old_key]
|
||||
obj[new_key] = new_value
|
||||
else:
|
||||
obj[old_key] = new_value
|
||||
elif hasattr(obj, '__dict__'):
|
||||
# 处理结构体对象
|
||||
for attr_name, attr_value in vars(obj).items():
|
||||
setattr(obj, attr_name, _decode_recursive(attr_value))
|
||||
return obj
|
||||
|
||||
@staticmethod
|
||||
@wraps(original_read_from)
|
||||
def wrapped_read_from(ios: tarscore.TarsInputStream):
|
||||
value = original_read_from(ios)
|
||||
# 遍历对象的所有属性
|
||||
# 遍历对象的所有属性进行解码
|
||||
for attr_name, attr_value in vars(value).items():
|
||||
# 如果是bytes类型
|
||||
if isinstance(attr_value, bytes):
|
||||
# 将其解码为字符串
|
||||
setattr(value, attr_name, attr_value.decode(STANDARD_CHARSET))
|
||||
setattr(value, attr_name, _decode_recursive(attr_value))
|
||||
return value
|
||||
|
||||
cls.readFrom = wrapped_read_from
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
from biliup.common.tars import tarscore
|
||||
from .__util import auto_decode_fields
|
||||
from ..wup_struct.UserId import HuyaUserId
|
||||
|
||||
@auto_decode_fields
|
||||
class HuyaGetCdnTokenExReq(tarscore.struct):
|
||||
|
||||
__tars_class__ = "HUYA.GetCdnTokenExReq"
|
||||
|
||||
def __init__(self):
|
||||
self.sFlvUrl: tarscore.string = ""
|
||||
self.sStreamName: tarscore.string = ""
|
||||
self.iLoopTime: tarscore.int32 = 0
|
||||
self.tId: HuyaUserId = HuyaUserId()
|
||||
self.iAppId: tarscore.int32 = 66
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.string, 0, value.sFlvUrl)
|
||||
oos.write(tarscore.string, 1, value.sStreamName)
|
||||
oos.write(tarscore.int32, 2, value.iLoopTime)
|
||||
oos.write(HuyaUserId, 3, value.tId)
|
||||
oos.write(tarscore.int32, 4, value.iAppId)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaGetCdnTokenExReq()
|
||||
value.sFlvUrl = ios.read(tarscore.string, 0, False)
|
||||
value.sStreamName = ios.read(tarscore.string, 1, False)
|
||||
value.iLoopTime = ios.read(tarscore.int32, 2, False)
|
||||
value.tId = ios.read(HuyaUserId, 3, False)
|
||||
value.iAppId = ios.read(tarscore.int32, 4, False)
|
||||
return value
|
||||
|
||||
def as_dict(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
@auto_decode_fields
|
||||
class HuyaGetCdnTokenExRsp(tarscore.struct):
|
||||
|
||||
__tars_class__ = "HUYA.GetCdnTokenExRsp"
|
||||
|
||||
def __init__(self):
|
||||
self.sFlvToken: tarscore.string = ""
|
||||
self.iExpireTime: tarscore.int64 = 0
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.string, 0, value.sFlvToken)
|
||||
oos.write(tarscore.int64, 1, value.iExpireTime)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaGetCdnTokenExRsp()
|
||||
value.sFlvToken = ios.read(tarscore.string, 0, False)
|
||||
value.iExpireTime = ios.read(tarscore.int64, 1, False)
|
||||
return value
|
||||
|
||||
def as_dict(self):
|
||||
return self.__dict__.copy()
|
||||
@@ -1,74 +0,0 @@
|
||||
from biliup.common.tars import tarscore
|
||||
from .__util import auto_decode_fields
|
||||
|
||||
@auto_decode_fields
|
||||
class HuyaGetCdnTokenReq(tarscore.struct):
|
||||
|
||||
__tars_class__ = "HUYA.GetCdnTokenReq"
|
||||
|
||||
def __init__(self):
|
||||
self.url: tarscore.string = ""
|
||||
self.cdnType: tarscore.string = ""
|
||||
self.streamName: tarscore.string = ""
|
||||
self.presenterUid: tarscore.int32 = 0
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.string, 0, value.url)
|
||||
oos.write(tarscore.string, 1, value.cdnType)
|
||||
oos.write(tarscore.string, 2, value.streamName)
|
||||
oos.write(tarscore.int64, 3, value.presenterUid)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaGetCdnTokenReq()
|
||||
value.url = ios.read(tarscore.string, 0, False)
|
||||
value.cdnType = ios.read(tarscore.string, 1, False)
|
||||
value.streamName = ios.read(tarscore.string, 2, False)
|
||||
value.presenterUid = ios.read(tarscore.int64, 3, False)
|
||||
return value
|
||||
|
||||
def as_dict(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
@auto_decode_fields
|
||||
class HuyaGetCdnTokenRsp(tarscore.struct):
|
||||
|
||||
__tars_class__ = "HUYA.GetCdnTokenRsp"
|
||||
|
||||
def __init__(self):
|
||||
self.url: tarscore.string = ""
|
||||
self.cdnType: tarscore.string = ""
|
||||
self.streamName: tarscore.string = ""
|
||||
self.presenterUid: tarscore.int64 = 0
|
||||
self.antiCode: tarscore.string = ""
|
||||
self.sTime: tarscore.string = ""
|
||||
self.flvAntiCode: tarscore.string = ""
|
||||
self.hlsAntiCode: tarscore.string = ""
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.string, 0, value.url)
|
||||
oos.write(tarscore.string, 1, value.cdnType)
|
||||
oos.write(tarscore.string, 2, value.streamName)
|
||||
oos.write(tarscore.int64, 3, value.presenterUid)
|
||||
oos.write(tarscore.string, 4, value.antiCode)
|
||||
oos.write(tarscore.string, 5, value.sTime)
|
||||
oos.write(tarscore.string, 6, value.flvAntiCode)
|
||||
oos.write(tarscore.string, 7, value.hlsAntiCode)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaGetCdnTokenRsp()
|
||||
value.url = ios.read(tarscore.string, 0, False)
|
||||
value.cdnType = ios.read(tarscore.string, 1, False)
|
||||
value.streamName = ios.read(tarscore.string, 2, False)
|
||||
value.presenterUid = ios.read(tarscore.int64, 3, False)
|
||||
value.antiCode = ios.read(tarscore.string, 4, False)
|
||||
value.sTime = ios.read(tarscore.string, 5, False)
|
||||
value.flvAntiCode = ios.read(tarscore.string, 6, False)
|
||||
value.hlsAntiCode = ios.read(tarscore.string, 7, False)
|
||||
return value
|
||||
|
||||
def as_dict(self):
|
||||
return self.__dict__.copy()
|
||||
@@ -1,60 +0,0 @@
|
||||
from biliup.common.tars import tarscore
|
||||
from .__util import auto_decode_fields
|
||||
from ..wup_struct.UserId import HuyaUserId
|
||||
from ..wup_struct.BeginLiveNotice import HuyaBeginLiveNotice
|
||||
from ..wup_struct.StreamSettingNotice import HuyaStreamSettingNotice
|
||||
|
||||
|
||||
class HuyaGetLivingInfoReq(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.GetLivingInfoReq"
|
||||
|
||||
def __init__(self):
|
||||
self.tId: HuyaUserId = HuyaUserId()
|
||||
self.lTopSid: tarscore.int64 = 0
|
||||
self.lSubSid: tarscore.int64 = 0
|
||||
self.lPresenterUid: tarscore.int64 = 0
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(HuyaUserId, 0, value.tId)
|
||||
oos.write(tarscore.int64, 1, value.lTopSid)
|
||||
oos.write(tarscore.int64, 2, value.lSubSid)
|
||||
oos.write(tarscore.int64, 3, value.lPresenterUid)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaGetLivingInfoReq()
|
||||
value.tId = ios.read(HuyaUserId, 0, False)
|
||||
value.lTopSid = ios.read(tarscore.int64, 1, False)
|
||||
value.lSubSid = ios.read(tarscore.int64, 2, False)
|
||||
value.lPresenterUid = ios.read(tarscore.int64, 3, False)
|
||||
return value
|
||||
|
||||
|
||||
|
||||
class HuyaGetLivingInfoRsp(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.GetLivingInfoRsp"
|
||||
|
||||
def __init__(self):
|
||||
self.bIsLiving: tarscore.int32 = 0
|
||||
self.tNotice: HuyaBeginLiveNotice = HuyaBeginLiveNotice()
|
||||
self.tStreamSettingNotice: HuyaStreamSettingNotice = HuyaStreamSettingNotice()
|
||||
self.bIsSelfLiving: tarscore.int32 = 0
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.int32, 0, value.bIsLiving)
|
||||
oos.write(HuyaBeginLiveNotice, 1, value.tNotice)
|
||||
oos.write(HuyaStreamSettingNotice, 2, value.tStreamSettingNotice)
|
||||
oos.write(tarscore.int32, 3, value.bIsSelfLiving)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaGetLivingInfoRsp()
|
||||
value.bIsLiving = ios.read(tarscore.int32, 0, False)
|
||||
value.tNotice = ios.read(HuyaBeginLiveNotice, 1, False)
|
||||
value.tStreamSettingNotice = ios.read(HuyaStreamSettingNotice, 2, False)
|
||||
value.bIsSelfLiving = ios.read(tarscore.int32, 3, False)
|
||||
return value
|
||||
@@ -1,47 +0,0 @@
|
||||
from biliup.plugins.huya_wup import Wup, DEFAULT_TICKET_NUMBER
|
||||
from biliup.plugins.huya_wup.packet import (
|
||||
HuyaGetCdnTokenReq,
|
||||
HuyaGetCdnTokenRsp
|
||||
)
|
||||
from biliup.plugins.huya_wup.packet.__util import STANDARD_CHARSET
|
||||
|
||||
import httpx
|
||||
client = httpx.Client()
|
||||
|
||||
if __name__ == "__main__":
|
||||
# import base64
|
||||
cdn: str = "TX"
|
||||
stream_name: str = "1199627305549-1199627305549-5718448156589424640-2399254734554-10057-A-0-1-imgplus.flv"
|
||||
presenter_uid: int = 1199627305549
|
||||
wup_url: str = "https://wup.huya.com"
|
||||
huya_headers: dict = {
|
||||
"user-agent": f"HYSDK(Windows, 30000002)_APP(pc_exe&6090007&official)_SDK(trans&2.24.0.5157)",
|
||||
"referer": "https://www.huya.com/",
|
||||
"origin": "https://www.huya.com",
|
||||
}
|
||||
# TupVersion3 = 3
|
||||
wup_req = Wup()
|
||||
# wup_req.version = TupVersion3
|
||||
wup_req.requestid = abs(DEFAULT_TICKET_NUMBER)
|
||||
wup_req.servant = "liveui"
|
||||
wup_req.func = "getCdnTokenInfo"
|
||||
token_info_req = HuyaGetCdnTokenReq()
|
||||
token_info_req.cdnType = cdn
|
||||
token_info_req.streamName = stream_name
|
||||
token_info_req.presenterUid = presenter_uid
|
||||
wup_req.put(
|
||||
vtype=HuyaGetCdnTokenRsp,
|
||||
name="tReq",
|
||||
value=token_info_req
|
||||
)
|
||||
data = wup_req.encode_v3()
|
||||
rsp = client.post(wup_url, data=data, headers=huya_headers)
|
||||
rsp_bytes = rsp.content
|
||||
wup_rsp = Wup()
|
||||
wup_rsp.decode_v3(rsp_bytes)
|
||||
token_info_rsp = wup_rsp.get(
|
||||
vtype=HuyaGetCdnTokenRsp,
|
||||
# name=bytes("tRsp", encoding=STANDARD_CHARSET)
|
||||
name="tRsp"
|
||||
)
|
||||
print(token_info_rsp.as_dict())
|
||||
@@ -57,6 +57,7 @@ class Wup(TarsUniPacket):
|
||||
name = name.encode(STANDARD_CHARSET)
|
||||
|
||||
if (name in self.__new_buffer) == False:
|
||||
print(self.__new_buffer)
|
||||
raise Exception("UniAttribute not found key:%s" % name)
|
||||
|
||||
t = self.__new_buffer[name]
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
from biliup.common.tars import tarscore
|
||||
from .StreamInfo import HuyaStreamInfo
|
||||
from .MultiStreamInfo import HuyaMultiStreamInfo
|
||||
|
||||
|
||||
class HuyaBeginLiveNotice(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.BeginLiveNotice"
|
||||
|
||||
def __init__(self):
|
||||
self.lPresenterUid: tarscore.int64 = 0
|
||||
self.iGameId: tarscore.int32 = 0
|
||||
self.sGameName: tarscore.string = ""
|
||||
self.iRandomRange: tarscore.int32 = 0
|
||||
self.iStreamType: tarscore.int32 = 0
|
||||
self.vStreamInfo: tarscore.vector = tarscore.vector(HuyaStreamInfo) # FIXME
|
||||
self.vCdnList: tarscore.vector = tarscore.vector(tarscore.string) # FIXME
|
||||
self.lLiveId: tarscore.int64 = 0
|
||||
self.iPCDefaultBitRate: tarscore.int32 = 0
|
||||
self.iWebDefaultBitRate: tarscore.int32 = 0
|
||||
self.iMobileDefaultBitRate: tarscore.int32 = 0
|
||||
self.lMultiStreamFlag: tarscore.int64 = 0
|
||||
self.sNick: tarscore.string = ""
|
||||
self.lYYId: tarscore.int64 = 0
|
||||
self.lAttendeeCount: tarscore.int64 = 0
|
||||
self.iCodecType: tarscore.int32 = 0
|
||||
self.iScreenType: tarscore.int32 = 0
|
||||
self.vMultiStreamInfo: tarscore.vector = tarscore.vector(HuyaMultiStreamInfo) # FIXME
|
||||
self.sLiveDesc: tarscore.string = ""
|
||||
self.lLiveCompatibleFlag: tarscore.int64 = 0
|
||||
self.sAvatarUrl: tarscore.string = ""
|
||||
self.iSourceType: tarscore.int32 = 0
|
||||
self.sSubchannelName: tarscore.string = ""
|
||||
self.sVideoCaptureUrl: tarscore.string = ""
|
||||
self.iStartTime: tarscore.int32 = 0
|
||||
self.lChannelId: tarscore.int64 = 0
|
||||
self.lSubChannelId: tarscore.int64 = 0
|
||||
self.sLocation: tarscore.string = ""
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.int64, 0, value.lPresenterUid)
|
||||
oos.write(tarscore.int32, 1, value.iGameId)
|
||||
oos.write(tarscore.string, 2, value.sGameName)
|
||||
oos.write(tarscore.int32, 3, value.iRandomRange)
|
||||
oos.write(tarscore.int32, 4, value.iStreamType)
|
||||
oos.write(tarscore.vector, 5, value.vStreamInfo)
|
||||
oos.write(tarscore.vector, 6, value.vCdnList)
|
||||
oos.write(tarscore.int64, 7, value.lLiveId)
|
||||
oos.write(tarscore.int32, 8, value.iPCDefaultBitRate)
|
||||
oos.write(tarscore.int32, 9, value.iWebDefaultBitRate)
|
||||
oos.write(tarscore.int32, 10, value.iMobileDefaultBitRate)
|
||||
oos.write(tarscore.int64, 11, value.lMultiStreamFlag)
|
||||
oos.write(tarscore.string, 12, value.sNick)
|
||||
oos.write(tarscore.int64, 13, value.lYYId)
|
||||
oos.write(tarscore.int64, 14, value.lAttendeeCount)
|
||||
oos.write(tarscore.int32, 15, value.iCodecType)
|
||||
oos.write(tarscore.int32, 16, value.iScreenType)
|
||||
oos.write(tarscore.vector, 17, value.vMultiStreamInfo)
|
||||
oos.write(tarscore.string, 18, value.sLiveDesc)
|
||||
oos.write(tarscore.int64, 19, value.lLiveCompatibleFlag)
|
||||
oos.write(tarscore.string, 20, value.sAvatarUrl)
|
||||
oos.write(tarscore.int32, 21, value.iSourceType)
|
||||
oos.write(tarscore.string, 22, value.sSubchannelName)
|
||||
oos.write(tarscore.string, 23, value.sVideoCaptureUrl)
|
||||
oos.write(tarscore.int32, 24, value.iStartTime)
|
||||
oos.write(tarscore.int64, 25, value.lChannelId)
|
||||
oos.write(tarscore.int64, 26, value.lSubChannelId)
|
||||
oos.write(tarscore.string, 27, value.sLocation)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaBeginLiveNotice()
|
||||
value.lPresenterUid = ios.read(tarscore.int64, 0, False)
|
||||
value.iGameId = ios.read(tarscore.int32, 1, False)
|
||||
value.sGameName = ios.read(tarscore.string, 2, False)
|
||||
value.iRandomRange = ios.read(tarscore.int32, 3, False)
|
||||
value.iStreamType = ios.read(tarscore.int32, 4, False)
|
||||
value.vStreamInfo = ios.read(tarscore.vector, 5, False)
|
||||
value.vCdnList = ios.read(tarscore.vector, 6, False)
|
||||
value.lLiveId = ios.read(tarscore.int64, 7, False)
|
||||
value.iPCDefaultBitRate = ios.read(tarscore.int32, 8, False)
|
||||
value.iWebDefaultBitRate = ios.read(tarscore.int32, 9, False)
|
||||
value.iMobileDefaultBitRate = ios.read(tarscore.int32, 10, False)
|
||||
value.lMultiStreamFlag = ios.read(tarscore.int64, 11, False)
|
||||
value.sNick = ios.read(tarscore.string, 12, False)
|
||||
value.lYYId = ios.read(tarscore.int64, 13, False)
|
||||
value.lAttendeeCount = ios.read(tarscore.int64, 14, False)
|
||||
value.iCodecType = ios.read(tarscore.int32, 15, False)
|
||||
value.iScreenType = ios.read(tarscore.int32, 16, False)
|
||||
value.vMultiStreamInfo = ios.read(tarscore.vector, 17, False)
|
||||
value.sLiveDesc = ios.read(tarscore.string, 18, False)
|
||||
value.lLiveCompatibleFlag = ios.read(tarscore.int64, 19, False)
|
||||
value.sAvatarUrl = ios.read(tarscore.string, 20, False)
|
||||
value.iSourceType = ios.read(tarscore.int32, 21, False)
|
||||
value.sSubchannelName = ios.read(tarscore.string, 22, False)
|
||||
value.sVideoCaptureUrl = ios.read(tarscore.string, 23, False)
|
||||
value.iStartTime = ios.read(tarscore.int32, 24, False)
|
||||
value.lChannelId = ios.read(tarscore.int64, 25, False)
|
||||
value.lSubChannelId = ios.read(tarscore.int64, 26, False)
|
||||
value.sLocation = ios.read(tarscore.string, 27, False)
|
||||
return value
|
||||
|
||||
def as_dict(self):
|
||||
return {
|
||||
"lPresenterUid": self.lPresenterUid,
|
||||
"iGameId": self.iGameId,
|
||||
"sGameName": self.sGameName,
|
||||
"iRandomRange": self.iRandomRange,
|
||||
"iStreamType": self.iStreamType,
|
||||
"vStreamInfo": self.vStreamInfo,
|
||||
"vCdnList": self.vCdnList,
|
||||
"lLiveId": self.lLiveId,
|
||||
"iPCDefaultBitRate": self.iPCDefaultBitRate,
|
||||
"iWebDefaultBitRate": self.iWebDefaultBitRate,
|
||||
"iMobileDefaultBitRate": self.iMobileDefaultBitRate,
|
||||
"lMultiStreamFlag": self.lMultiStreamFlag,
|
||||
"sNick": self.sNick,
|
||||
"lYYId": self.lYYId,
|
||||
"lAttendeeCount": self.lAttendeeCount,
|
||||
"iCodecType": self.iCodecType,
|
||||
"iScreenType": self.iScreenType,
|
||||
"vMultiStreamInfo": self.vMultiStreamInfo,
|
||||
"sLiveDesc": self.sLiveDesc,
|
||||
"lLiveCompatibleFlag": self.lLiveCompatibleFlag,
|
||||
"sAvatarUrl": self.sAvatarUrl,
|
||||
"iSourceType": self.iSourceType,
|
||||
"sSubchannelName": self.sSubchannelName,
|
||||
"sVideoCaptureUrl": self.sVideoCaptureUrl,
|
||||
"iStartTime": self.iStartTime,
|
||||
"lChannelId": self.lChannelId,
|
||||
"lSubChannelId": self.lSubChannelId,
|
||||
"sLocation": self.sLocation
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
from biliup.common.tars import tarscore
|
||||
|
||||
class HuyaMultiStreamInfo(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.MultiStreamInfo"
|
||||
|
||||
def __init__(self):
|
||||
self.sDisplayName: tarscore.string = ""
|
||||
self.iBitRate: tarscore.int32 = 0
|
||||
self.iCodecType: tarscore.int32 = 0
|
||||
self.iCompatibleFlag: tarscore.int32 = 0
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.string, 0, value.sDisplayName)
|
||||
oos.write(tarscore.int32, 1, value.iBitRate)
|
||||
oos.write(tarscore.int32, 2, value.iCodecType)
|
||||
oos.write(tarscore.int32, 3, value.iCompatibleFlag)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaMultiStreamInfo()
|
||||
value.sDisplayName = ios.read(tarscore.string, 0, False)
|
||||
value.iBitRate = ios.read(tarscore.int32, 1, False)
|
||||
value.iCodecType = ios.read(tarscore.int32, 2, False)
|
||||
value.iCompatibleFlag = ios.read(tarscore.int32, 3, False)
|
||||
return value
|
||||
@@ -1,69 +0,0 @@
|
||||
from biliup.common.tars import tarscore
|
||||
|
||||
class HuyaStreamInfo(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.StreamInfo"
|
||||
|
||||
def __init__(self):
|
||||
self.sCdnType: tarscore.string = ""
|
||||
self.iIsMaster: tarscore.int32 = 0
|
||||
self.lChannelId: tarscore.int64 = 0
|
||||
self.lSubChannelId: tarscore.int64 = 0
|
||||
self.lPresenterUid: tarscore.int64 = 0
|
||||
self.sStreamName: tarscore.string = ""
|
||||
self.sFlvUrl: tarscore.string = ""
|
||||
self.sFlvUrlSuffix: tarscore.string = ""
|
||||
self.sFlvAntiCode: tarscore.string = ""
|
||||
self.sHlsUrl: tarscore.string = ""
|
||||
self.sHlsUrlSuffix: tarscore.string = ""
|
||||
self.sHlsAntiCode: tarscore.string = ""
|
||||
self.iLineIndex: tarscore.int32 = 0
|
||||
self.iIsMultiStream: tarscore.int32 = 0
|
||||
self.iPCPriorityRate: tarscore.int32 = 0
|
||||
self.iWebPriorityRate: tarscore.int32 = 0
|
||||
self.iMobilePriorityRate: tarscore.int32 = 0
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.string, 0, value.sCdnType)
|
||||
oos.write(tarscore.int32, 1, value.iIsMaster)
|
||||
oos.write(tarscore.int64, 2, value.lChannelId)
|
||||
oos.write(tarscore.int64, 3, value.lSubChannelId)
|
||||
oos.write(tarscore.int64, 4, value.lPresenterUid)
|
||||
oos.write(tarscore.string, 5, value.sStreamName)
|
||||
oos.write(tarscore.string, 6, value.sFlvUrl)
|
||||
oos.write(tarscore.string, 7, value.sFlvUrlSuffix)
|
||||
oos.write(tarscore.string, 8, value.sFlvAntiCode)
|
||||
oos.write(tarscore.string, 9, value.sHlsUrl)
|
||||
oos.write(tarscore.string, 10, value.sHlsUrlSuffix)
|
||||
oos.write(tarscore.string, 11, value.sHlsAntiCode)
|
||||
oos.write(tarscore.int32, 12, value.iLineIndex)
|
||||
oos.write(tarscore.int32, 13, value.iIsMultiStream)
|
||||
oos.write(tarscore.int32, 14, value.iPCPriorityRate)
|
||||
oos.write(tarscore.int32, 15, value.iWebPriorityRate)
|
||||
oos.write(tarscore.int32, 16, value.iMobilePriorityRate)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaStreamInfo()
|
||||
value.sCdnType = ios.read(tarscore.string, 0, False)
|
||||
value.iIsMaster = ios.read(tarscore.int32, 1, False)
|
||||
value.lChannelId = ios.read(tarscore.int64, 2, False)
|
||||
value.lSubChannelId = ios.read(tarscore.int64, 3, False)
|
||||
value.lPresenterUid = ios.read(tarscore.int64, 4, False)
|
||||
value.sStreamName = ios.read(tarscore.string, 5, False)
|
||||
value.sFlvUrl = ios.read(tarscore.string, 6, False)
|
||||
value.sFlvUrlSuffix = ios.read(tarscore.string, 7, False)
|
||||
value.sFlvAntiCode = ios.read(tarscore.string, 8, False)
|
||||
value.sHlsUrl = ios.read(tarscore.string, 9, False)
|
||||
value.sHlsUrlSuffix = ios.read(tarscore.string, 10, False)
|
||||
value.sHlsAntiCode = ios.read(tarscore.string, 11, False)
|
||||
value.iLineIndex = ios.read(tarscore.int32, 12, False)
|
||||
value.iIsMultiStream = ios.read(tarscore.int32, 13, False)
|
||||
value.iPCPriorityRate = ios.read(tarscore.int32, 14, False)
|
||||
value.iWebPriorityRate = ios.read(tarscore.int32, 15, False)
|
||||
value.iMobilePriorityRate = ios.read(tarscore.int32, 16, False)
|
||||
return value
|
||||
|
||||
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
from biliup.common.tars import tarscore
|
||||
|
||||
class HuyaStreamSettingNotice(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.StreamSettingNotice"
|
||||
|
||||
def __init__(self):
|
||||
self.lPresenterUid: tarscore.int64 = 0
|
||||
self.iBitRate: tarscore.int32 = 0
|
||||
self.iResolution: tarscore.int32 = 0
|
||||
self.iFrameRate: tarscore.int32 = 0
|
||||
self.lLiveId: tarscore.int64 = 0
|
||||
self.sDisplayName: tarscore.string = ""
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.int64, 0, value.lPresenterUid)
|
||||
oos.write(tarscore.int32, 1, value.iBitRate)
|
||||
oos.write(tarscore.int32, 2, value.iResolution)
|
||||
oos.write(tarscore.int32, 3, value.iFrameRate)
|
||||
oos.write(tarscore.int64, 4, value.lLiveId)
|
||||
oos.write(tarscore.string, 5, value.sDisplayName)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaStreamSettingNotice()
|
||||
value.lPresenterUid = ios.read(tarscore.int64, 0, False)
|
||||
value.iBitRate = ios.read(tarscore.int32, 1, False)
|
||||
value.iResolution = ios.read(tarscore.int32, 2, False)
|
||||
value.iFrameRate = ios.read(tarscore.int32, 3, False)
|
||||
value.lLiveId = ios.read(tarscore.int64, 4, False)
|
||||
value.sDisplayName = ios.read(tarscore.string, 5, False)
|
||||
return value
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
from biliup.common.tars import tarscore
|
||||
from ..packet.__util import auto_decode_fields
|
||||
|
||||
@auto_decode_fields
|
||||
class HuyaUserId(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.UserId"
|
||||
__tars_class__ = "HUYA.UserId"
|
||||
|
||||
def __init__(self):
|
||||
self.lUid: tarscore.int64 = 0
|
||||
@@ -10,6 +12,9 @@ class HuyaUserId(tarscore.struct):
|
||||
self.sToken: tarscore.string = ""
|
||||
self.sHuYaUA: tarscore.string = ""
|
||||
self.sCookie: tarscore.string = ""
|
||||
self.iTokenType: tarscore.int32 = 0
|
||||
self.sDeviceId: tarscore.string = ""
|
||||
self.sQIMEI: tarscore.string = ""
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
@@ -18,18 +23,19 @@ class HuyaUserId(tarscore.struct):
|
||||
oos.write(tarscore.string, 2, value.sToken)
|
||||
oos.write(tarscore.string, 3, value.sHuYaUA)
|
||||
oos.write(tarscore.string, 4, value.sCookie)
|
||||
oos.write(tarscore.int32, 5, value.iTokenType)
|
||||
oos.write(tarscore.string, 6, value.sDeviceId)
|
||||
oos.write(tarscore.string, 7, value.sQIMEI)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaUserId()
|
||||
value.lUid = ios.read(tarscore.int64, 0, False)
|
||||
# print(("lUid = %d" % value.lUid))
|
||||
value.sGuid = ios.read(tarscore.string, 1, False)
|
||||
# print(("sGuid = %s" % value.sGuid))
|
||||
value.sToken = ios.read(tarscore.string, 2, False)
|
||||
# print(("sToken = %s" % value.sToken))
|
||||
value.sHuYaUA = ios.read(tarscore.string, 3, False)
|
||||
# print(("sHuYaUA = %s" % value.sHuYaUA))
|
||||
value.sCookie = ios.read(tarscore.string, 4, False)
|
||||
# print(("sCookie = %s" % value.sCookie))
|
||||
value.iTokenType = ios.read(tarscore.int32, 5, False)
|
||||
value.sDeviceId = ios.read(tarscore.string, 6, False)
|
||||
value.sQIMEI = ios.read(tarscore.string, 7, False)
|
||||
return value
|
||||
@@ -1,7 +1,7 @@
|
||||
from biliup.common.tars import tarscore
|
||||
|
||||
class HuyaWSUserInfo(tarscore.struct):
|
||||
__tars_class__ = "Huya.WSUserInfo"
|
||||
__tars_class__ = "HUYA.WSUserInfo"
|
||||
|
||||
def __init__(self):
|
||||
self.lUid: tarscore.int64 = 0
|
||||
|
||||
@@ -2,11 +2,16 @@ from biliup.common.tars import tarscore
|
||||
from biliup.common.tars.__tars import BinBuffer
|
||||
|
||||
class HuyaWebSocketCommand(tarscore.struct):
|
||||
__tars_class__ = "Huya.WebSocketCommand"
|
||||
__tars_class__ = "HUYA.WebSocketCommand"
|
||||
|
||||
def __init__(self):
|
||||
self.iCmdType: tarscore.int32 = 0
|
||||
self.vData: tarscore.bytes = b''
|
||||
self.lRequestId: tarscore.int64 = 0
|
||||
self.traceId: tarscore.string = ""
|
||||
self.iEncryptType: tarscore.int32 = 0
|
||||
self.lTime: tarscore.int64 = 0
|
||||
self.sMD5: tarscore.string = ""
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
|
||||
Generated
+2428
-1249
File diff suppressed because it is too large.
Load diff
+3
-3
@@ -24,11 +24,11 @@
|
||||
"@types/node": "^20.13.0",
|
||||
"@types/react": "^18.3.3",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-next": "^15.1.3",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-next": "^14.2.26",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"ncp": "^2.0.0",
|
||||
"sass": "^1.87.0",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user