diff --git a/app/ui/plugins/bilibili.tsx b/app/ui/plugins/bilibili.tsx index 4fae6d0..a7a3e29 100644 --- a/app/ui/plugins/bilibili.tsx +++ b/app/ui/plugins/bilibili.tsx @@ -221,7 +221,7 @@ const Bilibili: React.FC = props => { /> {
选择全局默认的下载插件, 可选:
- 1. streamlink(仅限 hls 流,不支持的流将回退到 ffmpeg。需安装 FFmpeg) + 1. streamlink(仅限 hls 流,不支持的流将回退到 ffmpeg。非 Docker 用户需自行安装 FFmpeg)
- 2. ffmpeg(需安装 FFmpeg) + 2. ffmpeg(非 Docker 用户需自行安装 FFmpeg)
3. stream-gears(默认。防 FLV 流花屏)
4. sync-downloader(流式边录边传,需先为主播设定上传模板。不受 - pool2/threads/segment_time 控制,默认 3 线程上传,请确保上传带宽充足。需安装 FFmpeg)详见 Wiki 点击查看 + pool2/threads/segment_time 控制,默认 3 线程上传,请确保上传带宽充足。非 Docker 用户需自行安装 FFmpeg)详见 Wiki 点击查看 +
+ 5. ytarchive(仅适用于 Youtube Live) +
+ {/* 6. mesio(基于 Rust 的命令行视频下载/修复器)详见 项目主页 */}
} style={{ width: '100%' }} @@ -56,6 +60,8 @@ const Global: React.FC = () => { ffmpeg stream-gears(默认) sync-downloader(边录边传) + ytarchive(仅适用于 Youtube Live) + {/* mesio */} {formApi.getValue('downloader') === 'sync-downloader' ? ( <> diff --git a/app/ui/plugins/huya.tsx b/app/ui/plugins/huya.tsx index c0c0efe..fa078fb 100644 --- a/app/ui/plugins/huya.tsx +++ b/app/ui/plugins/huya.tsx @@ -82,12 +82,12 @@ const Huya: React.FC = props => { > 直播线路3(AL) 直播线路5(TX) - 直播线路6(HW) + {/* 直播线路6(HW) */} {/* 网宿(WS) */} 直播线路13(AL13) 直播线路14(HS) 直播线路15(TX15) - 直播线路16(HW16) + {/* 直播线路16(HW16) */} = props => { /> 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): diff --git a/biliup/plugins/huya_wup/__init__.py b/biliup/plugins/huya_wup/__init__.py new file mode 100644 index 0000000..17d9e07 --- /dev/null +++ b/biliup/plugins/huya_wup/__init__.py @@ -0,0 +1,6 @@ +from .packet import * +from .wup import Wup + +__all__ = ['Wup', 'HuyaGetCdnTokenInfoReq', 'HuyaGetCdnTokenInfoRsp'] + +DEFAULT_TICKET_NUMBER = -1 \ No newline at end of file diff --git a/biliup/plugins/huya_wup/packet/__init__.py b/biliup/plugins/huya_wup/packet/__init__.py new file mode 100644 index 0000000..ab26e1a --- /dev/null +++ b/biliup/plugins/huya_wup/packet/__init__.py @@ -0,0 +1,8 @@ + +from .getCdnTokenInfo import ( + HuyaGetCdnTokenInfoReq, + HuyaGetCdnTokenInfoRsp +) + +__all__ = ['HuyaGetCdnTokenInfoReq', 'HuyaGetCdnTokenInfoRsp'] + diff --git a/biliup/plugins/huya_wup/packet/__util.py b/biliup/plugins/huya_wup/packet/__util.py new file mode 100644 index 0000000..fb282fc --- /dev/null +++ b/biliup/plugins/huya_wup/packet/__util.py @@ -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 \ No newline at end of file diff --git a/biliup/plugins/huya_wup/packet/getCdnTokenInfo.py b/biliup/plugins/huya_wup/packet/getCdnTokenInfo.py new file mode 100644 index 0000000..1757566 --- /dev/null +++ b/biliup/plugins/huya_wup/packet/getCdnTokenInfo.py @@ -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() diff --git a/biliup/plugins/huya_wup/packet/getLivingInfo.py b/biliup/plugins/huya_wup/packet/getLivingInfo.py new file mode 100644 index 0000000..61040a6 --- /dev/null +++ b/biliup/plugins/huya_wup/packet/getLivingInfo.py @@ -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 diff --git a/biliup/plugins/huya_wup/test_wup.py b/biliup/plugins/huya_wup/test_wup.py new file mode 100644 index 0000000..bdc5817 --- /dev/null +++ b/biliup/plugins/huya_wup/test_wup.py @@ -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()) diff --git a/biliup/plugins/huya_wup/wup.py b/biliup/plugins/huya_wup/wup.py new file mode 100644 index 0000000..6722e30 --- /dev/null +++ b/biliup/plugins/huya_wup/wup.py @@ -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__() \ No newline at end of file diff --git a/biliup/plugins/huya_wup/wup_struct/BeginLiveNotice.py b/biliup/plugins/huya_wup/wup_struct/BeginLiveNotice.py new file mode 100644 index 0000000..d166580 --- /dev/null +++ b/biliup/plugins/huya_wup/wup_struct/BeginLiveNotice.py @@ -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 + } \ No newline at end of file diff --git a/biliup/plugins/huya_wup/wup_struct/MultiStreamInfo.py b/biliup/plugins/huya_wup/wup_struct/MultiStreamInfo.py new file mode 100644 index 0000000..54ddeba --- /dev/null +++ b/biliup/plugins/huya_wup/wup_struct/MultiStreamInfo.py @@ -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 diff --git a/biliup/plugins/huya_wup/wup_struct/StreamInfo.py b/biliup/plugins/huya_wup/wup_struct/StreamInfo.py new file mode 100644 index 0000000..fce161f --- /dev/null +++ b/biliup/plugins/huya_wup/wup_struct/StreamInfo.py @@ -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 + + + diff --git a/biliup/plugins/huya_wup/wup_struct/StreamSettingNotice.py b/biliup/plugins/huya_wup/wup_struct/StreamSettingNotice.py new file mode 100644 index 0000000..ebb5782 --- /dev/null +++ b/biliup/plugins/huya_wup/wup_struct/StreamSettingNotice.py @@ -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 + diff --git a/biliup/plugins/huya_wup/wup_struct/UserId.py b/biliup/plugins/huya_wup/wup_struct/UserId.py new file mode 100644 index 0000000..f99ab2e --- /dev/null +++ b/biliup/plugins/huya_wup/wup_struct/UserId.py @@ -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 \ No newline at end of file diff --git a/biliup/plugins/huya_wup/wup_struct/WSUserInfo.py b/biliup/plugins/huya_wup/wup_struct/WSUserInfo.py new file mode 100644 index 0000000..b12ed05 --- /dev/null +++ b/biliup/plugins/huya_wup/wup_struct/WSUserInfo.py @@ -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) diff --git a/biliup/plugins/huya_wup/wup_struct/WebSocketCommand.py b/biliup/plugins/huya_wup/wup_struct/WebSocketCommand.py new file mode 100644 index 0000000..34b3908 --- /dev/null +++ b/biliup/plugins/huya_wup/wup_struct/WebSocketCommand.py @@ -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) diff --git a/biliup/plugins/huya_wup/wup_struct/__init__.py b/biliup/plugins/huya_wup/wup_struct/__init__.py new file mode 100644 index 0000000..da33175 --- /dev/null +++ b/biliup/plugins/huya_wup/wup_struct/__init__.py @@ -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