feat(Huya): 支持 Huya Wup 协议以获取正确直播流 (#1385)
* feat(Huya): 支持 Huya Wup 协议以获取正确直播流 * chore: 补充 webui 内容
This commit is contained in:
1 parent
795f2965cc
commit
8293b6cd91
41 files changed
+980
-70
No files matched your search
@@ -221,7 +221,7 @@ const Bilibili: React.FC<Props> = props => {
|
||||
/>
|
||||
<Form.Switch
|
||||
field="bili_anonymous_origin"
|
||||
extraText="使用自定义API获取 master.m3u8 内的 hls_fmp4 原画流,无法录制特殊直播。默认关闭。"
|
||||
extraText="使用自定义API获取 master playlist 内的 hls_fmp4 原画流,无法录制特殊直播。默认关闭。"
|
||||
label="免登录原画(bili_anonymous_origin)"
|
||||
fieldStyle={{
|
||||
alignSelf: 'stretch',
|
||||
|
||||
@@ -35,14 +35,18 @@ const Global: React.FC = () => {
|
||||
<div style={{ fontSize: '14px' }}>
|
||||
选择全局默认的下载插件, 可选:
|
||||
<br />
|
||||
1. streamlink(仅限 hls 流,不支持的流将回退到 ffmpeg。需安装 FFmpeg)
|
||||
1. streamlink(仅限 hls 流,不支持的流将回退到 ffmpeg。非 Docker 用户需自行安装 FFmpeg)
|
||||
<br />
|
||||
2. ffmpeg(需安装 FFmpeg)
|
||||
2. ffmpeg(非 Docker 用户需自行安装 FFmpeg)
|
||||
<br />
|
||||
3. stream-gears(默认。防 FLV 流花屏)
|
||||
<br />
|
||||
4. sync-downloader(流式边录边传,需先为主播设定上传模板。不受
|
||||
pool2/threads/segment_time 控制,默认 3 线程上传,请确保上传带宽充足。需安装 FFmpeg)详见 Wiki <a href="https://github.com/biliup/biliup/wiki/%E8%BE%B9%E5%BD%95%E8%BE%B9%E4%BC%A0%E5%8A%9F%E8%83%BD" target="_blank" rel="noopener noreferrer" >点击查看</a>
|
||||
pool2/threads/segment_time 控制,默认 3 线程上传,请确保上传带宽充足。非 Docker 用户需自行安装 FFmpeg)详见 Wiki <a href="https://github.com/biliup/biliup/wiki/%E8%BE%B9%E5%BD%95%E8%BE%B9%E4%BC%A0%E5%8A%9F%E8%83%BD" target="_blank" rel="noopener noreferrer" >点击查看</a>
|
||||
<br />
|
||||
5. ytarchive(仅适用于 Youtube Live)
|
||||
<br />
|
||||
{/* 6. mesio(基于 Rust 的命令行视频下载/修复器)详见 <a href="https://github.com/hua0512/rust-srec/tree/main/mesio-cli" target="_blank" rel="noopener noreferrer" >项目主页</a> */}
|
||||
</div>
|
||||
}
|
||||
style={{ width: '100%' }}
|
||||
@@ -56,6 +60,8 @@ const Global: React.FC = () => {
|
||||
<Select.Option value="ffmpeg">ffmpeg</Select.Option>
|
||||
<Select.Option value="stream-gears">stream-gears(默认)</Select.Option>
|
||||
<Select.Option value="sync-downloader">sync-downloader(边录边传)</Select.Option>
|
||||
<Select.Option value="ytarchive">ytarchive(仅适用于 Youtube Live)</Select.Option>
|
||||
{/* <Select.Option value="mesio">mesio</Select.Option> */}
|
||||
</Form.Select>
|
||||
{formApi.getValue('downloader') === 'sync-downloader' ? (
|
||||
<>
|
||||
|
||||
@@ -82,12 +82,12 @@ const Huya: React.FC<Props> = props => {
|
||||
>
|
||||
<Select.Option value="AL">直播线路3(AL)</Select.Option>
|
||||
<Select.Option value="TX">直播线路5(TX)</Select.Option>
|
||||
<Select.Option value="HW">直播线路6(HW)</Select.Option>
|
||||
{/* <Select.Option value="HW">直播线路6(HW)</Select.Option> */}
|
||||
{/* <Select.Option value="WS">网宿(WS)</Select.Option> */}
|
||||
<Select.Option value="AL13">直播线路13(AL13)</Select.Option>
|
||||
<Select.Option value="HS">直播线路14(HS)</Select.Option>
|
||||
<Select.Option value="TX15">直播线路15(TX15)</Select.Option>
|
||||
<Select.Option value="HW16">直播线路16(HW16)</Select.Option>
|
||||
{/* <Select.Option value="HW16">直播线路16(HW16)</Select.Option> */}
|
||||
</Form.Select>
|
||||
<Form.Switch
|
||||
field="huya_cdn_fallback"
|
||||
@@ -129,7 +129,7 @@ const Huya: React.FC<Props> = props => {
|
||||
/>
|
||||
<Form.Switch
|
||||
field="huya_use_wup"
|
||||
extraText="使用 WUP 协议的请求头,可能解决部分直播分区 2 分钟分段问题"
|
||||
extraText="使用 WUP 协议获取直播流,可能解决部分直播分区 2 分钟分段问题"
|
||||
label="使用 WUP 协议(huya_use_wup)"
|
||||
initValue={entity?.hasOwnProperty('huya_use_wup') ? entity['huya_use_wup'] : true}
|
||||
fieldStyle={{
|
||||
|
||||
@@ -210,7 +210,7 @@ class DanmakuClient(IDanmakuClient):
|
||||
except:
|
||||
logger.warning(f"{DanmakuClient.__name__}:{self.__url}:弹幕处理异常", exc_info=True)
|
||||
continue
|
||||
|
||||
|
||||
# 正常弹幕记录
|
||||
if msg_type == 'danmaku': # 文字弹幕
|
||||
try:
|
||||
@@ -341,7 +341,7 @@ class DanmakuClient(IDanmakuClient):
|
||||
self.__content['uid'] = 0
|
||||
logger.warning(f"{DanmakuClient.__name__}:{self.__url}: 弹幕连接异常,降级至非完整弹幕")
|
||||
continue
|
||||
logger.warning(f"{DanmakuClient.__name__}:{self.__url}: 弹幕连接异常,将在 30 秒后重试")
|
||||
logger.warning(f"{DanmakuClient.__name__}:{self.__url}: 弹幕连接异常,将在 30 秒后重试", exc_info=True)
|
||||
except:
|
||||
# 记录异常不到外部处理
|
||||
logger.exception(f"{DanmakuClient.__name__}:{self.__url}: 弹幕异常,将在 30 秒后重试")
|
||||
|
||||
+40
-13
@@ -1,8 +1,11 @@
|
||||
import aiohttp
|
||||
from biliup.plugins import random_user_agent
|
||||
|
||||
from .tars import tarscore
|
||||
from biliup.common.tars import tarscore
|
||||
from biliup.plugins import match1
|
||||
from biliup.plugins.huya_wup.wup_struct import EWebSocketCommandType
|
||||
from biliup.plugins.huya_wup.wup_struct.WebSocketCommand import HuyaWebSocketCommand
|
||||
from biliup.plugins.huya_wup.wup_struct.WSUserInfo import HuyaWSUserInfo
|
||||
|
||||
|
||||
class Huya:
|
||||
@@ -29,21 +32,45 @@ class Huya:
|
||||
# tid = match1(room_page, r"lChannelId\":\"?(\d+)\"?")
|
||||
# sid = match1(room_page, r"lSubChannelId\":\"?(\d+)\"?")
|
||||
|
||||
import base64
|
||||
ws_user_info = HuyaWSUserInfo()
|
||||
ws_user_info.iUid = uid
|
||||
ws_user_info.bAnonymous = False
|
||||
ws_user_info.lGroupId = uid
|
||||
ws_user_info.lGroupType = 3
|
||||
oos = tarscore.TarsOutputStream()
|
||||
oos.write(tarscore.int64, 0, uid)
|
||||
oos.write(tarscore.boolean, 1, False) # Anonymous
|
||||
oos.write(tarscore.string, 2, "") # sGuid
|
||||
oos.write(tarscore.string, 3, "")
|
||||
oos.write(tarscore.int64, 4, 0) # tid
|
||||
oos.write(tarscore.int64, 5, 0) # sid
|
||||
oos.write(tarscore.int64, 6, uid)
|
||||
oos.write(tarscore.int64, 7, 3)
|
||||
ws_user_info.writeTo(oos, ws_user_info)
|
||||
|
||||
wscmd = tarscore.TarsOutputStream()
|
||||
wscmd.write(tarscore.int32, 0, 1)
|
||||
wscmd.write(tarscore.bytes, 1, oos.getBuffer())
|
||||
# b64data = base64.b64encode(oos.getBuffer())
|
||||
# print(b64data)
|
||||
|
||||
reg_datas.append(wscmd.getBuffer())
|
||||
ws_cmd = HuyaWebSocketCommand()
|
||||
ws_cmd.iCmdType = EWebSocketCommandType.EWSCmd_RegisterReq
|
||||
ws_cmd.vData = oos.getBuffer()
|
||||
oos = tarscore.TarsOutputStream()
|
||||
ws_cmd.writeTo(oos, ws_cmd)
|
||||
|
||||
# oos = tarscore.TarsOutputStream()
|
||||
# oos.write(tarscore.int64, 0, uid)
|
||||
# oos.write(tarscore.boolean, 1, False) # Anonymous
|
||||
# oos.write(tarscore.string, 2, "") # sGuid
|
||||
# oos.write(tarscore.string, 3, "")
|
||||
# oos.write(tarscore.int64, 4, 0) # tid
|
||||
# oos.write(tarscore.int64, 5, 0) # sid
|
||||
# oos.write(tarscore.int64, 6, uid)
|
||||
# oos.write(tarscore.int64, 7, 3)
|
||||
|
||||
# b64data = base64.b64encode(oos.getBuffer())
|
||||
# print(b64data)
|
||||
|
||||
# wscmd = tarscore.TarsOutputStream()
|
||||
# wscmd.write(tarscore.int32, 0, 1)
|
||||
# wscmd.write(tarscore.bytes, 1, oos.getBuffer())
|
||||
|
||||
# b64data = base64.b64encode(oos.getBuffer())
|
||||
# print(b64data)
|
||||
|
||||
reg_datas.append(oos.getBuffer())
|
||||
|
||||
return Huya.wss_url, reg_datas
|
||||
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -50,13 +50,13 @@ class RequestPacket(util.struct):
|
||||
def readFrom(ios):
|
||||
value = RequestPacket()
|
||||
value.iVersion = ios.read(util.int16, 1, True, 0)
|
||||
print(("iVersion = %d" % value.iVersion))
|
||||
# print(("iVersion = %d" % value.iVersion))
|
||||
value.cPacketType = ios.read(util.int8, 2, True, 0)
|
||||
print(("cPackerType = %d" % value.cPacketType))
|
||||
# print(("cPackerType = %d" % value.cPacketType))
|
||||
value.iMessageType = ios.read(util.int32, 3, True, 0)
|
||||
print(("iMessageType = %d" % value.iMessageType))
|
||||
# print(("iMessageType = %d" % value.iMessageType))
|
||||
value.iRequestId = ios.read(util.int32, 4, True, 0)
|
||||
print(("iRequestId = %d" % value.iRequestId))
|
||||
# print(("iRequestId = %d" % value.iRequestId))
|
||||
value.sServantName = ios.read(util.string, 5, True, '22222222')
|
||||
value.sFuncName = ios.read(util.string, 6, True, '')
|
||||
value.sBuffer = ios.read(util.bytes, 7, True, value.sBuffer)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -9,13 +9,14 @@ import subprocess
|
||||
import threading
|
||||
import time
|
||||
import shutil
|
||||
import copy
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import AsyncGenerator, List, Callable, Optional
|
||||
from urllib.parse import urlparse
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import requests
|
||||
from requests.utils import DEFAULT_ACCEPT_ENCODING
|
||||
from requests.utils import DEFAULT_ACCEPT_ENCODING, parse_header_links
|
||||
from httpx import HTTPStatusError
|
||||
|
||||
from biliup.common.util import client, loop, check_timerange
|
||||
@@ -65,6 +66,7 @@ class DownloadBase(ABC):
|
||||
'accept-language': 'zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
|
||||
'user-agent': random_user_agent(),
|
||||
}
|
||||
self.stream_headers = copy.deepcopy(self.fake_headers)
|
||||
self.segment_time = config.get('segment_time', '01:00:00')
|
||||
self.time_range = config.get('time_range')
|
||||
self.excluded_keywords = config.get('excluded_keywords')
|
||||
@@ -104,6 +106,7 @@ class DownloadBase(ABC):
|
||||
return True
|
||||
|
||||
def download(self):
|
||||
self.update_headers(self.stream_headers)
|
||||
# print(f"{self.plugin_msg}: Plugin settings - {self.__dict__}")
|
||||
logger.debug(f"{self.plugin_msg}: Plugin settings - {self.__dict__}")
|
||||
# logger.info(f"{self.plugin_msg}: Request headers - {self.fake_headers}")
|
||||
@@ -133,7 +136,7 @@ class DownloadBase(ABC):
|
||||
if not self.file_size:
|
||||
self.file_size = 2 * 1024 * 1024 * 1024
|
||||
self.file_size = ((self.file_size + min_size - 1) // min_size) * min_size # 向上取整
|
||||
sync_download(self.raw_stream_url, self.fake_headers,
|
||||
sync_download(self.raw_stream_url, self.stream_headers,
|
||||
max_file_size=int(self.file_size / 1024 / 1024),
|
||||
output_prefix=self.gen_download_filename(True),
|
||||
stream_info=stream_info,
|
||||
@@ -150,7 +153,7 @@ class DownloadBase(ABC):
|
||||
else:
|
||||
# 其他流stream_gears会按hls保存为ts
|
||||
self.suffix = 'ts'
|
||||
stream_gears_download(self.raw_stream_url, self.fake_headers, self.gen_download_filename(),
|
||||
stream_gears_download(self.raw_stream_url, self.stream_headers, self.gen_download_filename(),
|
||||
self.segment_time,
|
||||
self.file_size,
|
||||
lambda file_name: self.__download_segment_callback(file_name))
|
||||
@@ -231,7 +234,7 @@ class DownloadBase(ABC):
|
||||
# '--http-proxy', 'http://127.0.0.1:7890',
|
||||
# '--hls-live-restart',
|
||||
]
|
||||
for key, value in self.fake_headers.items():
|
||||
for key, value in self.stream_headers.items():
|
||||
streamlink_cmd.extend(['--http-header', f'{key}={value}'])
|
||||
streamlink_cmd.extend([
|
||||
self.raw_stream_url,
|
||||
@@ -241,7 +244,7 @@ class DownloadBase(ABC):
|
||||
streamlink_proc = subprocess.Popen(streamlink_cmd, stdout=subprocess.PIPE)
|
||||
input_uri = 'pipe:0'
|
||||
else:
|
||||
input_args += ['-headers', ''.join('%s: %s\r\n' % x for x in self.fake_headers.items()),
|
||||
input_args += ['-headers', ''.join('%s: %s\r\n' % x for x in self.stream_headers.items()),
|
||||
'-rw_timeout', '20000000']
|
||||
if '.m3u8' in urlparse(self.raw_stream_url).path:
|
||||
input_args += ['-max_reload', '1000']
|
||||
@@ -499,6 +502,9 @@ class DownloadBase(ABC):
|
||||
else:
|
||||
return filename
|
||||
|
||||
def update_headers(self, headers):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def download_file_rename(old_file_name, file_name):
|
||||
try:
|
||||
|
||||
+215
-39
@@ -1,9 +1,10 @@
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
import html
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from urllib.parse import parse_qs, unquote
|
||||
from async_lru import alru_cache
|
||||
from typing import (
|
||||
@@ -20,9 +21,16 @@ from ..engine.decorators import Plugin
|
||||
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 (
|
||||
HuyaGetCdnTokenInfoReq,
|
||||
HuyaGetCdnTokenInfoRsp
|
||||
)
|
||||
|
||||
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_WEB_ROOM_DATA_REGEX = r"var TT_ROOM_DATA = (.*?);"
|
||||
|
||||
@Plugin.download(regexp=r'https?://(?:(?:www|m)\.)?huya\.com')
|
||||
@@ -72,16 +80,12 @@ class Huya(DownloadBase):
|
||||
|
||||
# is_xingxiu = (room_profile['gid'] == 1663)
|
||||
gid_blacklist = [1663, ]
|
||||
skip_query_build = self.huya_imgplus and (self.huya_mobile_api or room_profile['gid'] in gid_blacklist)
|
||||
stream_urls = self.build_stream_urls(room_profile['streams_info'], skip_query_build)
|
||||
skip_query_build = room_profile['gid'] in gid_blacklist
|
||||
stream_urls = await self.build_stream_urls(room_profile['streams_info'], skip_query_build)
|
||||
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]
|
||||
|
||||
# Thx stream-rec
|
||||
if self.huya_use_wup:
|
||||
self.update_headers(self.fake_headers)
|
||||
|
||||
try:
|
||||
self.raw_stream_url = self.add_ratio(
|
||||
stream_urls[self.huya_cdn],
|
||||
@@ -113,7 +117,7 @@ class Huya(DownloadBase):
|
||||
if not room_profile['live']:
|
||||
logger.debug(f"{self.plugin_msg}: {room_profile['message']}")
|
||||
return False
|
||||
stream_urls = self.build_stream_urls(room_profile['streams_info'], skip_query_build)
|
||||
stream_urls = await self.build_stream_urls(room_profile['streams_info'], skip_query_build)
|
||||
|
||||
self.raw_stream_url = self.add_ratio(
|
||||
stream_urls[self.huya_cdn],
|
||||
@@ -123,12 +127,10 @@ 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:
|
||||
'''
|
||||
添加码率
|
||||
@@ -158,14 +160,12 @@ 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', '')
|
||||
|
||||
|
||||
def build_stream_urls(
|
||||
async def build_stream_urls(
|
||||
self,
|
||||
streams_info: List[Dict[str, Any]],
|
||||
skip_query_build: bool
|
||||
@@ -173,7 +173,7 @@ class Huya(DownloadBase):
|
||||
'''
|
||||
构建流地址
|
||||
:param streams_info: 流信息
|
||||
:param skip_query_build: 是否跳过构建anti_code
|
||||
:param skip_query_build: 跳过构建anti_code
|
||||
:return: 流地址
|
||||
'''
|
||||
proto = self.huya_protocol
|
||||
@@ -187,22 +187,34 @@ 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 not skip_query_build:
|
||||
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}"
|
||||
base_url = stream[f's{proto}Url'].replace('http://', 'https://') # 强制https
|
||||
streams[cdn] = f"{base_url}/{stream_name}.{suffix}?{anti_code}"
|
||||
weights[cdn] = priority
|
||||
return self.__weight_sorting(streams, weights)
|
||||
|
||||
|
||||
def extract_room_profile(self, data: Union[str, Dict[str, Any]]) -> Dict[str, Any]:
|
||||
'''
|
||||
ON: 直播
|
||||
REPLAY: 重播
|
||||
OFF: 未开播
|
||||
'''
|
||||
# PC web
|
||||
if isinstance(data, str):
|
||||
room_data = json_loads(match1(data, HUYA_WEB_ROOM_DATA_REGEX))
|
||||
s = data.split('stream: ')[1].split('};')[0]
|
||||
@@ -215,6 +227,7 @@ class Huya(DownloadBase):
|
||||
}
|
||||
live_info = s_json['data'][0]['gameLiveInfo']
|
||||
streams_info = s_json['data'][0]['gameStreamInfoList']
|
||||
# Mobile API(微信小程序?)
|
||||
elif isinstance(data, dict):
|
||||
data = data['data']
|
||||
if data['liveStatus'] != 'ON' or not data.get('liveData', {}).get('bitRateInfo'):
|
||||
@@ -238,7 +251,6 @@ class Huya(DownloadBase):
|
||||
'streams_info': streams_info,
|
||||
}
|
||||
|
||||
|
||||
async def get_room_profile(self, use_api=False) -> dict:
|
||||
'''
|
||||
获取房间信息
|
||||
@@ -268,6 +280,42 @@ 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:
|
||||
'''
|
||||
获取 wup anti_code
|
||||
:param cdn: cdn类型
|
||||
:param stream_name: 流名称
|
||||
:param presenter_uid: 主播uid
|
||||
:param proto: 协议类型
|
||||
:return: wup anti_code
|
||||
'''
|
||||
proto = "hls" if proto == "Hls" else "flv"
|
||||
headers = {}
|
||||
self.update_headers(headers)
|
||||
wup_req = Wup()
|
||||
wup_req.requestid = abs(DEFAULT_TICKET_NUMBER)
|
||||
wup_req.servant = "liveui"
|
||||
wup_req.func = "getCdnTokenInfo"
|
||||
token_info_req = HuyaGetCdnTokenInfoReq()
|
||||
token_info_req.cdnType = cdn
|
||||
token_info_req.streamName = stream_name
|
||||
token_info_req.presenterUid = presenter_uid
|
||||
wup_req.put(HuyaGetCdnTokenInfoReq, "tReq", token_info_req)
|
||||
data = wup_req.encode_v3()
|
||||
rsp = await client.post(HUYA_WUP_BASE_URL, data=data, headers=headers)
|
||||
wup_rsp = Wup()
|
||||
wup_rsp.decode_v3(rsp.content)
|
||||
token_info_rsp = wup_rsp.get(HuyaGetCdnTokenInfoRsp,"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']
|
||||
|
||||
def build_query(self, stream_name, anti_code, uid: int) -> str:
|
||||
'''
|
||||
@@ -325,7 +373,6 @@ 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:
|
||||
@@ -335,7 +382,6 @@ class Huya(DownloadBase):
|
||||
pass
|
||||
return uid or random.randint(1400000000000, 1499999999999)
|
||||
|
||||
|
||||
async def get_anonymous_uid(self) -> int:
|
||||
try:
|
||||
rsp = await client.post(
|
||||
@@ -354,40 +400,170 @@ class Huya(DownloadBase):
|
||||
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
|
||||
|
||||
class UAType(Enum):
|
||||
MEDIA_PLAYER = 'media_player'
|
||||
HYSDK = 'hysdk'
|
||||
|
||||
class Platform(Enum):
|
||||
ANDROID = 'adr'
|
||||
HUYA_NFTV = 'huya_nftv'
|
||||
WEBSOCKET = 'webh5'
|
||||
WINDOWS = 'pc_exe'
|
||||
|
||||
class UAGenerator:
|
||||
# 配置字典
|
||||
HYAPP_CONFIGS = {
|
||||
Platform.ANDROID: {
|
||||
'platform': Platform.ANDROID,
|
||||
'version': '0.0.0', # LocalVersion or "0.0.0" + hotfix_version
|
||||
'channel': 'live'
|
||||
},
|
||||
Platform.HUYA_NFTV: {
|
||||
'platform': Platform.HUYA_NFTV,
|
||||
'version': '2.5.1.3141',
|
||||
'channel': 'official'
|
||||
},
|
||||
Platform.WINDOWS: {
|
||||
'platform': Platform.WINDOWS,
|
||||
'version': '6090007',
|
||||
'channel': 'official'
|
||||
},
|
||||
Platform.WEBSOCKET: { # UnUsed
|
||||
'platform': Platform.WEBSOCKET,
|
||||
'version': '2505091506',
|
||||
'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() -> str:
|
||||
hyapp_platform = 'huya_nftv' # adr or huya_nftv, pc_exe, webh5
|
||||
hyapp_version = '2.5.1.3141' # adr/nftv: LocalVersion or "0.0.0" + hotfix_version, pc_exe: 6070100, ws: 2505091506
|
||||
hyapp_channel = 'official' # adr: live, nftv/pc_exe: official, websocket
|
||||
android_api_level = random.randint(28, 35) # Build.VERSION.SDK_INT
|
||||
def get_hyapp_ua(platform: Platform = Platform.WINDOWS) -> str:
|
||||
'''
|
||||
生成 hyapp 用户代理字符串
|
||||
:param platform: 平台类型
|
||||
:return: 用户代理字符串
|
||||
'''
|
||||
config = UAGenerator.HYAPP_CONFIGS.get(platform)
|
||||
if not config:
|
||||
raise ValueError(f"不支持的平台: {platform}")
|
||||
|
||||
hyapp_platform = config['platform']
|
||||
hyapp_version = config['version']
|
||||
hyapp_channel = config['channel']
|
||||
|
||||
ua = f"{hyapp_platform}&{hyapp_version}&{hyapp_channel}"
|
||||
return ua if hyapp_platform in {'pc_exe', 'webh5'} else f"{ua}&{android_api_level}"
|
||||
# windows 和 websocket 不需要添加 android_api_level
|
||||
if platform not in {Platform.WINDOWS, Platform.WEBSOCKET}:
|
||||
android_api_level = random.randint(28, 35)
|
||||
ua = f"{ua}&{android_api_level}"
|
||||
|
||||
return ua
|
||||
|
||||
@staticmethod
|
||||
def get_hysdk_ua() -> str:
|
||||
hysdk_platform = 'Android' # Android, Windows
|
||||
hysdk_version = '30000002' # 30000002
|
||||
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() -> str:
|
||||
hy_mp_platform = 'android' # android
|
||||
hy_mp_version = '20000313' # 20000313
|
||||
def get_hy_media_player_ua(platform: Platform = Platform.WINDOWS) -> str:
|
||||
'''
|
||||
生成 hy_media_player 用户代理字符串
|
||||
:param platform: 平台类型
|
||||
:return: 用户代理字符串
|
||||
'''
|
||||
# 目前只支持 android 平台
|
||||
hy_mp_platform = 'android'
|
||||
hy_mp_version = '20000313'
|
||||
|
||||
return f"{hy_mp_platform}, {hy_mp_version}"
|
||||
|
||||
@staticmethod
|
||||
def get_hy_trans_mod_ua() -> str:
|
||||
hy_trans_mod_name = 'trans' # trans
|
||||
hy_trans_mod_version = '1.24.99-rel-tv' # nftv: 1.24.99-rel-tv, adr: 2.22.13-rel, win: 2.21.0.4784
|
||||
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 update_headers(headers: dict):
|
||||
user_agent = f"{Huya.get_hy_media_player_ua()}_APP({Huya.get_hyapp_ua()})_SDK({Huya.get_hy_trans_mod_ua()})"
|
||||
# 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
|
||||
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}")
|
||||
|
||||
|
||||
def _raise_for_room_block(text: str):
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
from .packet import *
|
||||
from .wup import Wup
|
||||
|
||||
__all__ = ['Wup', 'HuyaGetCdnTokenInfoReq', 'HuyaGetCdnTokenInfoRsp']
|
||||
|
||||
DEFAULT_TICKET_NUMBER = -1
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
from .getCdnTokenInfo import (
|
||||
HuyaGetCdnTokenInfoReq,
|
||||
HuyaGetCdnTokenInfoRsp
|
||||
)
|
||||
|
||||
__all__ = ['HuyaGetCdnTokenInfoReq', 'HuyaGetCdnTokenInfoRsp']
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
from functools import wraps
|
||||
from biliup.common.tars import tarscore
|
||||
|
||||
|
||||
STANDARD_CHARSET = 'utf-8'
|
||||
|
||||
|
||||
def auto_decode_fields(cls):
|
||||
"""自动解码类中的bytes类型数据"""
|
||||
original_read_from = cls.readFrom
|
||||
|
||||
@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))
|
||||
return value
|
||||
|
||||
cls.readFrom = wrapped_read_from
|
||||
return cls
|
||||
@@ -0,0 +1,86 @@
|
||||
from biliup.common.tars import tarscore
|
||||
from .__util import auto_decode_fields
|
||||
|
||||
@auto_decode_fields
|
||||
class HuyaGetCdnTokenInfoReq(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.GetCdnTokenInfoReq"
|
||||
|
||||
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.int32, 3, value.presenterUid)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaGetCdnTokenInfoReq()
|
||||
value.url = ios.read(tarscore.string, 0, False)
|
||||
# print(("url = %s" % value.url))
|
||||
value.cdnType = ios.read(tarscore.string, 1, False)
|
||||
# print(("cdnType = %s" % value.cdnType))
|
||||
value.streamName = ios.read(tarscore.string, 2, False)
|
||||
# print(("streamName = %s" % value.streamName))
|
||||
value.presenterUid = ios.read(tarscore.int32, 3, False)
|
||||
# print(("presenterUid = %d" % value.presenterUid))
|
||||
return value
|
||||
|
||||
def as_dict(self):
|
||||
return self.__dict__.copy()
|
||||
|
||||
@auto_decode_fields
|
||||
class HuyaGetCdnTokenInfoRsp(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.GetCdnTokenInfoRsp"
|
||||
|
||||
def __init__(self):
|
||||
self.url: tarscore.string = ""
|
||||
self.cdnType: tarscore.string = ""
|
||||
self.streamName: tarscore.string = ""
|
||||
self.presenterUid: tarscore.int32 = 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.int32, 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 = HuyaGetCdnTokenInfoRsp()
|
||||
value.url = ios.read(tarscore.string, 0, False)
|
||||
# print(("url = %s" % value.url))
|
||||
value.cdnType = ios.read(tarscore.string, 1, False)
|
||||
# print(("cdnType = %s" % value.cdnType))
|
||||
value.streamName = ios.read(tarscore.string, 2, False)
|
||||
# print(("streamName = %s" % value.streamName))
|
||||
value.presenterUid = ios.read(tarscore.int32, 3, False)
|
||||
# print(("presenterUid = %d" % value.presenterUid))
|
||||
value.antiCode = ios.read(tarscore.string, 4, False)
|
||||
# print(("antiCode = %s" % value.antiCode))
|
||||
value.sTime = ios.read(tarscore.string, 5, False)
|
||||
# print(("sTime = %s" % value.sTime))
|
||||
value.flvAntiCode = ios.read(tarscore.string, 6, False)
|
||||
# print(("flvAntiCode = %s" % value.flvAntiCode))
|
||||
value.hlsAntiCode = ios.read(tarscore.string, 7, False)
|
||||
# print(("hlsAntiCode = %s" % value.hlsAntiCode))
|
||||
return value
|
||||
|
||||
def as_dict(self):
|
||||
return self.__dict__.copy()
|
||||
@@ -0,0 +1,60 @@
|
||||
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
|
||||
@@ -0,0 +1,47 @@
|
||||
from biliup.plugins.huya_wup import Wup, DEFAULT_TICKET_NUMBER
|
||||
from biliup.plugins.huya_wup.packet import (
|
||||
HuyaGetCdnTokenInfoReq,
|
||||
HuyaGetCdnTokenInfoRsp
|
||||
)
|
||||
from biliup.plugins.huya_wup.packet.__util import STANDARD_CHARSET
|
||||
|
||||
import httpx
|
||||
client = httpx.Client()
|
||||
|
||||
if __name__ == "__main__":
|
||||
# import base64
|
||||
cdn: str = "HS"
|
||||
stream_name: str = "1708907089-1708907089-7339700059157561344-3417937634-10057-A-0-1-imgplus.flv"
|
||||
presenter_uid: int = 1708907089
|
||||
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 = HuyaGetCdnTokenInfoReq()
|
||||
token_info_req.cdnType = cdn
|
||||
token_info_req.streamName = stream_name
|
||||
token_info_req.presenterUid = presenter_uid
|
||||
wup_req.put(
|
||||
vtype=HuyaGetCdnTokenInfoReq,
|
||||
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=HuyaGetCdnTokenInfoRsp,
|
||||
# name=bytes("tRsp", encoding=STANDARD_CHARSET)
|
||||
name="tRsp"
|
||||
)
|
||||
print(token_info_rsp.as_dict())
|
||||
@@ -0,0 +1,86 @@
|
||||
import struct
|
||||
|
||||
from biliup.common.tars.__tup import TarsUniPacket
|
||||
from biliup.common.tars.__packet import RequestPacket
|
||||
from biliup.common.tars.__tars import (TarsInputStream, TarsOutputStream)
|
||||
from biliup.common.tars.__util import util
|
||||
|
||||
from .packet.__util import STANDARD_CHARSET
|
||||
|
||||
|
||||
class Wup(TarsUniPacket):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.__mapa = util.mapclass(util.string, util.bytes)
|
||||
self.__new_buffer = self.__mapa()
|
||||
self.__code = RequestPacket()
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self.__code.iVersion
|
||||
|
||||
@version.setter
|
||||
def version(self, value):
|
||||
self.__code.iVersion = value
|
||||
|
||||
@property
|
||||
def servant(self):
|
||||
return self.__code.sServantName
|
||||
|
||||
@servant.setter
|
||||
def servant(self, value):
|
||||
self.__code.sServantName = value
|
||||
|
||||
@property
|
||||
def func(self):
|
||||
return self.__code.sFuncName
|
||||
|
||||
@func.setter
|
||||
def func(self, value):
|
||||
self.__code.sFuncName = value
|
||||
|
||||
@property
|
||||
def requestid(self):
|
||||
return self.__code.iRequestId
|
||||
|
||||
@requestid.setter
|
||||
def requestid(self, value):
|
||||
self.__code.iRequestId = value
|
||||
|
||||
def put(self, vtype, name, value):
|
||||
oos = TarsOutputStream()
|
||||
oos.write(vtype, 0, value)
|
||||
self.__new_buffer[name] = oos.getBuffer()
|
||||
|
||||
def get(self, vtype, name):
|
||||
if isinstance(name, str):
|
||||
name = name.encode(STANDARD_CHARSET)
|
||||
|
||||
if (name in self.__new_buffer) == False:
|
||||
raise Exception("UniAttribute not found key:%s" % name)
|
||||
|
||||
t = self.__new_buffer[name]
|
||||
o = TarsInputStream(t)
|
||||
return o.read(vtype, 0, True)
|
||||
|
||||
def encode_v3(self):
|
||||
oos = TarsOutputStream()
|
||||
oos.write(self.__mapa, 0, self.__new_buffer)
|
||||
|
||||
self.__code.iVersion = 3 # Force to use TarsV3
|
||||
self.__code.sBuffer = oos.getBuffer()
|
||||
|
||||
sos = TarsOutputStream()
|
||||
RequestPacket.writeTo(sos, self.__code)
|
||||
|
||||
return struct.pack('!i', 4 + len(sos.getBuffer())) + sos.getBuffer()
|
||||
|
||||
def decode_v3(self, buf):
|
||||
ois = TarsInputStream(buf[4:])
|
||||
self.__code = RequestPacket.readFrom(ois)
|
||||
|
||||
sis = TarsInputStream(self.__code.sBuffer)
|
||||
self.__new_buffer = sis.read(self.__mapa, 0, True)
|
||||
|
||||
def clear(self):
|
||||
self.__code.__init__()
|
||||
@@ -0,0 +1,134 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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
|
||||
@@ -0,0 +1,69 @@
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
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
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
from biliup.common.tars import tarscore
|
||||
|
||||
class HuyaUserId(tarscore.struct):
|
||||
|
||||
__tars_class__ = "Huya.UserId"
|
||||
|
||||
def __init__(self):
|
||||
self.lUid: tarscore.int64 = 0
|
||||
self.sGuid: tarscore.string = ""
|
||||
self.sToken: tarscore.string = ""
|
||||
self.sHuYaUA: tarscore.string = ""
|
||||
self.sCookie: tarscore.string = ""
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.int64, 0, value.lUid)
|
||||
oos.write(tarscore.string, 1, value.sGuid)
|
||||
oos.write(tarscore.string, 2, value.sToken)
|
||||
oos.write(tarscore.string, 3, value.sHuYaUA)
|
||||
oos.write(tarscore.string, 4, value.sCookie)
|
||||
|
||||
@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))
|
||||
return value
|
||||
@@ -0,0 +1,37 @@
|
||||
from biliup.common.tars import tarscore
|
||||
|
||||
class HuyaWSUserInfo(tarscore.struct):
|
||||
__tars_class__ = "Huya.WSUserInfo"
|
||||
|
||||
def __init__(self):
|
||||
self.lUid: tarscore.int64 = 0
|
||||
self.bAnonymous: tarscore.boolean = True
|
||||
self.sGuid: tarscore.string = ""
|
||||
self.sToken: tarscore.string = ""
|
||||
self.lTid: tarscore.int64 = 0
|
||||
self.lSid: tarscore.int64 = 0
|
||||
self.lGroupId: tarscore.int64 = 0
|
||||
self.lGroupType: tarscore.int64 = 0
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.int64, 0, value.lUid)
|
||||
oos.write(tarscore.boolean, 1, value.bAnonymous)
|
||||
oos.write(tarscore.string, 2, value.sGuid)
|
||||
oos.write(tarscore.string, 3, value.sToken)
|
||||
oos.write(tarscore.int64, 4, value.lTid)
|
||||
oos.write(tarscore.int64, 5, value.lSid)
|
||||
oos.write(tarscore.int64, 6, value.lGroupId)
|
||||
oos.write(tarscore.int64, 7, value.lGroupType)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaWSUserInfo()
|
||||
value.lUid = ios.read(tarscore.int64, 0, False)
|
||||
value.bAnonymous = ios.read(tarscore.boolean, 1, False)
|
||||
value.sGuid = ios.read(tarscore.string, 2, False)
|
||||
value.sToken = ios.read(tarscore.string, 3, False)
|
||||
value.lTid = ios.read(tarscore.int64, 4, False)
|
||||
value.lSid = ios.read(tarscore.int64, 5, False)
|
||||
value.lGroupId = ios.read(tarscore.int64, 6, False)
|
||||
value.lGroupType = ios.read(tarscore.int64, 7, False)
|
||||
@@ -0,0 +1,24 @@
|
||||
from biliup.common.tars import tarscore
|
||||
from biliup.common.tars.__tars import BinBuffer
|
||||
|
||||
class HuyaWebSocketCommand(tarscore.struct):
|
||||
__tars_class__ = "Huya.WebSocketCommand"
|
||||
|
||||
def __init__(self):
|
||||
self.iCmdType: tarscore.int32 = 0
|
||||
self.vData: tarscore.bytes = b''
|
||||
|
||||
@staticmethod
|
||||
def writeTo(oos: tarscore.TarsOutputStream, value):
|
||||
oos.write(tarscore.int32, 0, value.iCmdType)
|
||||
oos.write(tarscore.bytes, 1, value.vData)
|
||||
# oos.write(tarscore.int64, 2, value.lRequestId)
|
||||
# oos.write(tarscore.string, 3, value.traceId)
|
||||
|
||||
@staticmethod
|
||||
def readFrom(ios: tarscore.TarsInputStream):
|
||||
value = HuyaWebSocketCommand()
|
||||
value.iCmdType = ios.read(tarscore.int32, 0, False)
|
||||
value.vData = ios.read(tarscore.bytes, 1, False)
|
||||
# value.lRequestId = ios.read(tarscore.int64, 2, False)
|
||||
# value.traceId = ios.read(tarscore.string, 3, False)
|
||||
@@ -0,0 +1,17 @@
|
||||
from enum import IntEnum
|
||||
|
||||
class EWebSocketCommandType(IntEnum):
|
||||
EWSCmd_NULL = 0
|
||||
EWSCmd_RegisterReq = 1
|
||||
EWSCmd_RegisterRsp = 2
|
||||
EWSCmd_WupReq = 3
|
||||
EWSCmd_WupRsp = 4
|
||||
EWSCmdC2S_HeartBeat = 5
|
||||
EWSCmdS2C_HeartBeatAck = 6
|
||||
EWSCmdS2C_MsgPushReq = 7
|
||||
EWSCmdC2S_DeregisterReq = 8
|
||||
EWSCmdS2C_DeRegisterRsp = 9
|
||||
EWSCmdC2S_VerifyCookieReq = 10
|
||||
EWSCmdS2C_VerifyCookieRsp = 11
|
||||
EWSCmdC2S_VerifyHuyaTokenReq = 12
|
||||
EWSCmdS2C_VerifyHuyaTokenRsp = 13
|
||||
Reference in new issue
Block a user