diff --git a/skills/kb-link-parser/link-parser.py b/skills/kb-link-parser/link-parser.py index 54f52b4..ce00cec 100644 --- a/skills/kb-link-parser/link-parser.py +++ b/skills/kb-link-parser/link-parser.py @@ -6,7 +6,8 @@ Supports: - Bilibili (video, article, up主, collection, series) - YouTube (video, channel, playlist) - Zhihu (question, answer, user, collection) -- Short links (b23.tv, etc.) +- Xiaohongshu (note, user) +- Short links (b23.tv, xhslink.com, etc.) Usage: python link-parser.py --url "https://www.bilibili.com/video/BV1xx411c7mD" @@ -44,6 +45,13 @@ class LinkParser: YOUTUBE_USER_PATTERN = re.compile(r'(?:https?://)?(?:www\.)?youtube\.com/user/([a-zA-Z0-9_-]+)') YOUTUBE_PLAYLIST_PATTERN = re.compile(r'(?:https?://)?(?:www\.)?youtube\.com/playlist\?list=([a-zA-Z0-9_-]+)') + # Xiaohongshu (小红书) patterns + XIAOHONGSHU_SHORT_PATTERN = re.compile(r'https?://xhslink\.com/o/([a-zA-Z0-9]+)') + XIAOHONGSHU_NOTE_PATTERN = re.compile(r'https?://(?:www\.)?xiaohongshu\.com/explore/([a-zA-Z0-9]+)') + XIAOHONGSHU_NOTE_PATTERN2 = re.compile(r'https?://(?:www\.)?xiaohongshu\.com/notes/([a-zA-Z0-9]+)') + XIAOHONGSHU_DISCOVERY_PATTERN = re.compile(r'https?://(?:www\.)?xiaohongshu\.com/discovery/item/([a-zA-Z0-9]+)') + XIAOHONGSHU_USER_PATTERN = re.compile(r'https?://(?:www\.)?xiaohongshu\.com/user/profile/(\d+)') + # Zhihu patterns ZHIHAU_QUESTION_PATTERN = re.compile(r'https?://(?:www\.)?zhihu\.com/question/(\d+)') ZHIHAU_ANSWER_PATTERN = re.compile(r'https?://(?:www\.)?zhihu\.com/question/\d+/answer/(\d+)') @@ -84,6 +92,15 @@ class LinkParser: # Fallback: return original URL return url + # Check if it's Xiaohongshu short link + if self.XIAOHONGSHU_SHORT_PATTERN.match(url): + try: + import requests + response = requests.get(url, allow_redirects=True, timeout=10) + return response.url + except Exception: + return url + return url def extract_bilibili_id(self, url: str) -> Optional[Dict[str, str]]: @@ -361,6 +378,63 @@ class LinkParser: return None + def extract_xiaohongshu_id(self, url: str) -> Optional[Dict[str, str]]: + """ + Extract Xiaohongshu ID from URL. + + Args: + url: Xiaohongshu URL + + Returns: + Dict with platform, type, and id fields, or None if not matched + """ + # Try short link + match = self.XIAOHONGSHU_SHORT_PATTERN.match(url) + if match: + return { + "platform": "xiaohongshu", + "type": "note", + "id": match.group(1) + } + + # Try explore link + match = self.XIAOHONGSHU_NOTE_PATTERN.match(url) + if match: + return { + "platform": "xiaohongshu", + "type": "note", + "id": match.group(1) + } + + # Try notes link + match = self.XIAOHONGSHU_NOTE_PATTERN2.match(url) + if match: + return { + "platform": "xiaohongshu", + "type": "note", + "id": match.group(1) + } + + # Try discovery item link (resolved from short link) + match = self.XIAOHONGSHU_DISCOVERY_PATTERN.match(url) + if match: + return { + "platform": "xiaohongshu", + "type": "note", + "id": match.group(1) + } + + # Try user profile + match = self.XIAOHONGSHU_USER_PATTERN.match(url) + if match: + return { + "platform": "xiaohongshu", + "type": "user", + "id": match.group(1) + } + + return None + def parse(self, url: str) -> Dict[str, Any]: """ Parse URL and extract platform-specific ID. @@ -406,6 +480,13 @@ class LinkParser: result["success"] = True return result + # Try Xiaohongshu + xiaohongshu_result = self.extract_xiaohongshu_id(url) + if xiaohongshu_result: + result.update(xiaohongshu_result) + result["success"] = True + return result + # Check for short links that need resolution short_links = self.shortlink_resolver.detect_short_links(url) if short_links: