Skip to content
This repository was archived by the owner on May 23, 2025. It is now read-only.

Commit c6b367a

Browse files
committed
feat: 抖音分享链接解析
1 parent 3d1276e commit c6b367a

File tree

2 files changed

+221
-0
lines changed

2 files changed

+221
-0
lines changed

plugins/all_in_one_config.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,9 @@ count = 3
269269

270270
[GoodMorning]
271271
enable = true
272+
273+
[Douyin]
274+
# Http代理设置
275+
# 格式: http://用户名:密码@代理地址:代理端口
276+
# 例如:http://127.0.0.1:7890
277+
http-proxy = ""

plugins/douyin_parser.py

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
import re
2+
import tomllib
3+
from typing import Dict, Any
4+
5+
import aiohttp
6+
from loguru import logger
7+
8+
from WechatAPI import WechatAPIClient
9+
from utils.decorators import on_text_message
10+
from utils.plugin_base import PluginBase
11+
12+
13+
class DouyinParserError(Exception):
14+
"""抖音解析器自定义异常基类"""
15+
pass
16+
17+
18+
class DouyinParser(PluginBase):
19+
description = "抖音无水印解析插件"
20+
author = "姜不吃先生" # 群友太给力了!
21+
version = "1.0.2"
22+
23+
def __init__(self):
24+
super().__init__()
25+
self.url_pattern = re.compile(r'https?://v\.douyin\.com/\w+/?')
26+
27+
# 读取代理配置
28+
with open("plugins/all_in_one_config.toml", "rb") as f:
29+
config = tomllib.load(f)
30+
self.http_proxy = config.get("Douyin", {}).get("http-proxy", None)
31+
32+
def _clean_response_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
33+
"""清理响应数据"""
34+
if not data:
35+
return data
36+
37+
# 使用固定的抖音图标作为封面
38+
data[
39+
'cover'] = "https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7c/49/e1/7c49e1af-ce92-d1c4-9a93-0a316e47ba94/AppIcon_TikTok-0-0-1x_U007epad-0-1-0-0-85-220.png/512x512bb.jpg"
40+
41+
return data
42+
43+
async def _get_real_video_url(self, video_url: str) -> str:
44+
"""获取真实视频链接"""
45+
try:
46+
async with aiohttp.ClientSession(proxy=self.http_proxy) as session:
47+
async with session.get(video_url, allow_redirects=True, timeout=30) as response:
48+
if response.status == 200:
49+
return str(response.url)
50+
else:
51+
logger.error(f"获取视频真实链接失败: {response.status}")
52+
return video_url
53+
except Exception as e:
54+
logger.error(f"获取视频真实链接时出错: {str(e)}")
55+
return video_url
56+
57+
async def _parse_douyin(self, url: str) -> Dict[str, Any]:
58+
"""调用抖音解析API"""
59+
try:
60+
api_url = "https://apih.kfcgw50.me/api/douyin"
61+
params = {
62+
'url': url,
63+
'type': 'json'
64+
}
65+
66+
logger.debug(f"开始解析抖音链接: {url}")
67+
logger.debug(f"请求API: {api_url}, 参数: {params}")
68+
69+
async with aiohttp.ClientSession() as session: # 解析不使用代理
70+
async with session.get(api_url, params=params, timeout=30) as response:
71+
if response.status != 200:
72+
raise DouyinParserError(f"API请求失败,状态码: {response.status}")
73+
74+
data = await response.json()
75+
logger.debug(f"原始API响应数据: {data}")
76+
77+
if data.get("code") == 200:
78+
result = data.get("data", {})
79+
80+
# 获取真实视频链接
81+
if result.get('video'):
82+
# 使用代理访问视频链接获取真实URL
83+
result['video'] = await self._get_real_video_url(result['video'])
84+
85+
result = self._clean_response_data(result)
86+
logger.debug(f"清理后的数据: {result}")
87+
return result
88+
else:
89+
raise DouyinParserError(data.get("message", "未知错误"))
90+
91+
except aiohttp.ClientTimeout:
92+
logger.error(f"API请求超时: {api_url}")
93+
raise DouyinParserError("解析超时,请稍后重试")
94+
except aiohttp.ClientError as e:
95+
logger.error(f"API请求错误: {str(e)}")
96+
raise DouyinParserError(f"网络请求失败: {str(e)}")
97+
except Exception as e:
98+
logger.error(f"解析过程发生错误: {str(e)}, URL: {url}")
99+
raise DouyinParserError(f"解析失败: {str(e)}")
100+
101+
async def _send_test_card(self, bot: WechatAPIClient, chat_id: str, sender: str):
102+
"""发送测试卡片消息"""
103+
try:
104+
# 测试数据
105+
test_data = {
106+
'video': 'https://v11-cold.douyinvod.com/c183ceff049f008265680819dbd8ac0a/67b206c0/video/tos/cn/tos-cn-ve-15/ok8JumeiqAI3pJ2nAiQE9rBiTfm1KtADABlBgV/?a=1128&ch=0&cr=0&dr=0&cd=0%7C0%7C0%7C0&cv=1&br=532&bt=532&cs=0&ds=3&ft=H4NIyvvBQx9Uf8ym8Z.6TQjSYE7OYMDtGkd~P4Aq8_45a&mime_type=video_mp4&qs=0&rc=ZzU5NTRnNDw1aGc5aDloZkBpanE4M3Y5cjNkeDMzNGkzM0AuLy1fLWFhXjQxNjFgYzRiYSNmXzZlMmRjcmdgLS1kLTBzcw%3D%3D&btag=80010e000ad000&cquery=100y&dy_q=1739716635&feature_id=aa7df520beeae8e397df15f38df0454c&l=20250216223715047FF68C05B9F67E1F19',
107+
'title': '测试视频标题',
108+
'name': '测试作者',
109+
'cover': 'https://is1-ssl.mzstatic.com/image/thumb/Purple221/v4/7c/49/e1/7c49e1af-ce92-d1c4-9a93-0a316e47ba94/AppIcon_TikTok-0-0-1x_U007epad-0-1-0-0-85-220.png/512x512bb.jpg'
110+
}
111+
112+
logger.info("开始发送测试卡片")
113+
logger.debug(f"测试数据: {test_data}")
114+
115+
# 发送测试卡片
116+
await bot.send_link_message(
117+
wxid=chat_id,
118+
url=test_data['video'],
119+
title=f"{test_data['title'][:30]} - {test_data['name'][:10]}",
120+
description="这是一个测试卡片消息",
121+
thumb_url=test_data['cover']
122+
)
123+
124+
logger.info("测试卡片发送成功")
125+
126+
# 发送详细信息
127+
debug_msg = (
128+
"🔍 测试卡片详情:\n"
129+
f"视频链接: {test_data['video']}\n"
130+
f"封面链接: {test_data['cover']}\n"
131+
f"标题: {test_data['title']} - {test_data['name']}"
132+
)
133+
await bot.send_text_message(
134+
wxid=chat_id,
135+
content=debug_msg,
136+
at=[sender]
137+
)
138+
139+
except Exception as e:
140+
error_msg = f"测试卡片发送失败: {str(e)}"
141+
logger.error(error_msg)
142+
await bot.send_text_message(
143+
wxid=chat_id,
144+
content=error_msg,
145+
at=[sender]
146+
)
147+
148+
@on_text_message
149+
async def handle_douyin_links(self, bot: WechatAPIClient, message: dict):
150+
content = message['Content']
151+
sender = message['SenderWxid']
152+
chat_id = message['FromWxid']
153+
154+
# 添加测试命令识别
155+
if content.strip() == "测试卡片":
156+
await self._send_test_card(bot, chat_id, sender)
157+
return
158+
159+
try:
160+
# 提取抖音链接
161+
match = self.url_pattern.search(content)
162+
if not match:
163+
return
164+
165+
original_url = match.group(0)
166+
logger.info(f"发现抖音链接: {original_url}")
167+
168+
# 解析视频信息
169+
video_info = await self._parse_douyin(original_url)
170+
171+
if not video_info:
172+
raise DouyinParserError("无法获取视频信息")
173+
174+
# 获取视频信息
175+
video_url = video_info.get('video', '')
176+
title = video_info.get('title', '无标题')
177+
author = video_info.get('name', '未知作者')
178+
cover = video_info.get('cover', '')
179+
180+
if not video_url:
181+
raise DouyinParserError("无法获取视频地址")
182+
183+
# 发送文字版消息
184+
text_msg = (
185+
f"🎬 解析成功\n"
186+
f"标题:{title}\n"
187+
f"作者:{author}\n"
188+
f"封面:{cover}\n"
189+
f"无水印链接:{video_url}"
190+
)
191+
await bot.send_text_message(
192+
wxid=chat_id,
193+
content=text_msg,
194+
at=[sender]
195+
)
196+
197+
# 发送卡片版消息
198+
await bot.send_link_message(
199+
wxid=chat_id,
200+
url=video_url,
201+
title=f"{title[:30]} - {author[:10]}" if author else title[:40],
202+
description="点击观看无水印视频",
203+
thumb_url=cover
204+
)
205+
206+
logger.info(f"已发送解析结果: 标题[{title}] 作者[{author}]")
207+
208+
except (DouyinParserError, Exception) as e:
209+
error_msg = str(e) if str(e) else "未知错误"
210+
logger.error(f"抖音解析失败: {error_msg}")
211+
await bot.send_text_message(
212+
wxid=chat_id,
213+
content=f"视频解析失败: {error_msg}",
214+
at=[sender]
215+
)

0 commit comments

Comments
 (0)