[teamai] Push 1 resource(s) from root
This commit is contained in:
@@ -0,0 +1,320 @@
|
||||
"""
|
||||
热点新闻摘要(增强版) - News Digest Module
|
||||
|
||||
功能:
|
||||
- 15种标签分类:科技、互联网、游戏、娱乐、社会、财经、汽车、体育、教育、健康、国际、房产、数码、时尚、美食
|
||||
- AI主动引导用户选择标签
|
||||
- 按标签获取和合并热榜
|
||||
"""
|
||||
|
||||
from typing import List, Dict, Optional, Any
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
import asyncio
|
||||
|
||||
# 标签到平台的映射
|
||||
TAG_MAPPING: Dict[str, List[str]] = {
|
||||
"科技": ["ithome", "36kr", "sspai", "csdn", "juejin", "51cto", "oschina", "infoq"],
|
||||
"互联网": ["sina-news", "netease-news", "qq-news", "sohu-news", "ifeng"],
|
||||
"游戏": ["genshin", "miyoushe", "lol", "hupu", "bilibili", "douyu", "huya", "netease-game"],
|
||||
"娱乐": ["weibo", "douban-group", "douban-movie", "mtime", "movie"],
|
||||
"社会": ["sina-news", "netease-news", "qq-news", "sohu-news", "ifeng", "qq"],
|
||||
"财经": ["sina-money", "eastmoney", "xueqiu", "jrj", "cnstock", "wallstreetcn"],
|
||||
"汽车": ["autohome", "car", "懂车帝", "bitauto", "car1"],
|
||||
"体育": ["hupu", "sports", "sina-sports", "qq-sports", "zhibo8"],
|
||||
"教育": ["zhihu", "知乎", "bilibili", "jike", "dazhihui"],
|
||||
"健康": ["zhihu", "知乎", "sina-health", "health", "baikemy"],
|
||||
"国际": ["sina-news", "netease-news", "qq-news", "ifeng", "cnn", "bbc"],
|
||||
"房产": ["lfang", "soufunianjia", "anjuke", "house"],
|
||||
"数码": ["ithome", "coolapk", "sspai", "geekpark", "少数派"],
|
||||
"时尚": ["mogujie", "meilishuo", "xiaohongshu", "微博时尚", "yoho"],
|
||||
"美食": ["dianping", "xiaohongshu", "大众点评", "maoyan", "ele.me"]
|
||||
}
|
||||
|
||||
# 所有可用的标签
|
||||
ALL_TAGS = list(TAG_MAPPING.keys())
|
||||
|
||||
|
||||
class DigestMode(Enum):
|
||||
"""摘要模式"""
|
||||
SINGLE = "single" # 单标签
|
||||
MULTI = "multi" # 多标签
|
||||
|
||||
|
||||
@dataclass
|
||||
class DigestConfig:
|
||||
"""摘要配置"""
|
||||
tags: List[str] # 选择的标签列表
|
||||
mode: DigestMode = DigestMode.MULTI
|
||||
items_per_platform: int = 10 # 每个平台显示的条目数
|
||||
total_items: int = 50 # 总条目数限制
|
||||
merge_strategy: str = "score" # 合并策略:score(按热度), time(按时间), random(随机)
|
||||
|
||||
|
||||
class NewsDigest:
|
||||
"""热点新闻摘要类"""
|
||||
|
||||
def __init__(self, api_client=None, formatter=None):
|
||||
"""
|
||||
初始化新闻摘要
|
||||
|
||||
Args:
|
||||
api_client: API客户端实例(可选,如果不提供则需要外部传入)
|
||||
formatter: 格式化器实例(可选)
|
||||
"""
|
||||
self.api_client = api_client
|
||||
self.formatter = formatter
|
||||
self.platforms = self._get_all_platforms()
|
||||
|
||||
def _get_all_platforms(self) -> List[str]:
|
||||
"""获取所有可用的平台"""
|
||||
platforms = set()
|
||||
for tag, plats in TAG_MAPPING.items():
|
||||
platforms.update(plats)
|
||||
return list(platforms)
|
||||
|
||||
async def fetch_all_hot_data(self, limit_per_platform: int = 10) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
一次性获取全部54个平台的热榜数据
|
||||
|
||||
Args:
|
||||
limit_per_platform: 每个平台获取的条目数
|
||||
|
||||
Returns:
|
||||
全部平台的热榜数据列表
|
||||
"""
|
||||
all_items = []
|
||||
|
||||
if self.api_client:
|
||||
for platform in self.platforms:
|
||||
try:
|
||||
items = await self.api_client.get_hot榜单(platform, limit=limit_per_platform)
|
||||
if items:
|
||||
for item in items:
|
||||
item["source_platform"] = platform
|
||||
all_items.extend(items)
|
||||
except Exception as e:
|
||||
print(f"Error fetching {platform}: {e}")
|
||||
continue
|
||||
|
||||
return all_items
|
||||
|
||||
async def get_tag_options(self) -> str:
|
||||
"""
|
||||
获取标签选择选项(AI引导话术)
|
||||
|
||||
Returns:
|
||||
格式化的标签选择文本
|
||||
"""
|
||||
options_text = "📊 **请选择您感兴趣的标签**\n\n"
|
||||
|
||||
# 分两列显示
|
||||
tags_left = ALL_TAGS[:8]
|
||||
tags_right = ALL_TAGS[8:]
|
||||
|
||||
for i, tag in enumerate(tags_left):
|
||||
right_tag = tags_right[i] if i < len(tags_right) else ""
|
||||
left_plats = ", ".join(TAG_MAPPING[tag][:3])
|
||||
right_plats = f"│ {i+8+1}. {right_tag}: {', '.join(TAG_MAPPING[right_tag][:3])}" if right_tag else ""
|
||||
options_text += f"{i+1}. {tag} ({left_plats}) {right_plats}\n"
|
||||
|
||||
options_text += "\n💡 您可以输入标签名称或数字编号,支持多选(如:1,3或科技+游戏)"
|
||||
return options_text
|
||||
|
||||
def parse_tags_from_input(self, user_input: str) -> List[str]:
|
||||
"""
|
||||
解析用户输入的标签
|
||||
|
||||
Args:
|
||||
user_input: 用户输入的文本
|
||||
|
||||
Returns:
|
||||
匹配的标签列表
|
||||
"""
|
||||
user_input = user_input.strip().lower()
|
||||
matched_tags = []
|
||||
|
||||
# 处理数字选择
|
||||
numbers = []
|
||||
import re
|
||||
number_pattern = re.findall(r'\d+', user_input)
|
||||
for num in number_pattern:
|
||||
idx = int(num) - 1
|
||||
if 0 <= idx < len(ALL_TAGS):
|
||||
numbers.append(ALL_TAGS[idx])
|
||||
|
||||
# 处理标签名称
|
||||
for tag in ALL_TAGS:
|
||||
if tag.lower() in user_input or tag in user_input:
|
||||
if tag not in matched_tags and tag not in numbers:
|
||||
matched_tags.append(tag)
|
||||
|
||||
# 合并数字选择的结果
|
||||
matched_tags.extend([t for t in numbers if t not in matched_tags])
|
||||
|
||||
return matched_tags if matched_tags else []
|
||||
|
||||
async def get_digest_by_tags(self, tags: List[str], config: Optional[DigestConfig] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
按标签获取新闻摘要(新版:先全部获取,再按标签筛选)
|
||||
|
||||
Args:
|
||||
tags: 标签列表
|
||||
config: 配置对象(可选)
|
||||
|
||||
Returns:
|
||||
合并后的热榜数据
|
||||
"""
|
||||
if config is None:
|
||||
config = DigestConfig(tags=tags)
|
||||
|
||||
# 步骤1:全部获取
|
||||
all_items = await self.fetch_all_hot_data(limit_per_platform=config.items_per_platform)
|
||||
|
||||
# 步骤2:按标签筛选
|
||||
# 获取所有相关平台
|
||||
target_platforms = set()
|
||||
for tag in tags:
|
||||
if tag in TAG_MAPPING:
|
||||
target_platforms.update(TAG_MAPPING[tag])
|
||||
|
||||
filtered_items = []
|
||||
for item in all_items:
|
||||
platform = item.get("source_platform", "")
|
||||
if platform in target_platforms:
|
||||
item["source_tag"] = next((t for t in tags if t in TAG_MAPPING and platform in TAG_MAPPING[t]), tags[0])
|
||||
filtered_items.append(item)
|
||||
|
||||
# 去重和合并
|
||||
merged_items = self._merge_items(filtered_items, config.merge_strategy)
|
||||
|
||||
# 限制总数
|
||||
merged_items = merged_items[:config.total_items]
|
||||
|
||||
return {
|
||||
"tags": tags,
|
||||
"platforms": list(target_platforms),
|
||||
"total_items": len(merged_items),
|
||||
"items": merged_items
|
||||
}
|
||||
|
||||
def _merge_items(self, items: List[Dict], strategy: str = "score") -> List[Dict]:
|
||||
"""
|
||||
合并和去重条目
|
||||
|
||||
Args:
|
||||
items: 条目列表
|
||||
strategy: 合并策略
|
||||
|
||||
Returns:
|
||||
合并后的条目列表
|
||||
"""
|
||||
if not items:
|
||||
return []
|
||||
|
||||
# 按标题去重
|
||||
seen_titles = set()
|
||||
unique_items = []
|
||||
|
||||
for item in items:
|
||||
title = item.get("title", "").strip().lower()
|
||||
if title and title not in seen_titles:
|
||||
seen_titles.add(title)
|
||||
unique_items.append(item)
|
||||
|
||||
# 根据策略排序
|
||||
if strategy == "score":
|
||||
# 按热度/分数排序
|
||||
unique_items.sort(key=lambda x: x.get("hot", 0) or x.get("score", 0), reverse=True)
|
||||
elif strategy == "time":
|
||||
# 按时间排序
|
||||
unique_items.sort(key=lambda x: x.get("time", "") or "", reverse=True)
|
||||
|
||||
return unique_items
|
||||
|
||||
def format_digest_response(self, digest_data: Dict[str, Any]) -> str:
|
||||
"""
|
||||
格式化摘要响应
|
||||
|
||||
Args:
|
||||
digest_data: 摘要数据
|
||||
|
||||
Returns:
|
||||
格式化的响应文本
|
||||
"""
|
||||
if not digest_data.get("items"):
|
||||
return "❌ 暂无热点数据"
|
||||
|
||||
tags = digest_data["tags"]
|
||||
items = digest_data["items"]
|
||||
|
||||
response = f"📰 **热点摘要 - {', '.join(tags)}**\n"
|
||||
response += f"来源平台: {', '.join(digest_data['platforms'])}\n"
|
||||
response += f"共 {digest_data['total_items']} 条热点\n"
|
||||
response += "-" * 40 + "\n\n"
|
||||
|
||||
for i, item in enumerate(items, 1):
|
||||
title = item.get("title", "无标题")
|
||||
hot = item.get("hot", item.get("score", ""))
|
||||
platform = item.get("source_platform", "")
|
||||
|
||||
response += f"{i}. {title}\n"
|
||||
if hot:
|
||||
response += f" 🔥 热度: {hot}"
|
||||
if platform:
|
||||
response += f" | 📱 {platform}"
|
||||
response += "\n\n"
|
||||
|
||||
return response
|
||||
|
||||
async def process_user_request(self, user_input: str) -> Dict[str, Any]:
|
||||
"""
|
||||
处理用户请求
|
||||
|
||||
Args:
|
||||
user_input: 用户输入
|
||||
|
||||
Returns:
|
||||
处理结果
|
||||
"""
|
||||
# 解析标签
|
||||
tags = self.parse_tags_from_input(user_input)
|
||||
|
||||
if not tags:
|
||||
# 返回引导信息
|
||||
return {
|
||||
"action": "ask_tag",
|
||||
"message": await self.get_tag_options()
|
||||
}
|
||||
|
||||
# 获取摘要
|
||||
digest_data = await self.get_digest_by_tags(tags)
|
||||
|
||||
# 格式化响应
|
||||
response_text = self.format_digest_response(digest_data)
|
||||
|
||||
return {
|
||||
"action": "show_digest",
|
||||
"data": digest_data,
|
||||
"message": response_text
|
||||
}
|
||||
|
||||
|
||||
# 便捷函数
|
||||
async def create_digest(api_client=None, formatter=None) -> NewsDigest:
|
||||
"""创建新闻摘要实例"""
|
||||
return NewsDigest(api_client=api_client, formatter=formatter)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试代码
|
||||
async def test():
|
||||
digest = await create_digest()
|
||||
print(await digest.get_tag_options())
|
||||
|
||||
print("\n" + "="*50 + "\n")
|
||||
|
||||
result = await digest.process_user_request("科技和游戏")
|
||||
print(result["message"])
|
||||
|
||||
asyncio.run(test())
|
||||
Reference in New Issue
Block a user