From f0007db68e715b0734f6e66f194d0fc8a4acb08d Mon Sep 17 00:00:00 2001 From: ihmily <114978440+ihmily@users.noreply.github.com> Date: Mon, 28 Oct 2024 00:36:10 +0800 Subject: [PATCH] feat: add live room title --- config/config.ini | 4 +- demo.py | 193 +++++++++++++++++++++ douyinliverecorder/spider.py | 319 ++++++++++++++++++++++++----------- douyinliverecorder/stream.py | 17 +- main.py | 100 ++++++----- 5 files changed, 484 insertions(+), 149 deletions(-) create mode 100644 demo.py diff --git a/config/config.ini b/config/config.ini index 0231ee2..e1db4ba 100644 --- a/config/config.ini +++ b/config/config.ini @@ -3,6 +3,8 @@ 直播保存路径(不填则默认) = 保存文件夹是否以作者区分 = 是 保存文件夹是否以时间区分 = 否 +保存文件夹是否以标题区分 = 否 +保存文件名是否包含标题 = 否 视频保存格式ts|mkv|flv|mp4|mp3音频|m4a音频 = ts 原画|超清|高清|标清|流畅 = 原画 是否使用代理ip(是/否) = 是 @@ -19,7 +21,7 @@ ts录制完成后自动转为mp4格式 = 是 生成时间字幕文件 = 否 是否录制完成后执行bash脚本 = 否 bash脚本路径 = -使用代理录制的平台(逗号分隔) = tiktok, afreecatv, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk +使用代理录制的平台(逗号分隔) = tiktok, afreecatv, soop, pandalive, winktv, flextv, popkontv, twitch, liveme, showroom, chzzk 额外使用代理录制的平台(逗号分隔) = [推送配置] diff --git a/demo.py b/demo.py new file mode 100644 index 0000000..186d009 --- /dev/null +++ b/demo.py @@ -0,0 +1,193 @@ +# -*- coding: utf-8 -*- +from douyinliverecorder.logger import logger +from douyinliverecorder import spider + +# 以下示例直播间链接不保证时效性,请自行查看链接是否能正常访问 +LIVE_STREAM_CONFIG = { + "douyin": { + "url": "https://live.douyin.com/745964462470", + "func": spider.get_douyin_app_stream_data, + }, + "tiktok": { + "url": "https://www.tiktok.com/@pearlgaga88/live", + "func": spider.get_tiktok_stream_data, + }, + "kuaishou": { + "url": "https://live.kuaishou.com/u/yall1102", + "func": spider.get_kuaishou_stream_data, + }, + "huya": { + "url": "https://www.huya.com/116", + "func": spider.get_huya_app_stream_url, + }, + "douyu": { + "url": "https://www.douyu.com/topic/wzDBLS6?rid=4921614&dyshid=", + "func": spider.get_douyu_info_data, + }, + "yy": { + "url": "https://www.yy.com/22490906/22490906", + "func": spider.get_yy_stream_data, + }, + "bilibili": { + "url": "https://live.bilibili.com/21593109", + "func": spider.get_bilibili_stream_data, + }, + "xhs": { + "url": "http://xhslink.com/O9f9fM", + "func": spider.get_xhs_stream_url, + }, + "bigo": { + "url": "https://www.bigo.tv/cn/716418802", + "func": spider.get_bigo_stream_url, + }, + "blued": { + "url": "https://app.blued.cn/live?id=Mp6G2R", + "func": spider.get_blued_stream_url, + }, + "afreecatv": { + "url": "https://play.afreecatv.com/sw7love", + "func": spider.get_afreecatv_stream_data, + }, + "soop": { + "url": "https://play.sooplive.co.kr/sw7love", + "func": spider.get_afreecatv_stream_data, + }, + "netease": { + "url": "https://cc.163.com/583946984", + "func": spider.get_netease_stream_data, + }, + "qiandurebo": { + "url": "https://qiandurebo.com/web/video.php?roomnumber=33333", + "func": spider.get_qiandurebo_stream_data, + }, + "pandatv": { + "url": "https://www.pandalive.co.kr/live/play/bara0109", + "func": spider.get_pandatv_stream_data, + }, + "maoerfm": { + "url": "https://fm.missevan.com/live/868895007", + "func": spider.get_maoerfm_stream_url, + }, + "winktv": { + "url": "https://www.winktv.co.kr/live/play/anjer1004", + "func": spider.get_winktv_stream_data, + }, + "flextv": { + "url": "https://www.flextv.co.kr/channels/593127/live", + "func": spider.get_flextv_stream_data, + }, + "looklive": { + "url": "https://look.163.com/live?id=65108820&position=3", + "func": spider.get_looklive_stream_url, + }, + "popkontv": { + "url": "https://www.popkontv.com/live/view?castId=wjfal007&partnerCode=P-00117", + "func": spider.get_popkontv_stream_url, + }, + "twitcasting": { + "url": "https://twitcasting.tv/c:uonq", + "func": spider.get_twitcasting_stream_url, + }, + "baidu": { + "url": "https://live.baidu.com/m/media/pclive/pchome/live.html?room_id=9175031377&tab_category", + "func": spider.get_baidu_stream_data, + }, + "weibo": { + "url": "https://weibo.com/u/7849520225", + "func": spider.get_weibo_stream_data, + }, + "kugou": { + "url": "https://fanxing2.kugou.com/50428671?refer=2177&sourceFrom=", + "func": spider.get_kugou_stream_url, + }, + "twitchtv": { + "url": "https://www.twitch.tv/gamerbee", + "func": spider.get_twitchtv_stream_data, + }, + "liveme": { + "url": "https://www.liveme.com/zh/v/17141937295821012854/index.html", + "func": spider.get_liveme_stream_url, + }, + "huajiao": { + "url": "https://www.huajiao.com/user/207446325", + "func": spider.get_huajiao_stream_url, + }, + "showroom": { + "url": "https://www.showroom-live.com/room/profile?room_id=511033", + "func": spider.get_showroom_stream_data, + }, + "acfun": { + "url": "https://live.acfun.cn/live/17912421", + "func": spider.get_acfun_stream_data, + }, + "changliao": { + "url": "https://www.tlclw.com/801044397", + "func": spider.get_changliao_stream_url, + }, + "yingke": { + "url": "https://www.inke.cn/liveroom/index.html?uid=710032101&id=1720857535354099", + "func": spider.get_yingke_stream_url, + }, + "yinbo": { + "url": "https://live.ybw1666.com/800008687", + "func": spider.get_yinbo_stream_url, + }, + "zhihu": { + "url": "https://www.zhihu.com/theater/114453", + "func": spider.get_zhihu_stream_url, + }, + "chzzk": { + "url": "https://chzzk.naver.com/live/458f6ec20b034f49e0fc6d03921646d2", + "func": spider.get_chzzk_stream_data, + }, + "haixiu": { + "url": "https://www.haixiutv.com/6095106", + "func": spider.get_haixiu_stream_url, + }, + "vvxqiu": { + "url": "https://h5webcdn-pro.vvxqiu.com//activity/videoShare/videoShare.html?h5Server=https://h5p.vvxqiu.com" + "&roomId=LP115924473&platformId=vvstar", + "func": spider.get_vvxqiu_stream_url, + }, + "17live": { + "url": "https://17.live/en/live/6302408", + "func": spider.get_17live_stream_url, + }, + "langlive": { + "url": "https://www.lang.live/en-US/room/3349463", + "func": spider.get_langlive_stream_url, + }, + "pplive": { + "url": "https://m.pp.weimipopo.com/live/preview.html?uid=91648673&anchorUid=91625862&app=plpl", + "func": spider.get_pplive_stream_url, + }, + "6room": { + "url": "https://v.6.cn/634435", + "func": spider.get_6room_stream_url, + }, + "lehai": { + "url": "https://www.lehaitv.com/8059096", + "func": spider.get_haixiu_stream_url, + }, + "huamao": { + "url": "https://h.catshow168.com/live/preview.html?uid=19066357&anchorUid=18895331", + "func": spider.get_pplive_stream_url, + } +} + + +def test_live_stream(platform_name: str, proxy_addr=None) -> None: + if platform_name in LIVE_STREAM_CONFIG: + config = LIVE_STREAM_CONFIG[platform_name] + try: + stream_data = config['func'](config['url'], proxy_addr=proxy_addr) + logger.debug(f"Stream data for {platform_name}: {stream_data}") + except Exception as e: + logger.error(f"Error fetching stream data for {platform_name}: {e}") + else: + logger.warning(f"No configuration found for platform: {platform_name}") + + +if __name__ == "__main__": + platform = "douyin" + test_live_stream(platform) diff --git a/douyinliverecorder/spider.py b/douyinliverecorder/spider.py index a1f2a7a..430a3d0 100644 --- a/douyinliverecorder/spider.py +++ b/douyinliverecorder/spider.py @@ -4,7 +4,7 @@ Author: Hmily GitHub: https://github.com/ihmily Date: 2023-07-15 23:15:00 -Update: 2024-10-16 23:28:12 +Update: 2024-10-28 00:32:16 Copyright (c) 2023-2024 by Hmily, All Rights Reserved. Function: Get live stream data. """ @@ -32,7 +32,6 @@ from .logger import script_path from .room import get_sec_user_id, get_unique_id from . import JS_SCRIPT_PATH - no_proxy_handler = urllib.request.ProxyHandler({}) opener = urllib.request.build_opener(no_proxy_handler) @@ -176,7 +175,16 @@ def get_douyin_app_stream_data(url: str, proxy_addr: Union[str, None] = None, co headers['Cookie'] = cookies def get_app_data(room_id, sec_uid): - api2 = f'https://webcast.amemv.com/webcast/room/reflow/info/?verifyFp=verify_lxj5zv70_7szNlAB7_pxNY_48Vh_ALKF_GA1Uf3yteoOY&type_id=0&live_id=1&room_id={room_id}&sec_user_id={sec_uid}&version_code=99.99.99&app_id=1128' + app_params = { + "verifyFp": "verify_lxj5zv70_7szNlAB7_pxNY_48Vh_ALKF_GA1Uf3yteoOY", + "type_id": "0", + "live_id": "1", + "room_id": room_id, + "sec_user_id": sec_uid, + "version_code": "99.99.99", + "app_id": "1128" + } + api2 = f'https://webcast.amemv.com/webcast/room/reflow/info/?{urllib.parse.urlencode(app_params)}' json_str2 = get_req(url=api2, proxy_addr=proxy_addr, headers=headers) json_data2 = json.loads(json_str2)['data'] room_data2 = json_data2['room'] @@ -187,7 +195,20 @@ def get_douyin_app_stream_data(url: str, proxy_addr: Union[str, None] = None, co web_rid = url.split('?')[0].split('live.douyin.com/') if len(web_rid) > 1: web_rid = web_rid[1] - api = f'https://live.douyin.com/webcast/room/web/enter/?aid=6383&app_name=douyin_web&live_id=1&device_platform=web&language=zh-CN&browser_language=zh-CN&browser_platform=Win32&browser_name=Chrome&browser_version=116.0.0.0&web_rid={web_rid}' + params = { + "aid": "6383", + "app_name": "douyin_web", + "live_id": "1", + "device_platform": "web", + "language": "zh-CN", + "browser_language": "zh-CN", + "browser_platform": "Win32", + "browser_name": "Chrome", + "browser_version": "116.0.0.0", + "web_rid": web_rid + + } + api = f'https://live.douyin.com/webcast/room/web/enter/?{urllib.parse.urlencode(params)}' json_str = get_req(url=api, proxy_addr=proxy_addr, headers=headers) json_data = json.loads(json_str)['data'] room_data = json_data['data'][0] @@ -288,7 +309,7 @@ def get_douyin_stream_data(url: str, proxy_addr: Union[str, None] = None, cookie def get_tiktok_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.79', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', 'Cookie': 'ttwid=1%7CM-rF193sJugKuNz2RGNt-rh6pAAR9IMceUSzlDnPCNI%7C1683274418%7Cf726d4947f2fc37fecc7aeb0cdaee52892244d04efde6f8a8edd2bb168263269; tiktok_webapp_theme=light; tt_chain_token=VWkygAWDlm1cFg/k8whmOg==; passport_csrf_token=6e422c5a7991f8cec7033a8082921510; passport_csrf_token_default=6e422c5a7991f8cec7033a8082921510; d_ticket=f8c267d4af4523c97be1ccb355e9991e2ae06; odin_tt=320b5f386cdc23f347be018e588873db7f7aea4ea5d1813681c3fbc018ea025dde957b94f74146dbc0e3612426b865ccb95ec8abe4ee36cca65f15dbffec0deff7b0e69e8ea536d46e0f82a4fc37d211; cmpl_token=AgQQAPNSF-RO0rT04baWtZ0T_jUjl4fVP4PZYM2QPw; uid_tt=319b558dbba684bb1557206c92089cd113a875526a89aee30595925d804b81c7; uid_tt_ss=319b558dbba684bb1557206c92089cd113a875526a89aee30595925d804b81c7; sid_tt=ad5e736f4bedb2f6d42ccd849e706b1d; sessionid=ad5e736f4bedb2f6d42ccd849e706b1d; sessionid_ss=ad5e736f4bedb2f6d42ccd849e706b1d; store-idc=useast5; store-country-code=us; store-country-code-src=uid; tt-target-idc=useast5; tt-target-idc-sign=qXNk0bb1pDQ0FbCNF120Pl9WWMLZg9Edv5PkfyCbS4lIk5ieW5tfLP7XWROnN0mEaSlc5hg6Oji1pF-yz_3ZXnUiNMrA9wNMPvI6D9IFKKVmq555aQzwPIGHv0aQC5dNRgKo5Z5LBkgxUMWEojTKclq2_L8lBciw0IGdhFm_XyVJtbqbBKKgybGDLzK8ZyxF4Jl_cYRXaDlshZjc38JdS6wruDueRSHe7YvNbjxCnApEFUv-OwJANSPU_4rvcqpVhq3JI2VCCfw-cs_4MFIPCDOKisk5EhAo2JlHh3VF7_CLuv80FXg_7ZqQ2pJeMOog294rqxwbbQhl3ATvjQV_JsWyUsMd9zwqecpylrPvtySI2u1qfoggx1owLrrUynee1R48QlanLQnTNW_z1WpmZBgVJqgEGLwFoVOmRzJuFFNj8vIqdjM2nDSdWqX8_wX3wplohkzkPSFPfZgjzGnQX28krhgTytLt7BXYty5dpfGtsdb11WOFHM6MZ9R9uLVB; sid_guard=ad5e736f4bedb2f6d42ccd849e706b1d%7C1690990657%7C15525213%7CMon%2C+29-Jan-2024+08%3A11%3A10+GMT; sid_ucp_v1=1.0.0-KGM3YzgwYjZhODgyYWI1NjIwNTA0NjBmOWUxMGRhMjIzYTI2YjMxNDUKGAiqiJ30keKD5WQQwfCppgYYsws4AkDsBxAEGgd1c2Vhc3Q1IiBhZDVlNzM2ZjRiZWRiMmY2ZDQyY2NkODQ5ZTcwNmIxZA; ssid_ucp_v1=1.0.0-KGM3YzgwYjZhODgyYWI1NjIwNTA0NjBmOWUxMGRhMjIzYTI2YjMxNDUKGAiqiJ30keKD5WQQwfCppgYYsws4AkDsBxAEGgd1c2Vhc3Q1IiBhZDVlNzM2ZjRiZWRiMmY2ZDQyY2NkODQ5ZTcwNmIxZA; tt_csrf_token=dD0EIH8q-pe3qDQsCyyD1jLN6KizJDRjOEyk; __tea_cache_tokens_1988={%22_type_%22:%22default%22%2C%22user_unique_id%22:%227229608516049831425%22%2C%22timestamp%22:1683274422659}; ttwid=1%7CM-rF193sJugKuNz2RGNt-rh6pAAR9IMceUSzlDnPCNI%7C1694002151%7Cd89b77afc809b1a610661a9d1c2784d80ebef9efdd166f06de0d28e27f7e4efe; msToken=KfJAVZ7r9D_QVeQlYAUZzDFbc1Yx-nZz6GF33eOxgd8KlqvTg1lF9bMXW7gFV-qW4MCgUwnBIhbiwU9kdaSpgHJCk-PABsHCtTO5J3qC4oCTsrXQ1_E0XtbqiE4OVLZ_jdF1EYWgKNPT2SnwGkQ=; msToken=KfJAVZ7r9D_QVeQlYAUZzDFbc1Yx-nZz6GF33eOxgd8KlqvTg1lF9bMXW7gFV-qW4MCgUwnBIhbiwU9kdaSpgHJCk-PABsHCtTO5J3qC4oCTsrXQ1_E0XtbqiE4OVLZ_jdF1EYWgKNPT2SnwGkQ=' } if cookies: @@ -337,7 +358,7 @@ def get_kuaishou_stream_data(url: str, proxy_addr: Union[str, None] = None, cook result = {"type": 2, "is_live": False} if 'errorType' in play_list or 'liveStream' not in play_list: - error_msg = play_list.get('errorType', {}).get('title', '') + play_list.get('errorType', {}).get('content', '') + error_msg = play_list['errorType']['title'] + play_list['errorType']['content'] print(f'失败地址:{url} 错误信息: {error_msg}') return result @@ -359,7 +380,7 @@ def get_kuaishou_stream_data(url: str, proxy_addr: Union[str, None] = None, cook def get_kuaishou_stream_data2(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { - 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Referer': "https://www.kuaishou.com/short-video/3x224rwabjmuc9y?fid=1712760877&cc=share_copylink&followRefer=151&shareMethod=TOKEN&docId=9&kpn=KUAISHOU&subBiz=BROWSE_SLIDE_PHOTO&photoId=3x224rwabjmuc9y&shareId=17144298796566&shareToken=X-6FTMeYTsY97qYL&shareResourceType=PHOTO_OTHER&userId=3xtnuitaz2982eg&shareType=1&et=1_i/2000048330179867715_h3052&shareMode=APP&originShareId=17144298796566&appType=21&shareObjectId=5230086626478274600&shareUrlOpened=0×tamp=1663833792288&utm_source=app_share&utm_medium=app_share&utm_campaign=app_share&location=app_share", 'content-type': 'application/json', @@ -421,7 +442,7 @@ def get_huya_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies: def get_huya_app_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { - 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.49(0x18003137) NetType/WIFI Language/zh_CN WeChat/8.0.49.33 CFNetwork/1474 Darwin/23.0.0', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', 'xweb_xhr': '1', 'referer': 'https://servicewechat.com/wx74767bf0b684f7d3/301/page-frame.html', 'accept-language': 'zh-CN,zh;q=0.9', @@ -450,6 +471,7 @@ def get_huya_app_stream_url(url: str, proxy_addr: Union[str, None] = None, cooki json_data = json.loads(json_str) anchor_name = json_data['data']['profileInfo']['nick'] live_status = json_data['data']['realLiveStatus'] + live_title = json_data['data']['liveData']['introduction'] if live_status != 'ON': return {'anchor_name': anchor_name, 'is_live': False} else: @@ -483,6 +505,7 @@ def get_huya_app_stream_url(url: str, proxy_addr: Union[str, None] = None, cooki 'm3u8_url': play_url_list[0]['m3u8_url'], 'flv_url': play_url_list[0]['flv_url'], 'record_url': record_url, + 'title': live_title } @@ -491,14 +514,7 @@ def md5(data): def get_token_js(rid: str, did: str, proxy_addr: Union[str, None] = None) -> Union[list, Dict[str, Any]]: - """ - 通过PC网页端的接口获取完整直播源。 - :param proxy_addr: - :param did: - :param rid: - :param cdn: 主线路ws-h5、备用线路tct-h5 - :param rate: 1流畅;2高清;3超清;4蓝光4M;0蓝光8M或10M - """ + url = f'https://www.douyu.com/{rid}' html_str = get_req(url=url, proxy_addr=proxy_addr) result = re.search(r'(vdwdae325w_64we[\s\S]*function ub98484234[\s\S]*?)function', html_str).group(1) @@ -524,7 +540,7 @@ def get_token_js(rid: str, did: str, proxy_addr: Union[str, None] = None) -> Uni def get_douyu_info_data(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> Dict[ str, Any]: headers = { - 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', 'Referer': 'https://m.douyu.com/3125893?rid=3125893&dyshid=0-96003918aa5365bc6dcb4933000316p1&dyshci=181', 'Cookie': 'dy_did=413b835d2ae00270f0c69f6400031601; acf_did=413b835d2ae00270f0c69f6400031601; Hm_lvt_e99aee90ec1b2106afe7ec3b199020a7=1692068308,1694003758; m_did=96003918aa5365bc6dcb4933000316p1; dy_teen_mode=%7B%22uid%22%3A%22472647365%22%2C%22status%22%3A0%2C%22birthday%22%3A%22%22%2C%22password%22%3A%22%22%7D; PHPSESSID=td59qi2fu2gepngb8mlehbeme3; acf_auth=94fc9s%2FeNj%2BKlpU%2Br8tZC3Jo9sZ0wz9ClcHQ1akL2Nhb6ZyCmfjVWSlR3LFFPuePWHRAMo0dt9vPSCoezkFPOeNy4mYcdVOM1a8CbW0ZAee4ipyNB%2Bflr58; dy_auth=bec5yzM8bUFYe%2FnVAjmUAljyrsX%2FcwRW%2FyMHaoArYb5qi8FS9tWR%2B96iCzSnmAryLOjB3Qbeu%2BBD42clnI7CR9vNAo9mva5HyyL41HGsbksx1tEYFOEwxSI; wan_auth37wan=5fd69ed5b27fGM%2FGoswWwDo%2BL%2FRMtnEa4Ix9a%2FsH26qF0sR4iddKMqfnPIhgfHZUqkAk%2FA1d8TX%2B6F7SNp7l6buIxAVf3t9YxmSso8bvHY0%2Fa6RUiv8; acf_uid=472647365; acf_username=472647365; acf_nickname=%E7%94%A8%E6%88%B776576662; acf_own_room=0; acf_groupid=1; acf_phonestatus=1; acf_avatar=https%3A%2F%2Fapic.douyucdn.cn%2Fupload%2Favatar%2Fdefault%2F24_; acf_ct=0; acf_ltkid=25305099; acf_biz=1; acf_stk=90754f8ed18f0c24; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=1694003778' } @@ -542,7 +558,7 @@ def get_douyu_info_data(url: str, proxy_addr: Union[str, None] = None, cookies: rid = json_data['pageProps']['room']['roomInfo']['roomInfo']['rid'] headers[ - 'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0' + 'User-Agent'] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0' url2 = f'https://www.douyu.com/betard/{rid}' json_str = get_req(url=url2, proxy_addr=proxy_addr, headers=headers) json_data = json.loads(json_str) @@ -551,9 +567,9 @@ def get_douyu_info_data(url: str, proxy_addr: Union[str, None] = None, cookies: "is_live": False } if json_data['room']['videoLoop'] == 0 and json_data['room']['show_status'] == 1: + result["title"] = json_data['room']['room_name'].replace(' ', '') result["is_live"] = True result["room_id"] = json_data['room']['room_id'] - return result @@ -563,7 +579,7 @@ def get_douyu_stream_data(rid: str, rate: str = '-1', proxy_addr: Union[str, Non did = '10000000000000000000000000003306' params_list = get_token_js(rid, did, proxy_addr=proxy_addr) headers = { - 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', 'Referer': 'https://m.douyu.com/3125893?rid=3125893&dyshid=0-96003918aa5365bc6dcb4933000316p1&dyshci=181', 'Cookie': 'dy_did=413b835d2ae00270f0c69f6400031601; acf_did=413b835d2ae00270f0c69f6400031601; Hm_lvt_e99aee90ec1b2106afe7ec3b199020a7=1692068308,1694003758; m_did=96003918aa5365bc6dcb4933000316p1; dy_teen_mode=%7B%22uid%22%3A%22472647365%22%2C%22status%22%3A0%2C%22birthday%22%3A%22%22%2C%22password%22%3A%22%22%7D; PHPSESSID=td59qi2fu2gepngb8mlehbeme3; acf_auth=94fc9s%2FeNj%2BKlpU%2Br8tZC3Jo9sZ0wz9ClcHQ1akL2Nhb6ZyCmfjVWSlR3LFFPuePWHRAMo0dt9vPSCoezkFPOeNy4mYcdVOM1a8CbW0ZAee4ipyNB%2Bflr58; dy_auth=bec5yzM8bUFYe%2FnVAjmUAljyrsX%2FcwRW%2FyMHaoArYb5qi8FS9tWR%2B96iCzSnmAryLOjB3Qbeu%2BBD42clnI7CR9vNAo9mva5HyyL41HGsbksx1tEYFOEwxSI; wan_auth37wan=5fd69ed5b27fGM%2FGoswWwDo%2BL%2FRMtnEa4Ix9a%2FsH26qF0sR4iddKMqfnPIhgfHZUqkAk%2FA1d8TX%2B6F7SNp7l6buIxAVf3t9YxmSso8bvHY0%2Fa6RUiv8; acf_uid=472647365; acf_username=472647365; acf_nickname=%E7%94%A8%E6%88%B776576662; acf_own_room=0; acf_groupid=1; acf_phonestatus=1; acf_avatar=https%3A%2F%2Fapic.douyucdn.cn%2Fupload%2Favatar%2Fdefault%2F24_; acf_ct=0; acf_ltkid=25305099; acf_biz=1; acf_stk=90754f8ed18f0c24; Hm_lpvt_e99aee90ec1b2106afe7ec3b199020a7=1694003778' } @@ -605,13 +621,51 @@ def get_yy_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies: U data = '{"head":{"seq":1701869217590,"appidstr":"0","bidstr":"121","cidstr":"' + cid + '","sidstr":"' + cid + '","uid64":0,"client_type":108,"client_ver":"5.17.0","stream_sys_ver":1,"app":"yylive_web","playersdk_ver":"5.17.0","thundersdk_ver":"0","streamsdk_ver":"5.17.0"},"client_attribute":{"client":"web","model":"web0","cpu":"","graphics_card":"","os":"chrome","osversion":"0","vsdk_version":"","app_identify":"","app_version":"","business":"","width":"1920","height":"1080","scale":"","client_type":8,"h265":0},"avp_parameter":{"version":1,"client_type":8,"service_type":0,"imsi":0,"send_time":1701869217,"line_seq":-1,"gear":4,"ssl":1,"stream_format":0}}' data_bytes = data.encode('utf-8') - url2 = f'https://stream-manager.yy.com/v3/channel/streams?uid=0&cid={cid}&sid={cid}&appid=0&sequence=1701869217590&encode=json' + params = { + "uid": "0", + "cid": cid, + "sid": cid, + "appid": "0", + "sequence": "1701869217590", + "encode": "json" + } + url2 = f'https://stream-manager.yy.com/v3/channel/streams?{urllib.parse.urlencode(params)}' json_str = get_req(url=url2, data=data_bytes, proxy_addr=proxy_addr, headers=headers) json_data = json.loads(json_str) json_data['anchor_name'] = anchor_name + + params = { + 'uid': '', + 'sid': cid, + 'ssid': cid, + '_': int(time.time() * 1000), + } + detail_api = f'https://www.yy.com/live/detail?{urllib.parse.urlencode(params)}' + json_str2 = get_req(detail_api, proxy_addr=proxy_addr, headers=headers) + json_data2 = json.loads(json_str2) + json_data['title'] = json_data2['data']['roomName'] return json_data +@trace_error_decorator +def get_bilibili_room_info_h5(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ + str: + headers = { + 'origin': 'https://live.bilibili.com', + 'referer': 'https://live.bilibili.com/', + 'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', + } + if cookies: + headers['Cookie'] = cookies + + room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] + api = f'https://api.live.bilibili.com/xlive/web-room/v1/index/getH5InfoByRoom?room_id={room_id}' + json_str = get_req(api, proxy_addr=proxy_addr, headers=headers) + room_info = json.loads(json_str) + title = room_info['data']['room_info']['title'] + return title + + @trace_error_decorator def get_bilibili_room_info(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: @@ -635,7 +689,9 @@ def get_bilibili_room_info(url: str, proxy_addr: Union[str, None] = None, cookie json_str2 = get_req(url=api, proxy_addr=proxy_addr, headers=headers) anchor_info = json.loads(json_str2) anchor_name = anchor_info['data']['info']['uname'] - return {"anchor_name": anchor_name, "live_status": live_status, "room_url": url} + + title = get_bilibili_room_info_h5(url, proxy_addr, cookies) + return {"anchor_name": anchor_name, "live_status": live_status, "room_url": url, "title": title} except Exception as e: print(e) return {"anchor_name": '', "live_status": False, "room_url": url} @@ -730,12 +786,14 @@ def get_xhs_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: U json_str = get_req(api, proxy_addr=proxy_addr, headers=headers) json_data = json.loads(json_str) anchor_name = json_data['data']['host_info']['nickname'] + live_title = json_data['data']['room']['name'] live_status = json_data['data']['room']['status'] result["anchor_name"] = anchor_name # 这个判断不准确,无论是否在直播status都为0 if live_status == 0: result['is_live'] = True + result['title'] = live_title flv_url = f'http://live-play.xhscdn.com/live/{room_id}.flv' result['flv_url'] = flv_url result['record_url'] = flv_url @@ -801,6 +859,8 @@ def get_bigo_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: } if live_status == 1: + live_title = json_data['data']['roomTopic'] + result['title'] = live_title m3u8_url = json_data['data']['hls_src'] result['m3u8_url'] = m3u8_url result['is_live'] = True @@ -816,7 +876,7 @@ def get_bigo_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: def get_blued_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { - 'User-Agent': 'Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', } @@ -1013,26 +1073,26 @@ def get_afreecatv_stream_data( play_url_list.append(url_prefix + i.strip()) bandwidth_pattern = re.compile(r'BANDWIDTH=(\d+)') bandwidth_list = bandwidth_pattern.findall(resp) - url_to_bandwidth = {url: int(bandwidth) for bandwidth, url in zip(bandwidth_list, play_url_list)} - play_url_list = sorted(play_url_list, key=lambda url: url_to_bandwidth[url], reverse=True) + url_to_bandwidth = {purl: int(bandwidth) for bandwidth, purl in zip(bandwidth_list, play_url_list)} + play_url_list = sorted(play_url_list, key=lambda purl: url_to_bandwidth[purl], reverse=True) return play_url_list if not anchor_name: def handle_login(): cookie = login_afreecatv(username, password, proxy_addr=proxy_addr) - if 'PdboxBbs=' in cookie: + if 'AuthTicket=' in cookie: print('SOOP[AfreecaTV]平台登录成功!开始获取直播数据...') return cookie def fetch_data(cookie): aid_token = get_afreecatv_tk(url, rtype='aid', proxy_addr=proxy_addr, cookies=cookie) - anchor_name, broad_no = get_afreecatv_tk(url, rtype='info', proxy_addr=proxy_addr, cookies=cookie) - view_url = get_afreecatv_cdn_url(broad_no, proxy_addr=proxy_addr)['view_url'] - m3u8_url = view_url + '?aid=' + aid_token - result['anchor_name'] = anchor_name - result['m3u8_url'] = m3u8_url + _anchor_name, _broad_no = get_afreecatv_tk(url, rtype='info', proxy_addr=proxy_addr, cookies=cookie) + _view_url = get_afreecatv_cdn_url(_broad_no, proxy_addr=proxy_addr)['view_url'] + _m3u8_url = _view_url + '?aid=' + aid_token + result['anchor_name'] = _anchor_name + result['m3u8_url'] = _m3u8_url result['is_live'] = True - result['play_url_list'] = get_url_list(m3u8_url) + result['play_url_list'] = get_url_list(_m3u8_url) result['new_cookies'] = cookie return result @@ -1076,7 +1136,7 @@ def get_netease_stream_data(url: str, proxy_addr: Union[str, None] = None, cooki 'accept': 'application/json, text/plain, */*', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'referer': 'https://cc.163.com/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.58', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if cookies: headers['Cookie'] = cookies @@ -1093,8 +1153,9 @@ def get_netease_stream_data(url: str, proxy_addr: Union[str, None] = None, cooki result["anchor_name"] = room_data['nickname'] else: result["anchor_name"] = live_data['nickname'] - result["stream_list"] = live_data['quickplay'] result["is_live"] = True + result["title"] = live_data['title'] + result["stream_list"] = live_data['quickplay'] return result @@ -1102,10 +1163,11 @@ def get_netease_stream_data(url: str, proxy_addr: Union[str, None] = None, cooki def get_qiandurebo_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { - 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,' + 'application/signed-exchange;v=b3;q=0.7', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'referer': 'https://qiandurebo.com/web/index.php', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.58', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if cookies: headers['Cookie'] = cookies @@ -1133,7 +1195,7 @@ def get_pandatv_stream_data(url: str, proxy_addr: Union[str, None] = None, cooki headers = { 'origin': 'https://www.pandalive.co.kr', 'referer': 'https://www.pandalive.co.kr/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.58', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if cookies: headers['Cookie'] = cookies @@ -1186,8 +1248,7 @@ def get_maoerfm_stream_url(url: str, proxy_addr: Union[str, None] = None, cookie 'accept': 'application/json, text/plain, */*', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'referer': 'https://fm.missevan.com/live/868895007', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' - 'Chrome/114.0.0.0 Safari/537.36 Edg/114.0.1823.58', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if cookies: headers['Cookie'] = cookies @@ -1211,6 +1272,7 @@ def get_maoerfm_stream_url(url: str, proxy_addr: Union[str, None] = None, cookie stream_list = json_data['info']['room']['channel'] m3u8_url = stream_list['hls_pull_url'] flv_url = stream_list['flv_pull_url'] + result['title'] = json_data['info']['room']['name'] result['m3u8_url'] = m3u8_url result['flv_url'] = flv_url result['is_live'] = True @@ -1226,7 +1288,7 @@ def get_winktv_bj_info(url: str, proxy_addr: Union[str, None] = None, cookies: U 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'content-type': 'application/x-www-form-urlencoded', 'referer': 'https://www.winktv.co.kr/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if cookies: headers['Cookie'] = cookies @@ -1254,7 +1316,7 @@ def get_winktv_stream_data(url: str, proxy_addr: Union[str, None] = None, cookie 'content-type': 'application/x-www-form-urlencoded', 'referer': 'https://www.winktv.co.kr', 'origin': 'https://www.winktv.co.kr', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if cookies: @@ -1297,7 +1359,7 @@ def login_flextv(username: str, password: str, proxy_addr: Union[str, None] = No 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'content-type': 'application/json;charset=UTF-8', 'referer': 'https://www.flextv.co.kr/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } data = { @@ -1347,7 +1409,7 @@ def get_flextv_stream_url( 'accept': 'application/json, text/plain, */*', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'referer': 'https://www.flextv.co.kr/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } user_id = url.split('/live')[0].rsplit('/', maxsplit=1)[-1] if cookie: @@ -1387,7 +1449,7 @@ def get_flextv_stream_data( 'accept': 'application/json, text/plain, */*', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'referer': 'https://www.flextv.co.kr/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if cookies: headers['Cookie'] = cookies @@ -1436,23 +1498,23 @@ def get_looklive_secret_data(text): charset = '1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+-=[]{}|;:,.<>?' return ''.join(secrets.choice(charset) for _ in range(size)).encode('utf-8') - def aes_encrypt(text: Union[str, bytes], seckey: Union[str, bytes]) -> bytes: - if isinstance(text, str): - text = text.encode('utf-8') - if isinstance(seckey, str): - seckey = seckey.encode('utf-8') - seckey = seckey[:16] # 16 (AES-128), 24 (AES-192), or 32 (AES-256) bytes + def aes_encrypt(_text: Union[str, bytes], _sec_key: Union[str, bytes]) -> bytes: + if isinstance(_text, str): + _text = _text.encode('utf-8') + if isinstance(_sec_key, str): + _sec_key = _sec_key.encode('utf-8') + _sec_key = _sec_key[:16] # 16 (AES-128), 24 (AES-192), or 32 (AES-256) bytes iv = bytes('0102030405060708', 'utf-8') - encryptor = AES.new(seckey, AES.MODE_CBC, iv) - padded_text = pad(text, AES.block_size) + encryptor = AES.new(_sec_key, AES.MODE_CBC, iv) + padded_text = pad(_text, AES.block_size) ciphertext = encryptor.encrypt(padded_text) encoded_ciphertext = base64.b64encode(ciphertext) return encoded_ciphertext - def rsa_encrypt(text: Union[str, bytes], pub_key: str, mod: str) -> str: - if isinstance(text, str): - text = text.encode('utf-8') - text_reversed = text[::-1] + def rsa_encrypt(_text: Union[str, bytes], pub_key: str, mod: str) -> str: + if isinstance(_text, str): + _text = _text.encode('utf-8') + text_reversed = _text[::-1] text_int = int(binascii.hexlify(text_reversed), 16) encrypted_int = pow(text_int, int(pub_key, 16), int(mod, 16)) return format(encrypted_int, 'x').zfill(256) @@ -1498,6 +1560,8 @@ def get_looklive_stream_url( print('Look直播暂时只支持音频直播,不支持Look视频直播!') else: play_url_list = json_data['data']['roomInfo']['liveUrl'] + live_title = json_data['data']['roomInfo']['title'] + result["title"] = live_title result["flv_url"] = play_url_list['httpPullUrl'] result["m3u8_url"] = play_url_list['hlsPullUrl'] result["record_url"] = play_url_list['hlsPullUrl'] @@ -1514,7 +1578,7 @@ def login_popkontv( 'Authorization': 'Basic FpAhe6mh8Qtz116OENBmRddbYVirNKasktdXQiuHfm88zRaFydTsFy63tzkdZY0u', 'Content-Type': 'application/json', 'Origin': 'https://www.popkontv.com', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } data = { @@ -1562,7 +1626,7 @@ def get_popkontv_stream_data( 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'Content-Type': 'application/json', 'Origin': 'https://www.popkontv.com', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if cookies: headers['Cookie'] = cookies @@ -1632,7 +1696,7 @@ def get_popkontv_stream_url( 'ClientKey': 'Client FpAhe6mh8Qtz116OENBmRddbYVirNKasktdXQiuHfm88zRaFydTsFy63tzkdZY0u', 'Content-Type': 'application/json', 'Origin': 'https://www.popkontv.com', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if access_token: @@ -1719,7 +1783,7 @@ def login_twitcasting( 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': 'https://twitcasting.tv/indexcaslogin.php?redir=%2Findexloginwindow.php%3Fnext%3D%252F&keep=1', 'Cookie': 'hl=zh; did=04fb08f1b15d248644f1dfa82816d323; _ga=GA1.1.1021187740.1709706998; keep=1; mfadid=yrQiEB26ruRg7mlMavABMBZWdOddzojW; _ga_X8R46Y30YM=GS1.1.1709706998.1.1.1709712274.0.0.0', - 'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36 Edg/121.0.0.0', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', } if cookies: @@ -1778,7 +1842,7 @@ def get_twitcasting_stream_url( 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'Referer': 'https://twitcasting.tv/?ch0', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } anchor_id = url.split('/')[3] @@ -1789,9 +1853,10 @@ def get_twitcasting_stream_url( def get_data(header): html_str = get_req(url, proxy_addr=proxy_addr, headers=header) anchor = re.search("(.*?) \\(@(.*?)\\) 的直播 - Twit", html_str) + title = re.search('<meta name="twitter:title" content="(.*?)">\n\\s+<meta', html_str) status = re.search('data-is-onlive="(.*?)"\n\\s+data-view-mode', html_str) movie_id = re.search('data-movie-id="(.*?)" data-audience-id', html_str) - return f'{anchor.group(1).strip()}-{anchor.group(2)}-{movie_id.group(1)}', status.group(1) + return f'{anchor.group(1).strip()}-{anchor.group(2)}-{movie_id.group(1)}', status.group(1), title.group(1) result = {"anchor_name": '', "is_live": False} new_cookie = None @@ -1805,7 +1870,7 @@ def get_twitcasting_stream_url( raise RuntimeError('TwitCasting登录失败,请检查配置文件中的账号密码是否正确') print('TwitCasting 登录成功!开始获取数据...') headers['Cookie'] = new_cookie - anchor_name, live_status = get_data(headers) + anchor_name, live_status, live_title = get_data(headers) except AttributeError: print('获取TwitCasting数据失败,正在尝试登录...') new_cookie = login_twitcasting(account_type=account_type, username=username, password=password, @@ -1814,7 +1879,7 @@ def get_twitcasting_stream_url( raise RuntimeError('TwitCasting登录失败,请检查配置文件中的账号密码是否正确') print('TwitCasting 登录成功!开始获取数据...') headers['Cookie'] = new_cookie - anchor_name, live_status = get_data(headers) + anchor_name, live_status, live_title = get_data(headers) result["anchor_name"] = anchor_name if live_status == 'true': @@ -1822,6 +1887,7 @@ def get_twitcasting_stream_url( result['m3u8_url'] = play_url result['record_url'] = play_url result['is_live'] = True + result['title'] = live_title result['new_cookies'] = new_cookie return result @@ -1833,7 +1899,7 @@ def get_baidu_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'Connection': 'keep-alive', 'Referer': 'https://live.baidu.com/', - 'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36 Edg/121.0.0.0', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', } if cookies: headers['Cookie'] = cookies @@ -1849,16 +1915,20 @@ def get_baidu_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies 'action': 'star', 'service': 'bdbox', 'osname': 'baiduboxapp', - 'data': '{"data":{"room_id":"' + room_id + '","device_id":"h5-683e85bdf741bf2492586f7ca39bf465","source_type":0,"osname":"baiduboxapp"},"replay_slice":0,"nid":"","schemeParams":{"src_pre":"pc","src_suf":"other","bd_vid":"","share_uid":"","share_cuk":"","share_ecid":"","zb_tag":"","shareTaskInfo":"{\\"room_id\\":\\"9175031377\\"}","share_from":"","ext_params":"","nid":""}}', + 'data': '{"data":{"room_id":"' + room_id + '","device_id":"h5-683e85bdf741bf2492586f7ca39bf465",' + '"source_type":0,"osname":"baiduboxapp"},"replay_slice":0,' + '"nid":"","schemeParams":{"src_pre":"pc","src_suf":"other",' + '"bd_vid":"","share_uid":"","share_cuk":"","share_ecid":"",' + '"zb_tag":"","shareTaskInfo":"{\\"room_id\\":\\"9175031377\\"}",' + '"share_from":"","ext_params":"","nid":""}}', 'ua': '360_740_ANDROID_0', 'bd_vid': '', 'uid': uid, '_': str(int(time.time() * 1000)), - 'callback': '__jsonp_callback_1__', } app_api = f'https://mbd.baidu.com/searchbox?{urllib.parse.urlencode(params)}' - jsonp_str = get_req(url=app_api, proxy_addr=proxy_addr, headers=headers) - json_data = jsonp_to_json(jsonp_str) + json_str = get_req(url=app_api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) key = list(json_data['data'].keys())[0] data = json_data['data'][key] anchor_name = data['host']['name'] @@ -1866,9 +1936,9 @@ def get_baidu_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies "anchor_name": anchor_name, "is_live": False, } - live_status = data['video']['stream'] - if live_status == 1: + if data['status'] == "0": result["is_live"] = True + live_title = data['video']['title'] play_url_list = data['video']['url_clarity_list'] url_list = [] prefix = 'https://hls.liveshow.bdstatic.com/live/' @@ -1883,6 +1953,7 @@ def get_baidu_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies if url_list: result['play_url_list'] = url_list + result['title'] = live_title result['is_live'] = True return result @@ -1893,7 +1964,7 @@ def get_weibo_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies headers = { 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'Cookie': 'XSRF-TOKEN=qAP-pIY5V4tO6blNOhA4IIOD; SUB=_2AkMRNMCwf8NxqwFRmfwWymPrbI9-zgzEieKnaDFrJRMxHRl-yT9kqmkhtRB6OrTuX5z9N_7qk9C3xxEmNR-8WLcyo2PM; SUBP=0033WrSXqPxfM72-Ws9jqgMF55529P9D9WWemwcqkukCduUO11o9sBqA; WBPSESS=Wk6CxkYDejV3DDBcnx2LOXN9V1LjdSTNQPMbBDWe4lO2HbPmXG_coMffJ30T-Avn_ccQWtEYFcq9fab1p5RR6PEI6w661JcW7-56BszujMlaiAhLX-9vT4Zjboy1yf2l', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0', } if cookies: headers['Cookie'] = cookies @@ -1927,9 +1998,11 @@ def get_weibo_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies if live_status == 1: result["is_live"] = True + live_title = json_data['data']['item']['desc'] play_url_list = json_data['data']['item']['stream_info']['pull'] m3u8_url = play_url_list['live_origin_hls_url'] flv_url = play_url_list['live_origin_flv_url'] + result['title'] = live_title result['play_url_list'] = [ {"m3u8_url": m3u8_url, "flv_url": flv_url}, {"m3u8_url": m3u8_url.split('_')[0] + '.m3u8', "flv_url": flv_url.split('_')[0] + '.flv'} @@ -1980,9 +2053,9 @@ def get_kugou_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: 'appid': '1010', '_': str(int(time.time() * 1000)), } - stream_api = f'https://fx1.service.kugou.com/video/pc/live/pull/mutiline/streamaddr?{urllib.parse.urlencode(params)}' + api = f'https://fx1.service.kugou.com/video/pc/live/pull/mutiline/streamaddr?{urllib.parse.urlencode(params)}' - json_str2 = get_req(url=stream_api, proxy_addr=proxy_addr, headers=headers) + json_str2 = get_req(api, proxy_addr=proxy_addr, headers=headers) json_data2 = json.loads(json_str2) stream_data = json_data2['data']['lines'] if stream_data: @@ -2035,7 +2108,7 @@ def get_twitchtv_room_info(url: str, token: str, proxy_addr: Union[str, None] = def get_twitchtv_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', 'Accept-Language': 'en-US', 'Referer': 'https://www.twitch.tv/', 'Client-ID': 'kimne78kx3ncx6brgo4mv6wki5h1ko', @@ -2047,7 +2120,12 @@ def get_twitchtv_stream_data(url: str, proxy_addr: Union[str, None] = None, cook data = { "operationName": "PlaybackAccessToken_Template", - "query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, $isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isLive) { value signature authorization { isForbidden forbiddenReasonCode } __typename } videoPlaybackAccessToken(id: $vodID, params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: $isVod) { value signature __typename }}", + "query": "query PlaybackAccessToken_Template($login: String!, $isLive: Boolean!, $vodID: ID!, " + "$isVod: Boolean!, $playerType: String!) { streamPlaybackAccessToken(channelName: $login, " + "params: {platform: \"web\", playerBackend: \"mediaplayer\", playerType: $playerType}) @include(if: " + "$isLive) { value signature authorization { isForbidden forbiddenReasonCode } __typename " + "} videoPlaybackAccessToken(id: $vodID, params: {platform: \"web\", playerBackend: \"mediaplayer\", " + "playerType: $playerType}) @include(if: $isVod) { value signature __typename }}", "variables": { "isLive": True, "login": uid, @@ -2098,17 +2176,17 @@ def get_twitchtv_stream_data(url: str, proxy_addr: Union[str, None] = None, cook @trace_error_decorator def get_liveme_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: - headers = { 'origin': 'https://www.liveme.com', 'referer': 'https://www.liveme.com', - 'user-agent': 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36', + 'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', } if cookies: headers['Cookie'] = cookies room_id = url.split("/index.html")[0].rsplit('/', maxsplit=1)[-1] - sign_data = execjs.compile(open(f'{JS_SCRIPT_PATH}/liveme.js').read()).call('sign', room_id, f'{JS_SCRIPT_PATH}/crypto-js.min.js') + sign_data = execjs.compile(open(f'{JS_SCRIPT_PATH}/liveme.js').read()).call('sign', room_id, + f'{JS_SCRIPT_PATH}/crypto-js.min.js') lm_s_sign = sign_data.pop("lm_s_sign") tongdun_black_box = sign_data.pop("tongdun_black_box") platform = sign_data.pop("os") @@ -2144,7 +2222,7 @@ def get_huajiao_sn(url: str, cookies: Union[str, None] = None, proxy_addr: Union headers = { 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'referer': 'https://www.huajiao.com/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', } if cookies: @@ -2170,7 +2248,7 @@ def get_huajiao_user_info(url: str, cookies: Union[str, None] = None, proxy_addr headers = { 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'referer': 'https://www.huajiao.com/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', } if cookies: @@ -2205,17 +2283,43 @@ def get_huajiao_user_info(url: str, cookies: Union[str, None] = None, proxy_addr return '未知直播间', None +def get_huajiao_stream_url_app(url: str, proxy_addr: Union[str, None] = None) -> \ + Dict[str, Any]: + headers = { + 'User-Agent': 'living/9.4.0 (com.huajiao.seeding; build:2410231746; iOS 17.0.0) Alamofire/9.4.0', + 'accept-language': 'zh-Hans-US;q=1.0', + 'sdk_version': '1', + } + room_id = url.rsplit('/', maxsplit=1)[1] + api = f'https://live.huajiao.com/feed/getFeedInfo?relateid={room_id}' + json_str = get_req(api, proxy_addr=proxy_addr, headers=headers) + json_data = json.loads(json_str) + + result = {"anchor_name": "", "is_live": False} + if not json_data.get('data'): + print('获取直播间数据失败,花椒直播间地址是非固定的,请手动更换地址进行录制') + return result + result["is_live"] = True + result['anchor_name'] = json_data['data']['feed']['title'] + play_url = f'https://al2-flv.live.huajiao.com/live_huajiao_h265/{json_data["data"]["feed"]["sn"]}.flv' + result['flv_url'] = play_url + result['record_url'] = play_url + return result + + @trace_error_decorator def get_huajiao_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'referer': 'https://www.huajiao.com/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', } if cookies: headers['Cookie'] = cookies + if '/user' not in url: + return get_huajiao_stream_url_app(url, proxy_addr) anchor_name, room_info = get_huajiao_user_info(url) result = {"anchor_name": anchor_name, "is_live": False} @@ -2246,14 +2350,19 @@ def get_liuxing_stream_url(url: str, proxy_addr: Union[str, None] = None, cookie 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'Referer': 'https://wap.7u66.com/198189?promoters=0', - 'User-Agent': 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36 Edg/121.0.0.0', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', } if cookies: headers['Cookie'] = cookies room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1] - api = f'https://wap.7u66.com/api/ui/room/v1.0.0/live.ashx?promoters=0&roomidx={room_id}¤tUrl=https://www.7u66.com/{room_id}?promoters=0' + params = { + "promoters": "0", + "roomidx": room_id, + "currentUrl": f"https://www.7u66.com/{room_id}?promoters=0" + } + api = f'https://wap.7u66.com/api/ui/room/v1.0.0/live.ashx?{urllib.parse.urlencode(params)}' json_str = get_req(url=api, proxy_addr=proxy_addr, headers=headers) json_data = json.loads(json_str) room_info = json_data['data']['roomInfo'] @@ -2276,7 +2385,7 @@ def get_showroom_stream_data(url: str, proxy_addr: Union[str, None] = None, cook headers = { 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', } if cookies: headers['Cookie'] = cookies @@ -2322,7 +2431,7 @@ def get_acfun_sign_params(proxy_addr: Union[str, None] = None, cookies: Union[st did = f'web_{generate_random_string(16)}' headers = { 'referer': 'https://live.acfun.cn/', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', 'cookie': f'_did={did};', } if cookies: @@ -2343,7 +2452,7 @@ def get_acfun_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies Dict[str, Any]: headers = { 'referer': 'https://live.acfun.cn/live/17912421', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', } if cookies: headers['Cookie'] = cookies @@ -2374,10 +2483,12 @@ def get_acfun_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies play_api = f'https://api.kuaishouzt.com/rest/zt/live/web/startPlay?{urllib.parse.urlencode(params)}' json_str = get_req(play_api, data=data, proxy_addr=proxy_addr, headers=headers) json_data = json.loads(json_str) + live_title = json_data['data']['caption'] videoPlayRes = json_data['data']['videoPlayRes'] play_url_list = json.loads(videoPlayRes)['liveAdaptiveManifest'][0]['adaptationSet']['representation'] play_url_list = sorted(play_url_list, key=itemgetter('bitrate'), reverse=True) result['play_url_list'] = play_url_list + result['title'] = live_title return result @@ -2386,7 +2497,7 @@ def get_acfun_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies def get_changliao_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { - 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Referer': 'https://wap.tlclw.com/phone/801044397?promoters=0', @@ -2432,7 +2543,7 @@ def get_yingke_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies Dict[str, Any]: headers = { 'Referer': 'https://www.inke.cn/', - 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', } if cookies: headers['Cookie'] = cookies @@ -2468,7 +2579,7 @@ def get_yingke_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies def get_yinbo_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { - 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'Referer': 'https://live.ybw1666.com/800005143?promoters=0', @@ -2515,7 +2626,7 @@ def get_zhihu_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Dict[str, Any]: headers = { 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', - 'user-agent': 'Mozilla/5.0 (iPad; CPU OS 16_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.6 Mobile/15E148 Safari/604.1 Edg/121.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', } if cookies: headers['Cookie'] = cookies @@ -2529,8 +2640,10 @@ def get_zhihu_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: live_status = live_data['drama']['status'] result = {"anchor_name": anchor_name, "is_live": False} if live_status == 1: + live_title = live_data['theme'] play_url = live_data['drama']['playInfo'] result["is_live"] = True + result["title"] = live_title result["m3u8_url"] = play_url['hlsUrl'] result["flv_url"] = play_url['playUrl'] result["record_url"] = play_url['hlsUrl'] @@ -2545,7 +2658,7 @@ def get_chzzk_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'origin': 'https://chzzk.naver.com', 'referer': 'https://chzzk.naver.com/live/458f6ec20b034f49e0fc6d03921646d2', - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0', + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0', } if cookies: headers['Cookie'] = cookies @@ -2577,7 +2690,7 @@ def get_haixiu_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies headers = { 'origin': 'https://www.haixiutv.com', 'referer': 'https://www.haixiutv.com/', - 'user-agent': 'Mozilla/5.0 (Linux; Android 8.0.0; SM-G955U Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36', + 'user-agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', } if cookies: headers['Cookie'] = cookies @@ -2592,13 +2705,14 @@ def get_haixiu_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies "accessToken": access_token, "tku": "3000006", "c": "10138100100000", - "_st1": int(time.time()*1000) + "_st1": int(time.time() * 1000) } - ajax_data = execjs.compile(open(f'{JS_SCRIPT_PATH}/haixiu.js').read()).call('sign', params, f'{JS_SCRIPT_PATH}/crypto-js.min.js') + ajax_data = execjs.compile(open(f'{JS_SCRIPT_PATH}/haixiu.js').read()).call('sign', params, + f'{JS_SCRIPT_PATH}/crypto-js.min.js') params["accessToken"] = urllib.parse.unquote(urllib.parse.unquote(access_token)) params['_ajaxData1'] = ajax_data - params['_'] = int(time.time()*1000) + params['_'] = int(time.time() * 1000) if 'haixiutv' in url: api = f'https://service.haixiutv.com/v2/room/{room_id}/media/advanceInfoRoom?{urllib.parse.urlencode(params)}' @@ -2629,7 +2743,7 @@ def get_haixiu_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies def get_vvxqiu_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \ Dict[str, Any]: headers = { - 'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148', + 'User-Agent': 'ios/7.830 (ios 17.0; ; iPhone 15 (A2846/A3089/A3090/A3092))', 'Access-Control-Request-Method': 'GET', 'Origin': 'https://h5webcdn-pro.vvxqiu.com', 'Referer': 'https://h5webcdn-pro.vvxqiu.com/', @@ -2647,9 +2761,11 @@ def get_vvxqiu_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies "anchor_name": anchor_name, "is_live": False, } - if json_data['data']['anchorRank'] != 0: + + m3u8_url = f'https://liveplay-pro.wasaixiu.com/live/1400442770_{room_id}_{room_id[2:]}_single.m3u8' + resp = get_req(m3u8_url, proxy_addr=proxy_addr, headers=headers) + if 'Not Found' not in resp: result["is_live"] = True - m3u8_url = f'https://liveplay-pro.wasaixiu.com/live/1400442770_{room_id}_{room_id[2:]}_single.m3u8' result['m3u8_url'] = m3u8_url result['record_url'] = m3u8_url return result @@ -2682,7 +2798,8 @@ def get_17live_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies api_1 = f'https://wap-api.17app.co/api/v1/lives/{room_id}/viewers/alive' json_str = get_req(api_1, json_data=json_data, proxy_addr=proxy_addr, headers=headers) json_data = json.loads(json_str) - if json_data['status'] == 2: + live_status = json_data.get("status") + if live_status and live_status == 2: result["is_live"] = True flv_url = json_data['pullURLsInfo']['rtmpURLs'][0]['urlHighQuality'] result['flv_url'] = flv_url @@ -2803,4 +2920,4 @@ def get_6room_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: flv_url = f'https://wlive.6rooms.com/httpflv/{flv_title}.flv' result['flv_url'] = flv_url result['record_url'] = get_req(flv_url, proxy_addr=proxy_addr, headers=headers, redirect_url=True) - return result \ No newline at end of file + return result diff --git a/douyinliverecorder/stream.py b/douyinliverecorder/stream.py index 196c10b..38e3c90 100644 --- a/douyinliverecorder/stream.py +++ b/douyinliverecorder/stream.py @@ -4,7 +4,7 @@ Author: Hmily GitHub: https://github.com/ihmily Date: 2023-07-15 23:15:00 -Update: 2024-10-02 04:36:12 +Update: 2024-10-27 17:15:00 Copyright (c) 2023-2024 by Hmily, All Rights Reserved. Function: Get live stream data. """ @@ -26,7 +26,7 @@ from .spider import ( @trace_error_decorator def get_douyin_stream_url(json_data: dict, video_quality: str) -> Dict[str, Any]: - anchor_name = json_data.get('anchor_name', None) + anchor_name = json_data.get('anchor_name') result = { "anchor_name": anchor_name, @@ -50,6 +50,7 @@ def get_douyin_stream_url(json_data: dict, video_quality: str) -> Dict[str, Any] quality_index = video_qualities.get(video_quality) m3u8_url = m3u8_url_list[quality_index] flv_url = flv_url_list[quality_index] + result['title'] = json_data['title'] result['m3u8_url'] = m3u8_url result['flv_url'] = flv_url result['is_live'] = True @@ -101,6 +102,7 @@ def get_tiktok_stream_url(json_data: dict, video_quality: str) -> Dict[str, Any] m3u8_url_list.append(m3u8_url_list[-1]) video_qualities = {"原画": 0, "蓝光": 0, "超清": 1, "高清": 2, "标清": 3, '流畅': 4} quality_index = video_qualities.get(video_quality) + result['title'] = live_room['liveRoom']['title'] result['flv_url'] = flv_url_list[quality_index]['url'] result['m3u8_url'] = m3u8_url_list[quality_index]['url'] result['is_live'] = True @@ -226,6 +228,7 @@ def get_huya_stream_url(json_data: dict, video_quality: str) -> Dict[str, Any]: flv_url = flv_url + str(video_quality_options[video_quality]) m3u8_url = m3u8_url + str(video_quality_options[video_quality]) + result['title'] = game_live_info['introduction'] result['flv_url'] = flv_url result['m3u8_url'] = m3u8_url result['is_live'] = True @@ -248,11 +251,11 @@ def get_douyu_stream_url(json_data: dict, video_quality: str, cookies: str, prox } rid = str(json_data["room_id"]) - json_data.pop("room_id", None) + json_data.pop("room_id") rate = video_quality_options.get(video_quality, '0') flv_data = get_douyu_stream_data(rid, rate, cookies=cookies, proxy_addr=proxy_addr) - rtmp_url = flv_data['data'].get('rtmp_url', None) - rtmp_live = flv_data['data'].get('rtmp_live', None) + rtmp_url = flv_data['data'].get('rtmp_url') + rtmp_live = flv_data['data'].get('rtmp_live') if rtmp_live: flv_url = f'{rtmp_url}/{rtmp_live}' json_data['flv_url'] = flv_url @@ -271,6 +274,7 @@ def get_yy_stream_url(json_data: dict) -> Dict[str, Any]: stream_line_addr = json_data['avp_info_res']['stream_line_addr'] cdn_info = list(stream_line_addr.values())[0] flv_url = cdn_info['cdn_info']['url'] + result['title'] = json_data['title'] result['flv_url'] = flv_url result['is_live'] = True result['record_url'] = flv_url @@ -303,6 +307,7 @@ def get_bilibili_stream_url(json_data: dict, video_quality: str, proxy_addr: str return { 'anchor_name': json_data['anchor_name'], 'is_live': True, + 'title': json_data['title'], 'record_url': play_url } @@ -324,6 +329,7 @@ def get_netease_stream_url(json_data: dict, video_quality: str) -> Dict[str, Any return { "is_live": True, "anchor_name": json_data['anchor_name'], + "title": json_data['title'], "flv_url": flv_url, "record_url": flv_url } @@ -352,4 +358,5 @@ def get_stream_url(json_data: dict, video_quality: str, url_type: str = 'm3u8', flv = play_url_list[selected_quality][extra_key] if extra_key else play_url_list[selected_quality] data["flv_url"] = flv data["record_url"] = flv + data['title'] = json_data.get('title') return data diff --git a/main.py b/main.py index c22f10a..6607eb2 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ Author: Hmily GitHub: https://github.com/ihmily Date: 2023-07-17 23:52:05 -Update: 2024-10-16 23:27:00 +Update: 2024-10-28 00:21:00 Copyright (c) 2023-2024 by Hmily, All Rights Reserved. Function: Record live stream video. """ @@ -37,8 +37,8 @@ from msg_push import ( version = "v3.0.9" platforms = ("\n国内站点:抖音|快手|虎牙|斗鱼|YY|B站|小红书|bigo|blued|网易CC|千度热播|猫耳FM|Look|TwitCasting|百度|微博|" - "酷狗|花椒|流星|Acfun|时光|映客|音播|知乎|嗨秀|VV星球|17Live|漂漂|六间房|乐嗨|花猫" - "\n海外站点:TikTok|SOOP[AfreecaTV]|PandaTV|WinkTV|FlexTV|PopkonTV|TwitchTV|LiveMe|ShowRoom|CHZZK|浪Live") + "酷狗|花椒|流星|Acfun|畅聊|映客|音播|知乎|嗨秀|VV星球|17Live|浪Live|漂漂|六间房|乐嗨|花猫" + "\n海外站点:TikTok|SOOP[AfreecaTV]|PandaTV|WinkTV|FlexTV|PopkonTV|TwitchTV|LiveMe|ShowRoom|CHZZK") recording = set() error_count = 0 @@ -65,7 +65,7 @@ config_file = f'{script_path}/config/config.ini' url_config_file = f'{script_path}/config/URL_config.ini' backup_dir = f'{script_path}/backup_config' text_encoding = 'utf-8-sig' -rstr = r"[\/\\\:\*\?\"\<\>\|&#.。,, ~]" +rstr = r"[\/\\\:\*\?\"\<\>\|&#.。,, ~!·]" ffmpeg_path = f"{script_path}/ffmpeg.exe" default_path = f'{script_path}/downloads' os.makedirs(default_path, exist_ok=True) @@ -299,12 +299,12 @@ def clear_record_info(record_name: str, record_url: str): def check_subprocess(record_name: str, record_url: str, ffmpeg_command: list, save_type: str, bash_file_path: Union[str, None] = None) -> bool: - save_path_name = ffmpeg_command[-1] + save_file_path = ffmpeg_command[-1] process = subprocess.Popen( ffmpeg_command, stderr=subprocess.STDOUT, startupinfo=get_startup_info(os_type) ) - subs_file_path = save_path_name.rsplit('.', maxsplit=1)[0] + subs_file_path = save_file_path.rsplit('.', maxsplit=1)[0] subs_thread_name = f'subs_{Path(subs_file_path).name}' if create_time_file and not split_video_by_time and '音频' not in save_type: create_var[subs_thread_name] = threading.Thread( @@ -327,19 +327,19 @@ def check_subprocess(record_name: str, record_url: str, ffmpeg_command: list, sa if return_code == 0: if ts_to_mp4 and save_type == 'TS': if split_video_by_time: - file_paths = get_file_paths(os.path.dirname(save_path_name)) - prefix = os.path.basename(save_path_name).rsplit('_', maxsplit=1)[0] + file_paths = get_file_paths(os.path.dirname(save_file_path)) + prefix = os.path.basename(save_file_path).rsplit('_', maxsplit=1)[0] for path in file_paths: if prefix in path: threading.Thread(target=converts_mp4, args=(path, delete_origin_file)).start() else: - threading.Thread(target=converts_mp4, args=(save_path_name, delete_origin_file)).start() + threading.Thread(target=converts_mp4, args=(save_file_path, delete_origin_file)).start() print(f"\n{record_name} {stop_time} 直播录制完成\n") if bash_file_path: if os_type != 'nt': print(f'准备执行自定义Bash脚本,请确认脚本是否有执行权限! 路径:{bash_file_path}') - bash_command = [bash_file_path, record_name.split(' ', maxsplit=1)[-1], save_path_name, save_type, + bash_command = [bash_file_path, record_name.split(' ', maxsplit=1)[-1], save_file_path, save_type, split_video_by_time, ts_to_mp4] run_bash(bash_command) else: @@ -371,14 +371,14 @@ def start_record(url_data: tuple, count_variable: int = -1): if proxy_addr: proxy_address = None for platform in enable_proxy_platform_list: - if platform and platform.strip() in url: + if platform and platform.strip() in record_url: proxy_address = proxy_addr break if not proxy_address: if extra_enable_proxy_platform_list: for pt in extra_enable_proxy_platform_list: - if pt and pt.strip() in url: + if pt and pt.strip() in record_url: proxy_address = proxy_addr_bak or None # print(f'\r代理地址:{proxy_address}') @@ -493,7 +493,7 @@ def start_record(url_data: tuple, count_variable: int = -1): username=afreecatv_username, password=afreecatv_password ) - if json_data and json_data.get('new_cookies', None): + if json_data and json_data.get('new_cookies'): update_config(config_file, 'Cookie', 'afreecatv_cookie', json_data['new_cookies']) port_info = stream.get_stream_url(json_data, record_quality, spec=True) else: @@ -553,7 +553,7 @@ def start_record(url_data: tuple, count_variable: int = -1): username=flextv_username, password=flextv_password ) - if json_data and json_data.get('new_cookies', None): + if json_data and json_data.get('new_cookies'): update_config(config_file, 'Cookie', 'flextv_cookie', json_data['new_cookies']) port_info = stream.get_stream_url(json_data, record_quality, spec=True) else: @@ -770,8 +770,8 @@ def start_record(url_data: tuple, count_variable: int = -1): error_window.append(1) else: anchor_name = re.sub(rstr, "_", anchor_name) - anchor_name = remove_emojis(anchor_name, '_') - + anchor_name = anchor_name.replace("(", "(").replace(")", ")") + anchor_name = remove_emojis(anchor_name, '_').strip('_') record_name = f'序号{count_variable} {anchor_name}' if record_url in url_comments: @@ -829,10 +829,17 @@ def start_record(url_data: tuple, count_variable: int = -1): time.sleep(push_check_seconds) continue - real_url = port_info.get('record_url', None) + real_url = port_info.get('record_url') full_path = f'{default_path}/{platform}' - if len(real_url) > 0: + if real_url: now = datetime.datetime.today().strftime("%Y-%m-%d_%H-%M-%S") + live_title = port_info.get('title') + title_in_name = '' + if live_title: + live_title = re.sub(rstr, "_", live_title).strip() + live_title = live_title.replace("(", "(").replace(")", ")") + live_title = remove_emojis(live_title, '_').strip('_') + title_in_name = live_title + '_' if filename_by_title else '' try: if len(video_save_path) > 0: @@ -846,6 +853,11 @@ def start_record(url_data: tuple, count_variable: int = -1): full_path = f'{full_path}/{anchor_name}' if folder_by_time: full_path = f'{full_path}/{now[:10]}' + if folder_by_title and port_info.get('title'): + if folder_by_time: + full_path = f'{full_path}/{live_title}_{anchor_name}' + else: + full_path = f'{full_path}/{now[:10]}_{live_title}' if not os.path.exists(full_path): os.makedirs(full_path) except Exception as e: @@ -899,7 +911,8 @@ def start_record(url_data: tuple, count_variable: int = -1): '17Live': 'referer:https://17.live/en/live/6302408', '浪Live': 'referer:https://www.lang.live', } - headers = record_headers.get(platform, '') + + headers = record_headers.get(platform) if headers: ffmpeg_command.insert(11, "-headers") ffmpeg_command.insert(12, headers) @@ -921,7 +934,7 @@ def start_record(url_data: tuple, count_variable: int = -1): f"{platform} | {anchor_name} | 直播源地址: {port_info['record_url']}") if video_save_type == "FLV": - filename = anchor_name + '_' + now + '.flv' + filename = anchor_name + f'_{title_in_name}' + now + '.flv' save_file_path = f'{full_path}/{filename}' print(f'{rec_info}/{filename}') @@ -935,7 +948,7 @@ def start_record(url_data: tuple, count_variable: int = -1): create_var[subs_thread_name].start() try: - flv_url = port_info.get('flv_url', None) + flv_url = port_info.get('flv_url') if flv_url: _filepath, _ = urllib.request.urlretrieve(real_url, save_file_path) record_finished = True @@ -951,14 +964,14 @@ def start_record(url_data: tuple, count_variable: int = -1): error_window.append(1) elif video_save_type == "MKV": - filename = anchor_name + '_' + now + ".mkv" + filename = anchor_name + f'_{title_in_name}' + now + ".mkv" print(f'{rec_info}/{filename}') save_file_path = full_path + '/' + filename try: if split_video_by_time: now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) - save_file_path = f"{full_path}/{anchor_name}_{now}_%03d.mkv" + save_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.mkv" command = [ "-flags", "global_header", "-c:v", "copy", @@ -999,14 +1012,14 @@ def start_record(url_data: tuple, count_variable: int = -1): error_window.append(1) elif video_save_type == "MP4": - filename = anchor_name + '_' + now + ".mp4" + filename = anchor_name + f'_{title_in_name}' + now + ".mp4" print(f'{rec_info}/{filename}') save_file_path = full_path + '/' + filename try: if split_video_by_time: now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) - save_file_path = f"{full_path}/{anchor_name}_{now}_%03d.mp4" + save_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.mp4" command = [ "-c:v", "copy", "-c:a", "aac", @@ -1025,7 +1038,7 @@ def start_record(url_data: tuple, count_variable: int = -1): "-c:v", "copy", "-c:a", "copy", "-f", "mp4", - "{path}".format(path=save_file_path), + save_file_path, ] ffmpeg_command.extend(command) @@ -1050,10 +1063,11 @@ def start_record(url_data: tuple, count_variable: int = -1): now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) extension = "mp3" if "MP3" in video_save_type else "m4a" name_format = "_%03d" if split_video_by_time else "" - save_path_name = f"{full_path}/{anchor_name}_{now}{name_format}.{extension}" + save_file_path = (f"{full_path}/{anchor_name}_{title_in_name}{now}" + f"{name_format}.{extension}") if split_video_by_time: - print(f'\r{anchor_name} 准备开始录制音频: {save_path_name}') + print(f'\r{anchor_name} 准备开始录制音频: {save_file_path}') if "MP3" in video_save_type: command = [ @@ -1063,7 +1077,7 @@ def start_record(url_data: tuple, count_variable: int = -1): "-f", "segment", "-segment_time", split_time, "-reset_timestamps", "1", - save_path_name, + save_file_path, ] else: command = [ @@ -1075,7 +1089,7 @@ def start_record(url_data: tuple, count_variable: int = -1): "-segment_time", split_time, "-segment_format", 'mpegts', "-reset_timestamps", "1", - save_path_name, + save_file_path, ] else: @@ -1084,7 +1098,7 @@ def start_record(url_data: tuple, count_variable: int = -1): "-map", "0:a", "-c:a", "libmp3lame", "-ab", "320k", - save_path_name, + save_file_path, ] else: @@ -1094,7 +1108,7 @@ def start_record(url_data: tuple, count_variable: int = -1): "-bsf:a", "aac_adtstoasc", "-ab", "320k", "-movflags", "+faststart", - save_path_name, + save_file_path, ] ffmpeg_command.extend(command) @@ -1117,11 +1131,11 @@ def start_record(url_data: tuple, count_variable: int = -1): else: if split_video_by_time: now = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime()) - filename = anchor_name + '_' + now + ".ts" + filename = anchor_name + f'_{title_in_name}' + now + ".ts" print(f'{rec_info}/{filename}') try: - save_path_name = f"{full_path}/{anchor_name}_{now}_%03d.ts" + save_file_path = f"{full_path}/{anchor_name}_{title_in_name}{now}_%03d.ts" command = [ "-c:v", "copy", "-c:a", "copy", @@ -1130,7 +1144,7 @@ def start_record(url_data: tuple, count_variable: int = -1): "-segment_time", split_time, "-segment_format", 'mpegts', "-reset_timestamps", "1", - save_path_name, + save_file_path, ] ffmpeg_command.extend(command) @@ -1143,8 +1157,8 @@ def start_record(url_data: tuple, count_variable: int = -1): ) if comment_end: if ts_to_mp4: - file_paths = get_file_paths(os.path.dirname(save_path_name)) - prefix = os.path.basename(save_path_name).rsplit('_', maxsplit=1)[0] + file_paths = get_file_paths(os.path.dirname(save_file_path)) + prefix = os.path.basename(save_file_path).rsplit('_', maxsplit=1)[0] for path in file_paths: if prefix in path: threading.Thread( @@ -1161,7 +1175,7 @@ def start_record(url_data: tuple, count_variable: int = -1): error_window.append(1) else: - filename = anchor_name + '_' + now + ".ts" + filename = anchor_name + f'_{title_in_name}' + now + ".ts" print(f'{rec_info}/{filename}') save_file_path = full_path + '/' + filename @@ -1171,7 +1185,7 @@ def start_record(url_data: tuple, count_variable: int = -1): "-c:a", "copy", "-map", "0", "-f", "mpegts", - "{path}".format(path=save_file_path), + save_file_path, ] ffmpeg_command.extend(command) @@ -1397,6 +1411,8 @@ while True: video_save_path = read_config_value(config, '录制设置', '直播保存路径(不填则默认)', "") folder_by_author = options.get(read_config_value(config, '录制设置', '保存文件夹是否以作者区分', "是"), False) folder_by_time = options.get(read_config_value(config, '录制设置', '保存文件夹是否以时间区分', "否"), False) + folder_by_title = options.get(read_config_value(config, '录制设置', '保存文件夹是否以标题区分', "否"), False) + filename_by_title = options.get(read_config_value(config, '录制设置', '保存文件名是否包含标题', "否"), False) video_save_type = read_config_value(config, '录制设置', '视频保存格式ts|mkv|flv|mp4|mp3音频|m4a音频', "ts") video_record_quality = read_config_value(config, '录制设置', '原画|超清|高清|标清|流畅', "原画") use_proxy = options.get(read_config_value(config, '录制设置', '是否使用代理ip(是/否)', "是"), False) @@ -1591,6 +1607,7 @@ while True: 'www.haixiutv.com', "h5webcdn-pro.vvxqiu.com", "17.live", + 'www.lang.live', "m.pp.weimipopo.com", "v.6.cn", "m.6.cn", @@ -1611,8 +1628,7 @@ while True: 'www.liveme.com', 'www.showroom-live.com', 'chzzk.naver.com', - 'm.chzzk.naver.com', - 'www.lang.live' + 'm.chzzk.naver.com' ] platform_host.extend(overseas_platform_host) @@ -1688,4 +1704,4 @@ while True: t2.start() first_run = False - time.sleep(3) \ No newline at end of file + time.sleep(3)