Add 39 shared skills from local agent inventory

Sources: ~/.openclaw/skills, ~/.agents/skills, workshop-skills, workspace/skills
This commit is contained in:
2026-09-10 16:54:57 +08:00
parent f3388c50e2
commit 3d3dd79df0
110 changed files with 18722 additions and 0 deletions
+432
View File
@@ -0,0 +1,432 @@
# -*- coding: utf-8 -*-
"""
DailyHotApi Skill - API 客户端封装
"""
import aiohttp
import asyncio
import subprocess
import os
from typing import Optional, Dict, List, Any
from datetime import datetime
from config import config
from storage import storage # 导入存储模块
# 部署状态存储
_deployment_status = {
"is_deploying": False,
"last_check": None,
"needs_deployment": False,
"message": ""
}
class HotSource:
"""热榜源定义"""
def __init__(self, id: str, name: str, category: str, description: str = ""):
self.id = id
self.name = name
self.category = category
self.description = description
# 54 个热榜源定义
HOT_SOURCES = {
# 视频/直播平台
"bilibili": HotSource("bilibili", "哔哩哔哩", "video", "热门榜"),
"acfun": HotSource("acfun", "AcFun", "video", "排行榜"),
"douyin": HotSource("douyin", "抖音", "video", "热点榜"),
"kuaishou": HotSource("kuaishou", "快手", "video", "热点榜"),
"coolapk": HotSource("coolapk", "酷安", "video", "热榜"),
# 社交媒体
"weibo": HotSource("weibo", "微博", "social", "热搜榜"),
"zhihu": HotSource("zhihu", "知乎", "social", "热榜"),
"zhihu-daily": HotSource("zhihu-daily", "知乎日报", "social", "推荐榜"),
"tieba": HotSource("tieba", "百度贴吧", "social", "热议榜"),
"douban-group": HotSource("douban-group", "豆瓣讨论小组", "social", "讨论精选"),
"v2ex": HotSource("v2ex", "V2EX", "social", "主题榜"),
"ngabbs": HotSource("ngabbs", "NGA", "social", "热帖"),
"hupu": HotSource("hupu", "虎扑", "social", "步行街热帖"),
# 新闻资讯
"baidu": HotSource("baidu", "百度", "news", "热搜榜"),
"thepaper": HotSource("thepaper", "澎湃新闻", "news", "热榜"),
"toutiao": HotSource("toutiao", "今日头条", "news", "热榜"),
"36kr": HotSource("36kr", "36氪", "news", "热榜"),
"qq-news": HotSource("qq-news", "腾讯新闻", "news", "热点榜"),
"sina": HotSource("sina", "新浪网", "news", "热榜"),
"sina-news": HotSource("sina-news", "新浪新闻", "news", "热点榜"),
"netease-news": HotSource("netease-news", "网易新闻", "news", "热点榜"),
"huxiu": HotSource("huxiu", "虎嗅", "news", "24小时"),
"ifanr": HotSource("ifanr", "爱范儿", "news", "快讯"),
# 科技/技术社区
"ithome": HotSource("ithome", "IT之家", "tech", "热榜"),
"ithome-xijiayi": HotSource("ithome-xijiayi", "IT之家「喜加一」", "tech", "最新动态"),
"sspai": HotSource("sspai", "少数派", "tech", "热榜"),
"csdn": HotSource("csdn", "CSDN", "tech", "排行榜"),
"juejin": HotSource("juejin", "稀土掘金", "tech", "热榜"),
"51cto": HotSource("51cto", "51CTO", "tech", "推荐榜"),
"nodeseek": HotSource("nodeseek", "NodeSeek", "tech", "最新动态"),
"hellogithub": HotSource("hellogithub", "HelloGitHub", "tech", "Trending"),
# 游戏/ACG
"genshin": HotSource("genshin", "原神", "game", "最新消息"),
"miyoushe": HotSource("miyoushe", "米游社", "game", "最新消息"),
"honkai": HotSource("honkai", "崩坏3", "game", "最新动态"),
"starrail": HotSource("starrail", "崩坏:星穹铁道", "game", "最新动态"),
"lol": HotSource("lol", "英雄联盟", "game", "更新公告"),
# 阅读/文化
"jianshu": HotSource("jianshu", "简书", "reading", "热门推荐"),
"guokr": HotSource("guokr", "果壳", "reading", "热门文章"),
"weread": HotSource("weread", "微信读书", "reading", "飙升榜"),
"douban-movie": HotSource("douban-movie", "豆瓣电影", "reading", "新片榜"),
# 工具/其他
"52pojie": HotSource("52pojie", "吾爱破解", "tool", "榜单"),
"hostloc": HotSource("hostloc", "全球主机交流", "tool", "榜单"),
"weatheralarm": HotSource("weatheralarm", "中央气象台", "tool", "全国气象预警"),
"earthquake": HotSource("earthquake", "中国地震台", "tool", "地震速报"),
"history": HotSource("history", "历史上的今天", "tool", "月-日"),
}
class DailyHotApiClient:
"""DailyHotApi 客户端"""
def __init__(self):
self.api_url = config.api_url
self.timeout = config.timeout
async def fetch_hot_list(self, source_id: str, use_cache: bool = False) -> Optional[Dict[str, Any]]:
"""
获取热榜数据
Args:
source_id: 热榜源 ID(如 weibo, zhihu, bilibili)
use_cache: 是否使用缓存(默认False,每次获取最新数据)
Returns:
热榜数据字典或 None(失败时)
"""
# 获取热榜源信息
source = HOT_SOURCES.get(source_id)
if not source:
return None
# 构建请求 URL
url = config.get_api_url(source_id)
# 步骤1:检查API是否可用
print(f"[DailyHotApi] 正在连接 {source.name}...")
api_available = await check_api_availability(url)
if not api_available:
# API不可用,尝试部署
print(f"[DailyHotApi] ⚠️ 后端服务不可用,尝试自动部署...")
# 触发部署
deploy_result = await deploy_daily_hot_api()
if deploy_result["success"]:
# 部署成功,等待服务启动
print(f"[DailyHotApi] ⏳ 等待服务启动 (5秒)...")
await asyncio.sleep(5)
# 再次检查API
api_available = await check_api_availability(url)
if not api_available:
# 仍然不可用,可能需要更多时间
print(f"[DailyHotApi] ⏳ 服务可能需要更多时间启动,再次等待 (10秒)...")
await asyncio.sleep(10)
api_available = await check_api_availability(url)
# 返回部署状态
if deploy_result["success"]:
return {
"success": True,
"deploy_message": deploy_result["message"],
"is_deployed": True,
"data": None,
"message": "🎉 后端服务已部署成功!请稍后再次尝试获取热榜数据。"
}
else:
return {
"success": False,
"deploy_message": "❌ 自动部署失败",
"steps": deploy_result.get("steps", []),
"data": None,
"message": f"⚠️ 无法连接后端服务\n\n自动部署失败:{deploy_result['message']}\n\n请手动部署:\n1. cd /root/.openclaw\n2. git clone https://github.com/imsyy/DailyHotApi.git\n3. cd DailyHotApi\n4. bash deploy.sh"
}
# API可用,获取数据
try:
timeout_obj = aiohttp.ClientTimeout(total=self.timeout)
async with aiohttp.ClientSession(timeout=timeout_obj) as session:
async with session.get(url) as response:
if response.status != 200:
return None
result = await response.json()
# 检查返回格式
if result.get("code") != 200:
return None
# 格式化数据
data = {
"platform": source.name,
"category": source.category,
"source_id": source_id,
"update_time": result.get("updateTime"),
"from_cache": result.get("fromCache", False),
"total": result.get("total", 0),
"data": self._format_items(result.get("data", [])),
}
# 每次获取最新数据后,保存到本地历史记录
save_result = storage.save_hot_list(source_id, data)
if save_result:
print(f"[DailyHotApi] ✅ 已保存 {source.name} 热榜数据到历史记录")
return data
except Exception as e:
print(f"[DailyHotApi] Error fetching {source_id}: {e}")
return None
async def get_hot榜单(self, source_id: str, limit: int = 10) -> Optional[List[Dict]]:
"""
获取热榜条目列表(兼容旧接口)
Args:
source_id: 热榜源 ID
limit: 返回条目数限制
Returns:
热榜条目列表或 None
"""
data = await self.fetch_hot_list(source_id)
if data:
return data.get("data", [])[:limit]
return None
def _format_items(self, items: List[Dict]) -> List[Dict]:
"""格式化热榜条目"""
formatted = []
for i, item in enumerate(items[:config.max_items], 1):
formatted.append({
"rank": i,
"title": item.get("title", ""),
"desc": item.get("desc", ""),
"hot": item.get("hot", ""),
"url": item.get("url", ""),
"mobile_url": item.get("mobileUrl", ""),
})
return formatted
def get_all_sources(self) -> Dict[str, Dict]:
"""获取所有热榜源"""
return {
source_id: {
"id": source_id,
"name": source.name,
"category": source.category,
"description": source.description,
}
for source_id, source in HOT_SOURCES.items()
}
def get_sources_by_category(self) -> Dict[str, List[Dict]]:
"""按类别获取热榜源"""
categories = {}
for source_id, source in HOT_SOURCES.items():
if source.category not in categories:
categories[source.category] = []
categories[source.category].append({
"id": source_id,
"name": source.name,
"description": source.description,
})
return categories
def search_sources(self, query: str) -> List[Dict]:
"""搜索热榜源"""
query = query.lower()
results = []
for source_id, source in HOT_SOURCES.items():
if (query in source.name.lower() or
query in source.id.lower() or
query in source.category.lower()):
results.append({
"id": source_id,
"name": source.name,
"category": source.category,
})
return results
# 全局客户端实例
api_client = DailyHotApiClient()
# ============================================
# 自动检测和部署功能
# ============================================
async def check_api_availability(url: str, timeout: int = 3) -> bool:
"""
检查API是否可用
Args:
url: API地址
timeout: 超时时间(秒)
Returns:
True表示可用,False表示不可用
"""
try:
timeout_obj = aiohttp.ClientTimeout(total=timeout)
async with aiohttp.ClientSession(timeout=timeout_obj) as session:
async with session.get(url, allow_redirects=True) as response:
return response.status == 200
except Exception as e:
print(f"[DailyHotApi] API不可用: {e}")
return False
async def deploy_daily_hot_api() -> Dict[str, Any]:
"""
自动部署DailyHotApi后端服务
Returns:
部署结果字典
"""
global _deployment_status
if _deployment_status["is_deploying"]:
return {
"success": False,
"message": "部署正在进行中,请稍候...",
"is_deploying": True
}
_deployment_status["is_deploying"] = True
_deployment_status["message"] = "🚀 正在自动部署DailyHotApi后端服务..."
result = {
"success": False,
"message": "",
"steps": []
}
try:
# 检查是否已安装
daily_hot_path = "/root/.openclaw/DailyHotApi"
if not os.path.exists(daily_hot_path):
# 步骤1:克隆仓库
step_msg = "📦 正在克隆DailyHotApi仓库..."
print(step_msg)
result["steps"].append(step_msg)
clone_cmd = ["git", "clone", "https://github.com/imsyy/DailyHotApi.git", daily_hot_path]
clone_proc = await asyncio.create_subprocess_exec(
*clone_cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await clone_proc.communicate()
if clone_proc.returncode != 0:
error_msg = f"❌ 克隆失败: {stderr.decode()}"
print(error_msg)
result["steps"].append(error_msg)
result["message"] = "部署失败:无法克隆仓库"
_deployment_status["is_deploying"] = False
return result
result["steps"].append("✅ 克隆成功")
else:
result["steps"].append("✅ DailyHotApi已存在,跳过克隆")
# 步骤2:部署服务
if os.path.exists(daily_hot_path):
step_msg = "🔧 正在部署DailyHotApi服务..."
print(step_msg)
result["steps"].append(step_msg)
deploy_script = os.path.join(daily_hot_path, "deploy.sh")
deploy_cmd = ["bash", deploy_script]
deploy_proc = await asyncio.create_subprocess_exec(
*deploy_cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, stderr = await deploy_proc.communicate()
deploy_output = stdout.decode() + stderr.decode()
if deploy_proc.returncode == 0:
result["steps"].append("✅ 部署成功")
result["success"] = True
result["message"] = "🎉 DailyHotApi后端服务部署成功!正在启动..."
_deployment_status["message"] = result["message"]
else:
error_msg = f"❌ 部署失败: {deploy_output}"
print(error_msg)
result["steps"].append(error_msg)
result["message"] = "部署失败,请手动检查"
except Exception as e:
error_msg = f"❌ 部署异常: {str(e)}"
print(error_msg)
result["steps"].append(error_msg)
result["message"] = f"部署异常: {str(e)}"
finally:
_deployment_status["is_deploying"] = False
_deployment_status["last_check"] = datetime.now().isoformat()
return result
def get_deployment_status() -> Dict[str, Any]:
"""
获取当前部署状态
Returns:
部署状态字典
"""
return {
"is_deploying": _deployment_status["is_deploying"],
"last_check": _deployment_status["last_check"],
"message": _deployment_status["message"]
}
async def test_connection() -> bool:
"""测试 API 连接"""
try:
timeout_obj = aiohttp.ClientTimeout(total=5)
async with aiohttp.ClientSession(timeout=timeout_obj) as session:
async with session.get(f"{config.api_url}/") as response:
return response.status == 200
except Exception:
return False
if __name__ == "__main__":
import sys
print("Testing DailyHotApi connection...")
if asyncio.run(test_connection()):
print("✓ Service is running")
else:
print("✗ Service not available")
print(f" URL: {config.api_url}")
sys.exit(1)