新增渠道: - Instagram: 基于 instaloader (⭐9.8K),读取帖子/Profile,Cookie 登录 - LinkedIn: 基于 linkedin-scraper-mcp (⭐900+) MCP 服务,Jina Reader fallback - Boss直聘: 基于 mcp-bosszp MCP 服务,Jina Reader fallback 代码改动: - 新建 channels/instagram.py, linkedin.py, bosszhipin.py - 注册到 channels/__init__.py - cli.py 添加 search-instagram/linkedin/bosszhipin 子命令 - cli.py 安装逻辑添加 instaloader 自动安装 - core.py 添加 search_instagram/linkedin/bosszhipin 方法 - README.md + docs/README_en.md 更新平台表格和选型表格 - docs/install.md 添加三个新渠道的配置说明和 Quick Reference
75 lines
2 KiB
Python
75 lines
2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Channel registry — routes URLs to the right channel.
|
|
|
|
This is the core of Agent Reach' pluggable architecture.
|
|
Add a new channel: just create a file and register it here.
|
|
Swap a backend: just change the implementation inside the channel file.
|
|
"""
|
|
|
|
from typing import Dict, List, Optional
|
|
from .base import Channel, ReadResult, SearchResult
|
|
|
|
# Import all channels
|
|
from .web import WebChannel
|
|
from .github import GitHubChannel
|
|
from .twitter import TwitterChannel
|
|
from .youtube import YouTubeChannel
|
|
from .reddit import RedditChannel
|
|
from .rss import RSSChannel
|
|
from .bilibili import BilibiliChannel
|
|
from .exa_search import ExaSearchChannel
|
|
from .xiaohongshu import XiaoHongShuChannel
|
|
from .instagram import InstagramChannel
|
|
from .linkedin import LinkedInChannel
|
|
from .bosszhipin import BossZhipinChannel
|
|
|
|
|
|
# Channel registry — order matters (first match wins, web is last as fallback)
|
|
ALL_CHANNELS: List[Channel] = [
|
|
GitHubChannel(),
|
|
TwitterChannel(),
|
|
YouTubeChannel(),
|
|
RedditChannel(),
|
|
BilibiliChannel(),
|
|
XiaoHongShuChannel(),
|
|
InstagramChannel(),
|
|
LinkedInChannel(),
|
|
BossZhipinChannel(),
|
|
RSSChannel(),
|
|
ExaSearchChannel(),
|
|
WebChannel(), # Fallback — handles any URL
|
|
]
|
|
|
|
# Search-capable channels
|
|
SEARCH_CHANNELS: Dict[str, Channel] = {
|
|
ch.name: ch for ch in ALL_CHANNELS if ch.can_search()
|
|
}
|
|
|
|
|
|
def get_channel_for_url(url: str) -> Channel:
|
|
"""Find the right channel for a URL."""
|
|
for channel in ALL_CHANNELS:
|
|
if channel.can_handle(url):
|
|
return channel
|
|
return WebChannel() # Should never reach here, but just in case
|
|
|
|
|
|
def get_channel(name: str) -> Optional[Channel]:
|
|
"""Get a channel by name."""
|
|
for ch in ALL_CHANNELS:
|
|
if ch.name == name:
|
|
return ch
|
|
return None
|
|
|
|
|
|
def get_all_channels() -> List[Channel]:
|
|
"""Get all registered channels."""
|
|
return ALL_CHANNELS
|
|
|
|
|
|
__all__ = [
|
|
"Channel", "ReadResult", "SearchResult",
|
|
"ALL_CHANNELS", "SEARCH_CHANNELS",
|
|
"get_channel_for_url", "get_channel", "get_all_channels",
|
|
]
|