feat(obsidian-kb): 实现短链接解析功能
- 新增 shortlink_resolver.py 模块 * 检测 B站短链接(b23.tv)和其他短链接服务 * 提供友好的用户引导信息 * 支持 Playwright 浏览器自动化解析(可选) - 集成到 handler.py * 短链接自动检测和提示 * 智能区分短链接和完整 URL - 更新 add_video.py * 支持短链接解析 * 添加 --playwright 参数启用浏览器解析 - 新增 test_shortlink.py 测试脚本 * 测试单个短链接检测 * 测试多个短链接检测 * 测试混合内容场景 * 所有测试通过 - 更新文档 * SKILL.md: 添加短链接说明 * AUTO_TRIGGER.md: 添加短链接章节 * obsidian-kb-improvements.md: 标记任务2为已完成 功能特点: - 方案A(用户引导):已实现并作为默认行为 - 方案B(浏览器自动化):提供 PlaywrightResolver 类,可选使用 - 方案C(代理池):未实现(复杂度高,收益低) 验收标准达成: ✅ 短链接能够友好处理(显示引导信息) ✅ 用户不会感到困惑 ✅ 支持 b23.tv 和其他短链接服务(检测) ✅ 集成到 handler.py ✅ 测试各种短链接场景
This commit is contained in:
@@ -176,12 +176,73 @@ handler.py 可以通过以下方式集成:
|
||||
|
||||
## 后续工作
|
||||
|
||||
### 短链接解析(P2 优先级)
|
||||
### 短链接解析(P2 优先级) ✅ 已完成
|
||||
|
||||
当前短链接仅显示提示,下一步可实现:
|
||||
- 使用浏览器自动化(Playwright)解析短链接
|
||||
- 实现代理池和请求头伪装
|
||||
- 缓存解析结果
|
||||
已完成短链接解析功能(2026-02-06):
|
||||
|
||||
#### 已实现功能
|
||||
|
||||
1. **短链接检测模块** (`shortlink_resolver.py`)
|
||||
- 检测 B站短链接(b23.tv)
|
||||
- 支持其他短链接服务(bit.ly, t.cn, dwz.cn 等)
|
||||
- 提供友好的用户引导信息
|
||||
|
||||
2. **用户引导(方案A)**
|
||||
- 检测到短链接时显示清晰的操作指南
|
||||
- 分别针对手机用户和桌面用户提供建议
|
||||
- 支持单个短链接和多个短链接的不同提示格式
|
||||
|
||||
3. **浏览器自动化(方案B - 可选)**
|
||||
- 提供了 PlaywrightResolver 类
|
||||
- 需要额外安装 Playwright:
|
||||
```bash
|
||||
pip install playwright
|
||||
playwright install chromium
|
||||
```
|
||||
- 可在 `add_video.py` 中使用 `--playwright` 参数启用
|
||||
|
||||
4. **集成到现有流程**
|
||||
- `handler.py` 集成短链接检测
|
||||
- `add_video.py` 支持短链接解析
|
||||
- 短链接会优先显示帮助信息,不会自动处理
|
||||
|
||||
#### 使用示例
|
||||
|
||||
**handler.py 自动触发:**
|
||||
```bash
|
||||
python3 handler.py "Check this: https://b23.tv/oLBZZfK"
|
||||
```
|
||||
|
||||
**add_video.py 手动添加:**
|
||||
```bash
|
||||
python3 add_video.py "https://b23.tv/oLBZZfK"
|
||||
python3 add_video.py "https://b23.tv/oLBZZfK" --playwright # 使用浏览器解析
|
||||
```
|
||||
|
||||
**shortlink_resolver.py 独立测试:**
|
||||
```bash
|
||||
python3 shortlink_resolver.py "Test link: https://b23.tv/abc"
|
||||
python3 shortlink_resolver.py "https://b23.tv/abc" --playwright # 浏览器解析
|
||||
```
|
||||
|
||||
#### 文件变更
|
||||
|
||||
```
|
||||
skills/obsidian-kb/
|
||||
├── SKILL.md [修改] 添加短链接说明
|
||||
├── handler.py [修改] 集成短链接检测
|
||||
├── add_video.py [修改] 支持短链接解析
|
||||
├── shortlink_resolver.py [新增] 短链接解析模块
|
||||
└── AUTO_TRIGGER.md [修改] 添加短链接章节
|
||||
```
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- [x] 短链接能够友好处理(显示引导信息)
|
||||
- [x] 用户不会感到困惑
|
||||
- [x] 支持 b23.tv 和其他短链接服务(检测)
|
||||
- [x] 集成到 handler.py
|
||||
- [x] 测试各种短链接场景(单个、多个、混合)
|
||||
|
||||
### YouTube 和知乎支持(P1 优先级)
|
||||
|
||||
|
||||
@@ -55,13 +55,17 @@ author: OpenClaw
|
||||
### 短链接友好提示
|
||||
|
||||
检测到短链接时,显示引导信息:
|
||||
|
||||
**单个短链接:**
|
||||
```
|
||||
🔗 检测到短链接
|
||||
|
||||
⚠️ 由于反爬虫保护,短链接无法自动解析
|
||||
|
||||
📍 短链接: https://b23.tv/xxxxx
|
||||
|
||||
📱 手机用户:
|
||||
1. 打开链接
|
||||
1. 点击/打开链接
|
||||
2. 在浏览器地址栏复制完整 URL
|
||||
3. 发送给助手
|
||||
|
||||
@@ -72,6 +76,30 @@ author: OpenClaw
|
||||
💡 提示:完整 URL 格式为:https://www.bilibili.com/video/BVxxxxx
|
||||
```
|
||||
|
||||
**多个短链接:**
|
||||
```
|
||||
⚠️ 检测到短链接
|
||||
|
||||
[1] B站短链接: https://b23.tv/abc123
|
||||
需要完整URL或BV号
|
||||
|
||||
[2] bit.ly: https://bit.ly/test
|
||||
需要完整URL
|
||||
|
||||
💡 建议:
|
||||
- 在浏览器打开链接后复制完整URL
|
||||
- 或者直接发送BV号(仅限B站)
|
||||
```
|
||||
|
||||
**技术说明:**
|
||||
- B站短链接(b23.tv)有反爬虫保护,无法直接解析
|
||||
- 本技能使用 `shortlink_resolver.py` 模块检测短链接
|
||||
- 提供 Playwright 可选方案(需额外安装)
|
||||
```bash
|
||||
pip install playwright
|
||||
playwright install chromium
|
||||
```
|
||||
|
||||
## 命令列表
|
||||
|
||||
| 命令 | 描述 | 用法 |
|
||||
|
||||
Regular → Executable
+45
-11
@@ -13,16 +13,22 @@ sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'src
|
||||
from sources.bilibili import BilibiliAdapter
|
||||
from models import Content, ContentStatus
|
||||
from storage import Storage
|
||||
from shortlink_resolver import (
|
||||
ShortLinkResolver,
|
||||
PlaywrightResolver,
|
||||
detect_and_format
|
||||
)
|
||||
|
||||
def resolve_short_url(url: str) -> str:
|
||||
def resolve_short_url(url: str, try_playwright: bool = False) -> str:
|
||||
"""
|
||||
解析短链接(如 b23.tv)到完整URL,或接受BV号
|
||||
|
||||
Args:
|
||||
url: 可能是短链接的URL、完整URL或BV号
|
||||
try_playwright: 是否尝试使用Playwright解析短链接
|
||||
|
||||
Returns:
|
||||
str: 完整URL或BV号
|
||||
str: 完整URL或BV号,如果失败返回None
|
||||
"""
|
||||
# 如果已经是完整URL,直接返回
|
||||
if 'bilibili.com/video/' in url:
|
||||
@@ -33,23 +39,42 @@ def resolve_short_url(url: str) -> str:
|
||||
return url
|
||||
|
||||
# 如果是短链接,尝试解析
|
||||
if 'b23.tv' in url:
|
||||
resolver = ShortLinkResolver()
|
||||
|
||||
if resolver.is_bilibili_short_link(url):
|
||||
print(f" ⚠️ 检测到短链接 {url}")
|
||||
print(f" 💡 B站对短链接有反爬虫保护,请提供:")
|
||||
print(f" 1. 完整URL: https://www.bilibili.com/video/BVxxxxx")
|
||||
print(f" 2. 或BV号: BVxxxxx")
|
||||
print(f"\n 提示: 可以在浏览器打开短链接,从地址栏复制完整URL")
|
||||
|
||||
# 尝试使用Playwright解析(如果启用)
|
||||
if try_playwright:
|
||||
print(f" 🔄 尝试使用浏览器解析...")
|
||||
playwright_resolver = PlaywrightResolver(headless=True)
|
||||
|
||||
try:
|
||||
resolved_url = playwright_resolver.resolve_bilibili_short(url)
|
||||
if resolved_url:
|
||||
print(f" ✅ 解析成功: {resolved_url}")
|
||||
return resolved_url
|
||||
else:
|
||||
print(f" ❌ 浏览器解析失败")
|
||||
except Exception as e:
|
||||
print(f" ❌ Playwright不可用或解析失败: {e}")
|
||||
|
||||
# 显示用户引导
|
||||
print("")
|
||||
print(resolver.format_short_link_help(url))
|
||||
print("")
|
||||
return None
|
||||
|
||||
# 默认返回原URL
|
||||
return url
|
||||
|
||||
def add_video_to_inbox(url: str) -> str:
|
||||
def add_video_to_inbox(url: str, try_playwright: bool = False) -> str:
|
||||
"""
|
||||
添加B站视频到收件箱
|
||||
|
||||
Args:
|
||||
url: B站视频URL
|
||||
try_playwright: 是否尝试使用Playwright解析短链接
|
||||
|
||||
Returns:
|
||||
str: Content ID
|
||||
@@ -57,7 +82,7 @@ def add_video_to_inbox(url: str) -> str:
|
||||
print(f"📥 正在处理视频: {url}")
|
||||
|
||||
# 解析短链接
|
||||
resolved_url = resolve_short_url(url)
|
||||
resolved_url = resolve_short_url(url, try_playwright=try_playwright)
|
||||
if resolved_url is None:
|
||||
return None
|
||||
|
||||
@@ -104,8 +129,17 @@ def add_video_to_inbox(url: str) -> str:
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 2:
|
||||
print("用法: python3 add_video.py <B站视频URL>")
|
||||
print("示例: python3 add_video.py https://b23.tv/oLBZZfK")
|
||||
print("示例:")
|
||||
print(" python3 add_video.py https://www.bilibili.com/video/BV1xx411c7mD")
|
||||
print(" python3 add_video.py BV1xx411c7mD")
|
||||
print(" python3 add_video.py https://b23.tv/oLBZZfK")
|
||||
print("\n选项:")
|
||||
print(" --playwright 尝试使用浏览器解析短链接")
|
||||
sys.exit(1)
|
||||
|
||||
url = sys.argv[1]
|
||||
add_video_to_inbox(url)
|
||||
try_playwright = '--playwright' in sys.argv
|
||||
if try_playwright and url == '--playwright' and len(sys.argv) > 2:
|
||||
url = sys.argv[2]
|
||||
|
||||
add_video_to_inbox(url, try_playwright=try_playwright)
|
||||
|
||||
@@ -16,6 +16,13 @@ from urllib.parse import urlparse
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'src'))
|
||||
|
||||
# Import short link resolver
|
||||
from shortlink_resolver import (
|
||||
ShortLinkResolver,
|
||||
detect_and_format,
|
||||
PlaywrightResolver
|
||||
)
|
||||
|
||||
# Pattern definitions for auto-detection
|
||||
PATTERNS = {
|
||||
'bilibili_full': re.compile(r'https?://(?:www\.)?bilibili\.com/video/(BV\w+)'),
|
||||
@@ -147,27 +154,17 @@ def format_preview(detected: List[Dict[str, str]]) -> str:
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def format_short_link_help() -> str:
|
||||
def format_short_link_help(short_link: Optional[str] = None) -> str:
|
||||
"""
|
||||
Format help message for short links.
|
||||
|
||||
Args:
|
||||
short_link: Optional short link URL to mention
|
||||
|
||||
Returns:
|
||||
Help message
|
||||
"""
|
||||
return """🔗 检测到短链接
|
||||
|
||||
⚠️ 由于反爬虫保护,短链接无法自动解析
|
||||
|
||||
📱 手机用户:
|
||||
1. 打开链接
|
||||
2. 在浏览器地址栏复制完整 URL
|
||||
3. 发送给助手
|
||||
|
||||
💻 桌面用户:
|
||||
1. 发送 BV 号(如:BV1jcFqz7EFW)
|
||||
2. 或使用完整 URL
|
||||
|
||||
💡 提示:完整 URL 格式为:https://www.bilibili.com/video/BVxxxxx"""
|
||||
return ShortLinkResolver.format_short_link_help(short_link)
|
||||
|
||||
|
||||
def get_action_prompt(detected: List[Dict[str, str]]) -> str:
|
||||
@@ -289,12 +286,19 @@ def handle_message(text: str) -> Optional[str]:
|
||||
Returns:
|
||||
Response message, or None if no action needed
|
||||
"""
|
||||
# Detect links
|
||||
# First, check for short links and provide help
|
||||
has_short, short_msg = detect_and_format(text)
|
||||
|
||||
# Detect all links (including short ones)
|
||||
detected = detect_links(text)
|
||||
|
||||
if not detected:
|
||||
return None
|
||||
|
||||
# If we have short links, show help message first
|
||||
if has_short:
|
||||
return short_msg
|
||||
|
||||
# Check if this is a confirmation (y/yes)
|
||||
text_lower = text.lower().strip()
|
||||
if text_lower in ('y', 'yes', '是', '好的', 'ok'):
|
||||
|
||||
Executable
+355
@@ -0,0 +1,355 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Short link resolver for obsidian-kb skill.
|
||||
|
||||
Provides utilities for detecting and resolving short links, particularly
|
||||
Bilibili short links (b23.tv) which may be blocked by anti-scraping measures.
|
||||
|
||||
Supported short link services:
|
||||
- Bilibili: b23.tv
|
||||
- Generic URL shorteners (detection only)
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Optional, Dict, List, Tuple
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
class ShortLinkResolver:
|
||||
"""Short link detection and resolution handler"""
|
||||
|
||||
# Pattern for Bilibili short links
|
||||
BILIBILI_SHORT_PATTERN = re.compile(r'https?://b23\.tv/([a-zA-Z0-9]+)')
|
||||
|
||||
# Pattern for other short link services
|
||||
OTHER_SHORT_PATTERNS = {
|
||||
'bit.ly': re.compile(r'https?://bit\.ly/\w+'),
|
||||
't.cn': re.compile(r'https?://t\.cn/\w+'),
|
||||
'dwz.cn': re.compile(r'https?://dwz\.cn/\w+'),
|
||||
'goo.gl': re.compile(r'https?://goo\.gl/\w+'),
|
||||
'tinyurl.com': re.compile(r'https?://tinyurl\.com/\w+'),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def detect_short_links(text: str) -> List[Dict[str, str]]:
|
||||
"""
|
||||
Detect short links in text.
|
||||
|
||||
Args:
|
||||
text: Input text to search
|
||||
|
||||
Returns:
|
||||
List of detected short links with metadata:
|
||||
[
|
||||
{
|
||||
'url': 'https://b23.tv/xxxxx',
|
||||
'platform': 'bilibili',
|
||||
'is_short': True
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
detected = []
|
||||
|
||||
# Check for Bilibili short links
|
||||
for match in ShortLinkResolver.BILIBILI_SHORT_PATTERN.finditer(text):
|
||||
detected.append({
|
||||
'url': match.group(0),
|
||||
'code': match.group(1),
|
||||
'platform': 'bilibili',
|
||||
'is_short': True
|
||||
})
|
||||
|
||||
# Check for other short link services
|
||||
for platform, pattern in ShortLinkResolver.OTHER_SHORT_PATTERNS.items():
|
||||
for match in pattern.finditer(text):
|
||||
detected.append({
|
||||
'url': match.group(0),
|
||||
'platform': platform,
|
||||
'is_short': True
|
||||
})
|
||||
|
||||
return detected
|
||||
|
||||
@staticmethod
|
||||
def is_bilibili_short_link(url: str) -> bool:
|
||||
"""
|
||||
Check if URL is a Bilibili short link.
|
||||
|
||||
Args:
|
||||
url: URL to check
|
||||
|
||||
Returns:
|
||||
True if URL is a b23.tv short link
|
||||
"""
|
||||
return bool(ShortLinkResolver.BILIBILI_SHORT_PATTERN.match(url))
|
||||
|
||||
@staticmethod
|
||||
def format_short_link_help(short_link: Optional[str] = None) -> str:
|
||||
"""
|
||||
Format help message for short links.
|
||||
|
||||
Args:
|
||||
short_link: Optional short link URL to mention
|
||||
|
||||
Returns:
|
||||
Help message
|
||||
"""
|
||||
lines = [
|
||||
"🔗 检测到短链接",
|
||||
"",
|
||||
"⚠️ 由于反爬虫保护,短链接无法自动解析",
|
||||
""
|
||||
]
|
||||
|
||||
if short_link:
|
||||
lines.append(f"📍 短链接: {short_link}")
|
||||
lines.append("")
|
||||
|
||||
lines.extend([
|
||||
"📱 手机用户:",
|
||||
" 1. 点击/打开链接",
|
||||
" 2. 在浏览器地址栏复制完整 URL",
|
||||
" 3. 发送给助手",
|
||||
"",
|
||||
"💻 桌面用户:",
|
||||
" 1. 发送 BV 号(如:BV1jcFqz7EFW)",
|
||||
" 2. 或使用完整 URL",
|
||||
"",
|
||||
"💡 提示:完整 URL 格式为:https://www.bilibili.com/video/BVxxxxx"
|
||||
])
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
@staticmethod
|
||||
def format_short_link_warning(short_links: List[Dict[str, str]]) -> str:
|
||||
"""
|
||||
Format warning message for multiple short links.
|
||||
|
||||
Args:
|
||||
short_links: List of short link metadata
|
||||
|
||||
Returns:
|
||||
Warning message
|
||||
"""
|
||||
if not short_links:
|
||||
return ""
|
||||
|
||||
lines = [
|
||||
"⚠️ 检测到短链接",
|
||||
""
|
||||
]
|
||||
|
||||
for i, link in enumerate(short_links, 1):
|
||||
platform = link.get('platform', 'unknown')
|
||||
url = link['url']
|
||||
|
||||
if platform == 'bilibili':
|
||||
lines.append(f" [{i}] B站短链接: {url}")
|
||||
lines.append(" 需要完整URL或BV号")
|
||||
else:
|
||||
lines.append(f" [{i}] {platform}: {url}")
|
||||
lines.append(" 需要完整URL")
|
||||
|
||||
lines.append("")
|
||||
|
||||
lines.extend([
|
||||
"💡 建议:",
|
||||
" - 在浏览器打开链接后复制完整URL",
|
||||
" - 或者直接发送BV号(仅限B站)"
|
||||
])
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class PlaywrightResolver:
|
||||
"""
|
||||
Browser-based short link resolver using Playwright.
|
||||
|
||||
This is an optional implementation for environments where Playwright
|
||||
is available. It can bypass anti-scraping measures by simulating
|
||||
a real browser.
|
||||
|
||||
Requirements:
|
||||
pip install playwright
|
||||
playwright install chromium
|
||||
"""
|
||||
|
||||
def __init__(self, headless: bool = True, timeout: int = 10000):
|
||||
"""
|
||||
Initialize Playwright resolver.
|
||||
|
||||
Args:
|
||||
headless: Whether to run browser in headless mode
|
||||
timeout: Page navigation timeout in milliseconds
|
||||
"""
|
||||
self.headless = headless
|
||||
self.timeout = timeout
|
||||
self._playwright = None
|
||||
|
||||
def _check_playwright_available(self) -> bool:
|
||||
"""Check if Playwright is installed and available"""
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def resolve(self, short_url: str) -> Optional[str]:
|
||||
"""
|
||||
Resolve short link using Playwright browser automation.
|
||||
|
||||
Args:
|
||||
short_url: Short URL to resolve
|
||||
|
||||
Returns:
|
||||
Final URL after redirects, or None if resolution fails
|
||||
"""
|
||||
if not self._check_playwright_available():
|
||||
raise ImportError(
|
||||
"Playwright not installed. Install with: pip install playwright && playwright install chromium"
|
||||
)
|
||||
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
try:
|
||||
with sync_playwright() as p:
|
||||
# Launch browser
|
||||
browser = p.chromium.launch(headless=self.headless)
|
||||
context = browser.new_context(
|
||||
user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
||||
viewport={'width': 1920, 'height': 1080}
|
||||
)
|
||||
page = context.new_page()
|
||||
|
||||
# Navigate to short URL
|
||||
page.goto(short_url, timeout=self.timeout, wait_until='networkidle')
|
||||
|
||||
# Get final URL
|
||||
final_url = page.url
|
||||
|
||||
# Close browser
|
||||
browser.close()
|
||||
|
||||
return final_url
|
||||
|
||||
except Exception as e:
|
||||
print(f"Playwright resolution failed: {e}")
|
||||
return None
|
||||
|
||||
def resolve_bilibili_short(self, short_url: str) -> Optional[str]:
|
||||
"""
|
||||
Resolve Bilibili short link and extract BV ID.
|
||||
|
||||
Args:
|
||||
short_url: Bilibili short URL (e.g., https://b23.tv/xxxxx)
|
||||
|
||||
Returns:
|
||||
Full Bilibili URL with BV ID, or None if resolution fails
|
||||
"""
|
||||
final_url = self.resolve(short_url)
|
||||
if not final_url:
|
||||
return None
|
||||
|
||||
# Check if final URL contains BV ID
|
||||
if 'bilibili.com/video/' in final_url:
|
||||
return final_url
|
||||
|
||||
# Extract BV ID if present in URL
|
||||
bv_pattern = re.compile(r'(BV[a-zA-Z0-9]{10})')
|
||||
match = bv_pattern.search(final_url)
|
||||
if match:
|
||||
return f"https://www.bilibili.com/video/{match.group(1)}"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_resolver() -> ShortLinkResolver:
|
||||
"""
|
||||
Get the default resolver instance.
|
||||
|
||||
Returns:
|
||||
ShortLinkResolver instance
|
||||
"""
|
||||
return ShortLinkResolver()
|
||||
|
||||
|
||||
def detect_and_format(text: str) -> Tuple[bool, Optional[str]]:
|
||||
"""
|
||||
Detect short links in text and format appropriate message.
|
||||
|
||||
Args:
|
||||
text: Input text to search
|
||||
|
||||
Returns:
|
||||
Tuple of (has_short_links, formatted_message)
|
||||
- has_short_links: True if short links detected
|
||||
- formatted_message: Help/warning message if short links found, None otherwise
|
||||
"""
|
||||
resolver = ShortLinkResolver()
|
||||
short_links = resolver.detect_short_links(text)
|
||||
|
||||
if not short_links:
|
||||
return False, None
|
||||
|
||||
# Determine appropriate message based on number and type of links
|
||||
if len(short_links) == 1:
|
||||
link = short_links[0]
|
||||
if link['platform'] == 'bilibili':
|
||||
return True, resolver.format_short_link_help(link['url'])
|
||||
else:
|
||||
return True, resolver.format_short_link_warning([link])
|
||||
else:
|
||||
return True, resolver.format_short_link_warning(short_links)
|
||||
|
||||
|
||||
# CLI interface for testing
|
||||
def main():
|
||||
"""CLI for testing the short link resolver."""
|
||||
import sys
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python3 shortlink_resolver.py 'text to test'")
|
||||
print("\nExamples:")
|
||||
print(' python3 shortlink_resolver.py "Check this: https://b23.tv/oLBZZfK"')
|
||||
print(' python3 shortlink_resolver.py "https://bit.ly/xxxxx"')
|
||||
print('\nOptions:')
|
||||
print(' --playwright Try to resolve short links using Playwright')
|
||||
sys.exit(1)
|
||||
|
||||
text = ' '.join(sys.argv[1:])
|
||||
|
||||
# Check if we should try Playwright
|
||||
use_playwright = '--playwright' in sys.argv
|
||||
if use_playwright:
|
||||
text = text.replace('--playwright', '').strip()
|
||||
resolver = PlaywrightResolver()
|
||||
|
||||
# Find short links
|
||||
short_links = ShortLinkResolver.detect_short_links(text)
|
||||
|
||||
for link in short_links:
|
||||
url = link['url']
|
||||
print(f"Resolving: {url}")
|
||||
|
||||
if link['platform'] == 'bilibili':
|
||||
resolved = resolver.resolve_bilibili_short(url)
|
||||
else:
|
||||
resolved = resolver.resolve(url)
|
||||
|
||||
if resolved:
|
||||
print(f" → {resolved}")
|
||||
else:
|
||||
print(f" ✗ Resolution failed")
|
||||
else:
|
||||
# Just detect and show help
|
||||
has_links, message = detect_and_format(text)
|
||||
|
||||
if has_links:
|
||||
print(message)
|
||||
else:
|
||||
print("No short links detected")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Executable
+184
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test script for short link resolver functionality.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from shortlink_resolver import ShortLinkResolver, detect_and_format
|
||||
|
||||
|
||||
def test_single_bilibili_short():
|
||||
"""Test single Bilibili short link detection."""
|
||||
print("Test 1: Single Bilibili short link")
|
||||
print("=" * 50)
|
||||
|
||||
text = "Check this video: https://b23.tv/oLBZZfK"
|
||||
has_links, message = detect_and_format(text)
|
||||
|
||||
print(f"Input: {text}")
|
||||
print(f"Has short links: {has_links}")
|
||||
if message:
|
||||
print(f"Message:\n{message}")
|
||||
print()
|
||||
|
||||
assert has_links, "Should detect short link"
|
||||
assert message, "Should have help message"
|
||||
print("✅ Test 1 passed\n")
|
||||
|
||||
|
||||
def test_multiple_short_links():
|
||||
"""Test multiple short links detection."""
|
||||
print("Test 2: Multiple short links")
|
||||
print("=" * 50)
|
||||
|
||||
text = "Check these: https://b23.tv/abc123 and https://bit.ly/test"
|
||||
has_links, message = detect_and_format(text)
|
||||
|
||||
print(f"Input: {text}")
|
||||
print(f"Has short links: {has_links}")
|
||||
if message:
|
||||
print(f"Message:\n{message}")
|
||||
print()
|
||||
|
||||
assert has_links, "Should detect short links"
|
||||
assert message, "Should have warning message"
|
||||
assert "[1]" in message, "Should show first link"
|
||||
assert "[2]" in message, "Should show second link"
|
||||
print("✅ Test 2 passed\n")
|
||||
|
||||
|
||||
def test_no_short_links():
|
||||
"""Test text with no short links."""
|
||||
print("Test 3: No short links")
|
||||
print("=" * 50)
|
||||
|
||||
text = "Here is a full URL: https://www.bilibili.com/video/BV1xx411c7mD"
|
||||
has_links, message = detect_and_format(text)
|
||||
|
||||
print(f"Input: {text}")
|
||||
print(f"Has short links: {has_links}")
|
||||
if message:
|
||||
print(f"Message: {message}")
|
||||
else:
|
||||
print("Message: None")
|
||||
print()
|
||||
|
||||
assert not has_links, "Should not detect short links"
|
||||
assert not message, "Should not have help message"
|
||||
print("✅ Test 3 passed\n")
|
||||
|
||||
|
||||
def test_bv_code():
|
||||
"""Test BV code (should not be treated as short link)."""
|
||||
print("Test 4: BV code")
|
||||
print("=" * 50)
|
||||
|
||||
text = "BV1jcFqz7EFW"
|
||||
has_links, message = detect_and_format(text)
|
||||
|
||||
print(f"Input: {text}")
|
||||
print(f"Has short links: {has_links}")
|
||||
if message:
|
||||
print(f"Message: {message}")
|
||||
else:
|
||||
print("Message: None")
|
||||
print()
|
||||
|
||||
assert not has_links, "BV code should not be treated as short link"
|
||||
assert not message, "Should not have help message"
|
||||
print("✅ Test 4 passed\n")
|
||||
|
||||
|
||||
def test_mixed_content():
|
||||
"""Test mixed content with short and full links."""
|
||||
print("Test 5: Mixed content")
|
||||
print("=" * 50)
|
||||
|
||||
text = """
|
||||
Watch these videos:
|
||||
https://www.bilibili.com/video/BV1xx411c7mD
|
||||
https://b23.tv/short123
|
||||
BV2yy522c8nE
|
||||
"""
|
||||
|
||||
has_links, message = detect_and_format(text)
|
||||
|
||||
print(f"Input:{text}")
|
||||
print(f"Has short links: {has_links}")
|
||||
if message:
|
||||
print(f"Message:\n{message}")
|
||||
print()
|
||||
|
||||
assert has_links, "Should detect short link"
|
||||
assert message, "Should have help message"
|
||||
print("✅ Test 5 passed\n")
|
||||
|
||||
|
||||
def test_resolver_direct():
|
||||
"""Test ShortLinkResolver directly."""
|
||||
print("Test 6: Direct resolver usage")
|
||||
print("=" * 50)
|
||||
|
||||
resolver = ShortLinkResolver()
|
||||
|
||||
# Test detection
|
||||
short_links = resolver.detect_short_links("Check https://b23.tv/test123")
|
||||
print(f"Detected: {short_links}")
|
||||
|
||||
assert len(short_links) == 1, "Should detect one short link"
|
||||
assert short_links[0]['platform'] == 'bilibili', "Should be Bilibili"
|
||||
assert short_links[0]['is_short'] == True, "Should be short link"
|
||||
|
||||
# Test format_short_link_help
|
||||
help_msg = resolver.format_short_link_help("https://b23.tv/test123")
|
||||
print(f"Help message:\n{help_msg}")
|
||||
|
||||
assert "短链接" in help_msg, "Should mention short link"
|
||||
assert "BV" in help_msg, "Should mention BV code"
|
||||
|
||||
# Test format_short_link_warning
|
||||
warning_msg = resolver.format_short_link_warning(short_links)
|
||||
print(f"Warning message:\n{warning_msg}")
|
||||
|
||||
assert "[1]" in warning_msg, "Should show numbered list"
|
||||
|
||||
print()
|
||||
print("✅ Test 6 passed\n")
|
||||
|
||||
|
||||
def run_all_tests():
|
||||
"""Run all tests."""
|
||||
print("\n" + "=" * 50)
|
||||
print("Running Short Link Resolver Tests")
|
||||
print("=" * 50 + "\n")
|
||||
|
||||
try:
|
||||
test_single_bilibili_short()
|
||||
test_multiple_short_links()
|
||||
test_no_short_links()
|
||||
test_bv_code()
|
||||
test_mixed_content()
|
||||
test_resolver_direct()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("✅ All tests passed!")
|
||||
print("=" * 50 + "\n")
|
||||
return 0
|
||||
|
||||
except AssertionError as e:
|
||||
print(f"\n❌ Test failed: {e}\n")
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"\n❌ Unexpected error: {e}\n")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(run_all_tests())
|
||||
Reference in New Issue
Block a user