mirror of
https://github.com/ihmily/DouyinLiveRecorder.git
synced 2025-12-26 05:48:32 +08:00
Fix xiaohongshu live record and add afreecatv login
This commit is contained in:
parent
63670d0025
commit
753c5e003d
29
README.md
29
README.md
@ -5,6 +5,7 @@
|
||||
[](https://github.com/ihmily/DouyinLiveRecorder)
|
||||
[](https://hub.docker.com/r/ihmily/douyin-live-recorder/tags)
|
||||

|
||||
[](https://github.com/ihmily/DouyinLiveRecorder/releases/latest)
|
||||

|
||||
|
||||
一款简易的可循环值守的直播录制工具,基于FFmpeg实现多平台直播源录制,支持自定义配置录制以及直播状态推送。
|
||||
@ -106,7 +107,7 @@ B站:
|
||||
https://live.bilibili.com/320
|
||||
|
||||
小红书:
|
||||
https://www.xiaohongshu.com/hina/livestream/568980065082002402?appuid=5f3f478a00000000010005b3&apptime=
|
||||
https://www.redelight.cn/hina/livestream/569077534207413574/1707413727088?appuid=5f3f478a00000000010005b3&
|
||||
|
||||
bigo直播:
|
||||
https://www.bigo.tv/cn/716418802
|
||||
@ -138,23 +139,9 @@ https://fm.missevan.com/live/868895007
|
||||
|
||||
该解析接口 ~~仅供演示~~(演示接口暂时停止,后续再开放),并且只包含抖音、快手、虎牙直播的解析,其他平台如有需要请自行添加,源码在这里 [DouyinLiveRecorder/api](https://github.com/ihmily/DouyinLiveRecorder/tree/main/api)
|
||||
|
||||
```HTTP
|
||||
GET https://hmily.vip/api/jx/live/?url=
|
||||
```
|
||||
|
||||
请求示例:
|
||||
|
||||
```HTTP
|
||||
GET https://hmily.vip/api/jx/live/?url=https://live.douyin.com/573716250978
|
||||
```
|
||||
|
||||
若需要将抖音直播间短链接转换为长链接,使用以下接口:
|
||||
|
||||
```HTTP
|
||||
GET https://hmily.vip/api/jx/live/convert.php?url=https://v.douyin.com/iQLgKSj/
|
||||
```
|
||||
|
||||
在线播放m3u8和flv视频网站:[M3U8 在线视频播放器 ](https://jx.hmily.vip/play/)
|
||||
在线播放m3u8和flv视频网站:[M3U8 在线视频播放器 ](https://jx.hmily.vip/play/),源码是 [index.html](https://github.com/ihmily/DouyinLiveRecorder/blob/main/index.html)
|
||||
|
||||
 
|
||||
|
||||
@ -165,6 +152,7 @@ GET https://hmily.vip/api/jx/live/convert.php?url=https://v.douyin.com/iQLgKSj/
|
||||
|
||||
```bash
|
||||
git clone https://github.com/ihmily/DouyinLiveRecorder.git
|
||||
|
||||
```
|
||||
|
||||
2.进入项目文件夹,安装依赖
|
||||
@ -262,9 +250,13 @@ docker-compose stop
|
||||
|
||||
## ⏳提交日志
|
||||
|
||||
- 20240209
|
||||
- 优化AfreecaTV录制,新增账号密码登录获取cookie以及持久保存
|
||||
- 修复了小红书直播因官方更新直播域名,导致无法录制直播的问题
|
||||
- 修复了更新URL配置文件的bug
|
||||
- 最后,祝大家新年快乐!
|
||||
- 20240129
|
||||
- 新增猫耳FM直播录制
|
||||
|
||||
- 20240127
|
||||
- 新增千度热播直播录制、新增pandaTV(韩国)直播录制
|
||||
|
||||
@ -273,15 +265,12 @@ docker-compose stop
|
||||
- 新增自定义设置不同直播间的录制画质(即每个直播间录制画质可不同)
|
||||
|
||||
- 修改录制视频保存路径为 `downloads` 文件夹,并且分平台进行保存。
|
||||
|
||||
- 20240114
|
||||
- 新增网易cc直播录制,优化ffmpeg参数,修改AfreecaTV输入直播地址格式
|
||||
|
||||
- 修改日志记录器 @[iridescentGray](https://github.com/iridescentGray)
|
||||
|
||||
- 20240102
|
||||
- 修复Linux上运行,新增docker配置文件
|
||||
|
||||
- 20231210
|
||||
|
||||
- 修复录制分段bug,修复bigo录制检测bug
|
||||
|
||||
@ -38,4 +38,8 @@ afreecatv_cookie =
|
||||
netease_cookie =
|
||||
千度热播_cookie =
|
||||
pandatv_cookie =
|
||||
猫耳FM_cookie =
|
||||
猫耳FM_cookie =
|
||||
|
||||
[账号密码]
|
||||
afreecatv账号 =
|
||||
afreecatv密码 =
|
||||
28
main.py
28
main.py
@ -4,7 +4,7 @@
|
||||
Author: Hmily
|
||||
GitHub: https://github.com/ihmily
|
||||
Date: 2023-07-17 23:52:05
|
||||
Update: 2024-01-29 18:45:09
|
||||
Update: 2024-02-09 02:41:18
|
||||
Copyright (c) 2023-2024 by Hmily, All Rights Reserved.
|
||||
Function: Record live stream video.
|
||||
"""
|
||||
@ -146,13 +146,16 @@ def display_info():
|
||||
logger.warning(f"错误信息: {e} 发生错误的行数: {e.__traceback__.tb_lineno}")
|
||||
|
||||
|
||||
def update_file(file_path: str, old_str: str, new_str: str):
|
||||
def update_file(file_path: str, old_str: str, new_str: str, start_str: str = None):
|
||||
# TODO: 更新文件操作
|
||||
file_data = ""
|
||||
with open(file_path, "r", encoding="utf-8-sig") as f:
|
||||
for text_line in f:
|
||||
if old_str in text_line:
|
||||
text_line = text_line.replace(old_str, new_str)
|
||||
if start_str:
|
||||
text_line = f'{start_str}{text_line}'
|
||||
|
||||
file_data += text_line
|
||||
with open(file_path, "w", encoding="utf-8-sig") as f:
|
||||
f.write(file_data)
|
||||
@ -576,7 +579,7 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
no_error = True
|
||||
new_record_url = ''
|
||||
count_time = time.time()
|
||||
|
||||
retry = 0
|
||||
record_quality, record_url, anchor_name = url_data
|
||||
print(f"\r运行新线程,传入地址 {record_url}")
|
||||
|
||||
@ -648,10 +651,14 @@ def start_record(url_data: tuple, count_variable: int = -1):
|
||||
json_data = get_bilibili_stream_data(record_url, cookies=bili_cookie)
|
||||
port_info = get_bilibili_stream_url(json_data, record_quality)
|
||||
|
||||
elif record_url.find("https://www.xiaohongshu.com/") > -1:
|
||||
elif record_url.find("https://www.redelight.cn/") > -1:
|
||||
platform = '小红书直播'
|
||||
if retry > 0:
|
||||
time.sleep(7200)
|
||||
retry = 0
|
||||
with semaphore:
|
||||
port_info = get_xhs_stream_url(record_url, cookies=xhs_cookie)
|
||||
retry += 1
|
||||
|
||||
elif record_url.find("https://www.bigo.tv/") > -1:
|
||||
platform = 'bigo直播'
|
||||
@ -1322,7 +1329,7 @@ while True:
|
||||
|
||||
# 读取URL_config.ini文件
|
||||
try:
|
||||
with open(url_config_file, "r", encoding=encoding) as file:
|
||||
with open(url_config_file, "r", encoding=encoding, errors='ignore') as file:
|
||||
for line in file:
|
||||
line = line.strip()
|
||||
if line.startswith("#") or len(line) < 20:
|
||||
@ -1362,7 +1369,7 @@ while True:
|
||||
'www.douyu.com',
|
||||
'www.yy.com',
|
||||
'live.bilibili.com',
|
||||
'www.xiaohongshu.com',
|
||||
'www.redelight.cn',
|
||||
'www.bigo.tv',
|
||||
'app.blued.cn',
|
||||
'play.afreecatv.com',
|
||||
@ -1378,12 +1385,19 @@ while True:
|
||||
url_tuples_list.append(new_line)
|
||||
else:
|
||||
print(f"{url} 未知链接.此条跳过")
|
||||
update_file(url_config_file, url, url, start_str='#')
|
||||
|
||||
while len(name_list):
|
||||
a = name_list.pop()
|
||||
replace_words = a.split('|')
|
||||
if replace_words[0] != replace_words[1]:
|
||||
update_file(url_config_file, replace_words[0], replace_words[1])
|
||||
if replace_words[1].startswith("#"):
|
||||
start_with = '#'
|
||||
new_word = replace_words[1][1:]
|
||||
else:
|
||||
start_with = None
|
||||
new_word = replace_words[1]
|
||||
update_file(url_config_file, replace_words[0], new_word, start_str=start_with)
|
||||
|
||||
if len(url_tuples_list) > 0:
|
||||
text_no_repeat_url = list(set(url_tuples_list))
|
||||
|
||||
198
spider.py
198
spider.py
@ -4,7 +4,7 @@
|
||||
Author: Hmily
|
||||
GitHub:https://github.com/ihmily
|
||||
Date: 2023-07-15 23:15:00
|
||||
Update: 2024-01-29 18:57:12
|
||||
Update: 2024-02-09 03:33:50
|
||||
Copyright (c) 2023 by Hmily, All Rights Reserved.
|
||||
Function: Get live stream data.
|
||||
"""
|
||||
@ -18,7 +18,14 @@ import re
|
||||
import json
|
||||
import execjs
|
||||
import urllib.request
|
||||
from utils import trace_error_decorator
|
||||
from utils import (
|
||||
trace_error_decorator,
|
||||
update_config,
|
||||
read_config_value,
|
||||
dict_to_cookie_str
|
||||
)
|
||||
import http.cookiejar
|
||||
from urllib.request import Request
|
||||
|
||||
no_proxy_handler = urllib.request.ProxyHandler({})
|
||||
opener = urllib.request.build_opener(no_proxy_handler)
|
||||
@ -68,7 +75,7 @@ def get_douyin_stream_data(url: str, cookies: Union[str, None] = None) -> Dict[s
|
||||
|
||||
@trace_error_decorator
|
||||
def get_tiktok_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> Dict[
|
||||
str, Any]:
|
||||
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',
|
||||
'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='
|
||||
@ -348,17 +355,19 @@ def get_bilibili_stream_data(url: str, cookies: Union[str, None] = None) -> Dict
|
||||
@trace_error_decorator
|
||||
def get_xhs_stream_url(url: str, 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': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',
|
||||
'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://www.xiaohongshu.com/hina/livestream/568979931846654360',
|
||||
'Referer': 'https://www.redelight.cn/hina/livestream/569077534207413574/1707413727088?share_source=&share_source_id=null&source=share_out_of_app&host_id=58bafe4282ec39085a56ece9&xhsshare=WeixinSession&appuid=5f3f478a00000000010005b3&apptime=1707413727',
|
||||
}
|
||||
if cookies:
|
||||
headers['Cookie'] = cookies
|
||||
|
||||
room_id = url.split('?')[0].rsplit('/', maxsplit=1)[1]
|
||||
appuid = re.search('appuid=(.*?)&', url).group(1)
|
||||
app_api = f'https://www.xiaohongshu.com/api/sns/red/live/app/v1/ecology/outside/share_info?room_id={room_id}'
|
||||
room_id = url.split('?')[0].rsplit('/', maxsplit=2)[1]
|
||||
# 原域名
|
||||
# app_api = f'https://www.xiaohongshu.com/api/sns/red/live/app/v1/ecology/outside/share_info?room_id={room_id}'
|
||||
app_api = f'https://www.redelight.cn/api/sns/red/live/app/v1/ecology/outside/share_info?room_id={room_id}'
|
||||
req = urllib.request.Request(app_api, headers=headers)
|
||||
response = opener.open(req, timeout=15)
|
||||
json_str = response.read().decode('utf-8')
|
||||
@ -370,7 +379,7 @@ def get_xhs_stream_url(url: str, cookies: Union[str, None] = None) -> Dict[str,
|
||||
"is_live": False,
|
||||
}
|
||||
|
||||
# 这个判断不准确,无论是否在直播都为0
|
||||
# 这个判断不准确,无论是否在直播status都为0
|
||||
if live_status == 0:
|
||||
flv_url = f'http://live-play.xhscdn.com/live/{room_id}.flv?uid={appuid}'
|
||||
result['flv_url'] = flv_url
|
||||
@ -450,8 +459,9 @@ def get_blued_stream_url(url: str, cookies: Union[str, None] = None) -> Dict[str
|
||||
return result
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
def get_afreecatv_cdn_url(broad_no: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> Dict[
|
||||
str, Any]:
|
||||
str, Any]:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/119.0',
|
||||
'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',
|
||||
@ -492,8 +502,106 @@ def get_afreecatv_cdn_url(broad_no: str, proxy_addr: Union[str, None] = None, co
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
def get_afreecatv_stream_url(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> Dict[
|
||||
str, Any]:
|
||||
def login_afreecatv(username: str, password: str, proxy_addr: Union[str, None] = None) -> Union[str, None]:
|
||||
if len(username.strip()) < 6 or len(password.strip()) < 10:
|
||||
raise RuntimeError('AfreecaTV登录失败!请在config.ini配置文件中填写正确的AfreecaTV平台的账号和密码')
|
||||
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',
|
||||
'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://login.afreecatv.com/afreeca/login.php?szFrom=full&request_uri=https%3A%2F%2Fwww.afreecatv.com%2F',
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
|
||||
}
|
||||
|
||||
data = {
|
||||
'szWork': 'login',
|
||||
'szType': 'json',
|
||||
'szUid': str(username),
|
||||
'szPassword': str(password),
|
||||
'isSaveId': 'true',
|
||||
'szScriptVar': 'oLoginRet',
|
||||
'szAction': '',
|
||||
'isLoginRetain': 'Y',
|
||||
}
|
||||
|
||||
url = 'https://login.afreecatv.com/app/LoginAction.php?callback=jQuery17208926278503069585_1707311376418'
|
||||
try:
|
||||
if proxy_addr:
|
||||
proxies = {
|
||||
'http': proxy_addr,
|
||||
'https': proxy_addr
|
||||
}
|
||||
|
||||
response = requests.post(url, data=data, headers=headers, proxies=proxies, timeout=15)
|
||||
cookie_dict = response.cookies.get_dict()
|
||||
else:
|
||||
|
||||
data = urllib.parse.urlencode(data).encode('utf-8')
|
||||
cookie_jar = http.cookiejar.CookieJar()
|
||||
login_opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie_jar))
|
||||
req = Request(url, data=data, headers=headers)
|
||||
_ = login_opener.open(req, timeout=15)
|
||||
cookie_dict = {cookie.name: cookie.value for cookie in cookie_jar}
|
||||
|
||||
cookie = dict_to_cookie_str(cookie_dict)
|
||||
return cookie
|
||||
except Exception:
|
||||
print('AfreecaTV登录失败,请检查配置文件中的账号密码是否正确')
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
def get_afreecatv_tk(url: str, rtype: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> \
|
||||
Union[str, tuple, None]:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0',
|
||||
'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://play.afreecatv.com/secretx/250989857',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
}
|
||||
|
||||
if cookies:
|
||||
headers['Cookie'] = cookies
|
||||
|
||||
split_url = url.split('/')
|
||||
bj_id = split_url[3] if len(split_url) < 6 else split_url[5]
|
||||
|
||||
data = {
|
||||
'bid': bj_id,
|
||||
'bno': '',
|
||||
'type': rtype,
|
||||
'pwd': '',
|
||||
'player_type': 'html5',
|
||||
'stream_type': 'common',
|
||||
'quality': 'master',
|
||||
'mode': 'landing',
|
||||
'from_api': '0',
|
||||
}
|
||||
|
||||
url2 = f'https://live.afreecatv.com/afreeca/player_live_api.php?bjid={bj_id}'
|
||||
if proxy_addr:
|
||||
proxies = {
|
||||
'http': proxy_addr,
|
||||
'https': proxy_addr
|
||||
}
|
||||
response = requests.post(url2, data=data, headers=headers, proxies=proxies, timeout=15)
|
||||
json_data = response.json()
|
||||
else:
|
||||
data_encoded = urllib.parse.urlencode(data).encode('utf-8')
|
||||
req = urllib.request.Request(url2, data=data_encoded, headers=headers)
|
||||
response = urllib.request.urlopen(req, timeout=15)
|
||||
json_str = response.read().decode('utf-8')
|
||||
json_data = json.loads(json_str)
|
||||
if rtype == 'aid':
|
||||
token = json_data["CHANNEL"]["AID"]
|
||||
return token
|
||||
else:
|
||||
return json_data['CHANNEL']['BJNICK'], json_data['CHANNEL']['BNO']
|
||||
|
||||
|
||||
@trace_error_decorator
|
||||
def get_afreecatv_stream_url(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; rv:109.0) Gecko/20100101 Firefox/119.0',
|
||||
'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',
|
||||
@ -516,6 +624,7 @@ def get_afreecatv_stream_url(url: str, proxy_addr: Union[str, None] = None, cook
|
||||
}
|
||||
|
||||
url2 = 'http://api.m.afreecatv.com/broad/a/watch'
|
||||
|
||||
if proxy_addr:
|
||||
proxies = {
|
||||
'http': proxy_addr,
|
||||
@ -523,27 +632,65 @@ def get_afreecatv_stream_url(url: str, proxy_addr: Union[str, None] = None, cook
|
||||
}
|
||||
response = requests.post(url2, data=data, headers=headers, proxies=proxies, timeout=15)
|
||||
json_data = response.json()
|
||||
|
||||
else:
|
||||
|
||||
data = urllib.parse.urlencode(data).encode('utf-8')
|
||||
req = urllib.request.Request(url2, data=data, headers=headers)
|
||||
data_encoded = urllib.parse.urlencode(data).encode('utf-8')
|
||||
req = urllib.request.Request(url2, data=data_encoded, headers=headers)
|
||||
response = urllib.request.urlopen(req, timeout=15)
|
||||
json_str = response.read().decode('utf-8')
|
||||
json_data = json.loads(json_str)
|
||||
|
||||
anchor_name = json_data['data']['user_nick']
|
||||
if not anchor_name:
|
||||
if json_data['data']['code'] == -3004:
|
||||
print("AfreecaTV直播获取失败:", json_data['data']['message'])
|
||||
elif json_data['data']['code'] == -3002:
|
||||
print("AfreecaTV直播获取失败:", json_data['data']['message'])
|
||||
if 'user_nick' in json_data['data']:
|
||||
anchor_name = json_data['data']['user_nick']
|
||||
else:
|
||||
anchor_name = ''
|
||||
result = {
|
||||
"anchor_name": '' if anchor_name is None else anchor_name,
|
||||
"is_live": False,
|
||||
}
|
||||
|
||||
if json_data['result'] == 1:
|
||||
if not anchor_name:
|
||||
def handle_login():
|
||||
username = read_config_value('./config/config.ini', '账号密码', 'afreecatv账号')
|
||||
password = read_config_value('./config/config.ini', '账号密码', 'afreecatv密码')
|
||||
cookie = login_afreecatv(username, password, proxy_addr=proxy_addr)
|
||||
if 'PdboxBbs=' in cookie:
|
||||
print('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
|
||||
result['is_live'] = True
|
||||
result['record_url'] = m3u8_url
|
||||
return result
|
||||
|
||||
if json_data['data']['code'] == -3001:
|
||||
print("AfreecaTV直播获取失败[直播刚结束]:", json_data['data']['message'])
|
||||
return result
|
||||
|
||||
elif json_data['data']['code'] == -3002:
|
||||
print("AfreecaTV直播获取失败[未登录]: 19+", json_data['data']['message'])
|
||||
print("正在尝试使用您的账号和密码登录AfreecaTV直播平台,请确保已配置")
|
||||
new_cookie = handle_login()
|
||||
if new_cookie and len(new_cookie) > 0:
|
||||
update_config('./config/config.ini', 'Cookie', 'afreecatv_cookie', new_cookie)
|
||||
return fetch_data(new_cookie)
|
||||
raise RuntimeError('AfreecaTV登录失败,请检查账号和密码是否正确')
|
||||
|
||||
elif json_data['data']['code'] == -3004:
|
||||
# print("AfreecaTV直播获取失败[未认证]:", json_data['data']['message'])
|
||||
if cookies and len(cookies) > 0:
|
||||
return fetch_data(cookies)
|
||||
else:
|
||||
raise RuntimeError('AfreecaTV登录失败,请检查账号和密码是否正确')
|
||||
elif json_data['data']['code'] == -6001:
|
||||
print(f"错误信息:{json_data['data']['message']}请检查输入的直播间地址是否正确")
|
||||
return result
|
||||
if json_data['result'] == 1 and anchor_name:
|
||||
broad_no = json_data['data']['broad_no']
|
||||
hls_authentication_key = json_data['data']['hls_authentication_key']
|
||||
view_url = get_afreecatv_cdn_url(broad_no, proxy_addr=proxy_addr)['view_url']
|
||||
@ -554,7 +701,7 @@ def get_afreecatv_stream_url(url: str, proxy_addr: Union[str, None] = None, cook
|
||||
return result
|
||||
|
||||
|
||||
# @trace_error_decorator
|
||||
@trace_error_decorator
|
||||
def get_netease_stream_data(url: str, cookies: Union[str, None] = None) -> Dict[str, Any]:
|
||||
headers = {
|
||||
'accept': 'application/json, text/plain, */*',
|
||||
@ -615,7 +762,7 @@ def get_qiandurebo_stream_data(url: str, cookies: Union[str, None] = None) -> Di
|
||||
|
||||
@trace_error_decorator
|
||||
def get_pandatv_stream_data(url: str, proxy_addr: Union[str, None] = None, cookies: Union[str, None] = None) -> Dict[
|
||||
str, Any]:
|
||||
str, Any]:
|
||||
headers = {
|
||||
'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',
|
||||
@ -730,11 +877,12 @@ if __name__ == '__main__':
|
||||
# room_url = 'https://www.yy.com/22490906/22490906' # YY直播
|
||||
# room_url = 'https://live.bilibili.com/21593109' # b站直播
|
||||
# 小红书直播
|
||||
# room_url = 'https://www.xiaohongshu.com/hina/livestream/568980065082002402?appuid=5f3f478a00000000010005b3&apptime='
|
||||
# room_url = 'https://www.redelight.cn/hina/livestream/569077534207413574/1707413727088?appuid=5f3f478a00000000010005b3&'
|
||||
# room_url = 'https://www.bigo.tv/cn/716418802' # bigo直播
|
||||
# room_url = 'https://app.blued.cn/live?id=Mp6G2R' # blued直播
|
||||
# room_url = 'https://play.afreecatv.com/sw7love' # afreecatv直播
|
||||
# room_url = 'https://m.afreecatv.com/#/player/hl6260' # afreecatv直播
|
||||
# room_url = 'https://play.afreecatv.com/secretx' # afreecatv直播
|
||||
# room_url = 'https://cc.163.com/583946984' # 网易cc直播
|
||||
# room_url = 'https://qiandurebo.com/web/video.php?roomnumber=33333' # 千度热播
|
||||
# room_url = 'https://www.pandalive.co.kr/live/play/bara0109' # pandaTV
|
||||
|
||||
71
utils.py
71
utils.py
@ -4,6 +4,7 @@ import functools
|
||||
import hashlib
|
||||
import traceback
|
||||
from logger import logger
|
||||
import configparser
|
||||
|
||||
|
||||
def trace_error_decorator(func):
|
||||
@ -27,3 +28,73 @@ def check_md5(file_path):
|
||||
with open(file_path, 'rb') as fp:
|
||||
file_md5 = hashlib.md5(fp.read()).hexdigest()
|
||||
return file_md5
|
||||
|
||||
|
||||
def dict_to_cookie_str(cookies_dict):
|
||||
cookie_str = '; '.join([f"{key}={value}" for key, value in cookies_dict.items()])
|
||||
return cookie_str
|
||||
|
||||
|
||||
def read_config_value(file_path, section, key):
|
||||
"""
|
||||
从配置文件中读取指定键的值。
|
||||
|
||||
参数:
|
||||
- file_path: 配置文件的路径。
|
||||
- section: 部分名称。
|
||||
- key: 键名称。
|
||||
|
||||
返回:
|
||||
- 键的值,如果部分或键不存在则返回None。
|
||||
"""
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
try:
|
||||
config.read(file_path, encoding='utf-8-sig')
|
||||
except Exception as e:
|
||||
print(f"读取配置文件时出错: {e}")
|
||||
return None
|
||||
|
||||
if section in config:
|
||||
if key in config[section]:
|
||||
return config[section][key]
|
||||
else:
|
||||
print(f"键[{key}]不存在于部分[{section}]中。")
|
||||
else:
|
||||
print(f"部分[{section}]不存在于文件中。")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def update_config(file_path, section, key, new_value):
|
||||
"""
|
||||
更新配置文件中的键值。
|
||||
|
||||
参数:
|
||||
- file_path: 配置文件的路径。
|
||||
- section: 要更新的部分名称。
|
||||
- key: 要更新的键名称。
|
||||
- new_value: 新的键值。
|
||||
"""
|
||||
config = configparser.ConfigParser()
|
||||
|
||||
try:
|
||||
config.read(file_path, encoding='utf-8-sig')
|
||||
except Exception as e:
|
||||
print(f"读取配置文件时出错: {e}")
|
||||
return
|
||||
|
||||
if section not in config:
|
||||
print(f"部分[{section}]不存在于文件中。")
|
||||
return
|
||||
|
||||
# 转义%字符
|
||||
escaped_value = new_value.replace('%', '%%')
|
||||
config[section][key] = escaped_value
|
||||
|
||||
try:
|
||||
with open(file_path, 'w', encoding='utf-8-sig') as configfile:
|
||||
config.write(configfile)
|
||||
print(f"配置文件中[{section}]下的{key}的值已更新")
|
||||
except Exception as e:
|
||||
print(f"写入配置文件时出错: {e}")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user