mirror of
https://github.com/Zippland/Bubbles.git
synced 2026-02-26 16:11:15 +08:00
refactor
This commit is contained in:
22
function_calls/services/__init__.py
Normal file
22
function_calls/services/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Service helpers for Function Call handlers."""
|
||||
|
||||
from .weather import get_weather_report
|
||||
from .news import get_news_digest
|
||||
from .reminder import create_reminder, list_reminders, delete_reminder
|
||||
from .help import build_help_text
|
||||
from .group_tools import summarize_messages, clear_group_messages
|
||||
from .perplexity import run_perplexity
|
||||
from .insult import build_insult
|
||||
|
||||
__all__ = [
|
||||
"get_weather_report",
|
||||
"get_news_digest",
|
||||
"create_reminder",
|
||||
"list_reminders",
|
||||
"delete_reminder",
|
||||
"build_help_text",
|
||||
"summarize_messages",
|
||||
"clear_group_messages",
|
||||
"run_perplexity",
|
||||
"build_insult",
|
||||
]
|
||||
47
function_calls/services/group_tools.py
Normal file
47
function_calls/services/group_tools.py
Normal file
@@ -0,0 +1,47 @@
|
||||
"""Group related utilities for Function Call handlers."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from commands.context import MessageContext
|
||||
|
||||
|
||||
@dataclass
|
||||
class GroupToolResult:
|
||||
success: bool
|
||||
message: str
|
||||
|
||||
|
||||
def summarize_messages(ctx: MessageContext) -> GroupToolResult:
|
||||
if not ctx.is_group:
|
||||
return GroupToolResult(success=True, message="⚠️ 消息总结功能仅支持群聊")
|
||||
|
||||
if not ctx.robot or not hasattr(ctx.robot, "message_summary") or not hasattr(ctx.robot, "chat"):
|
||||
return GroupToolResult(success=False, message="⚠️ 消息总结功能不可用")
|
||||
|
||||
try:
|
||||
summary = ctx.robot.message_summary.summarize_messages(ctx.msg.roomid, ctx.robot.chat)
|
||||
return GroupToolResult(success=True, message=summary)
|
||||
except Exception as exc:
|
||||
if ctx.logger:
|
||||
ctx.logger.error(f"生成消息总结出错: {exc}")
|
||||
return GroupToolResult(success=False, message="⚠️ 生成消息总结失败")
|
||||
|
||||
|
||||
def clear_group_messages(ctx: MessageContext) -> GroupToolResult:
|
||||
if not ctx.is_group:
|
||||
return GroupToolResult(success=True, message="⚠️ 消息历史管理功能仅支持群聊")
|
||||
|
||||
if not ctx.robot or not hasattr(ctx.robot, "message_summary"):
|
||||
return GroupToolResult(success=False, message="⚠️ 消息历史管理功能不可用")
|
||||
|
||||
try:
|
||||
cleared = ctx.robot.message_summary.clear_message_history(ctx.msg.roomid)
|
||||
if cleared:
|
||||
return GroupToolResult(success=True, message="✅ 已清除本群的消息历史记录")
|
||||
return GroupToolResult(success=True, message="⚠️ 本群没有消息历史记录")
|
||||
except Exception as exc:
|
||||
if ctx.logger:
|
||||
ctx.logger.error(f"清除消息历史出错: {exc}")
|
||||
return GroupToolResult(success=False, message="⚠️ 清除消息历史失败")
|
||||
34
function_calls/services/help.py
Normal file
34
function_calls/services/help.py
Normal file
@@ -0,0 +1,34 @@
|
||||
"""Static help text utility."""
|
||||
from __future__ import annotations
|
||||
|
||||
HELP_LINES = [
|
||||
"🤖 泡泡的指令列表 🤖",
|
||||
"",
|
||||
"【实用工具】",
|
||||
"- 天气/温度 [城市名]",
|
||||
"- 天气预报/预报 [城市名]",
|
||||
"- 新闻",
|
||||
"- ask [问题]",
|
||||
"",
|
||||
"【决斗 & 偷袭】",
|
||||
"- 决斗@XX",
|
||||
"- 偷袭@XX",
|
||||
"- 决斗排行/排行榜",
|
||||
"- 我的战绩/决斗战绩",
|
||||
"- 我的装备/查看装备",
|
||||
"- 改名 [旧名] [新名]",
|
||||
"",
|
||||
"【提醒】",
|
||||
"- 提醒xxxxx:一次性、每日、每周",
|
||||
"- 查看提醒/我的提醒/提醒列表",
|
||||
"- 删..提醒..",
|
||||
"",
|
||||
"【群聊工具】",
|
||||
"- summary/总结",
|
||||
"- clearmessages/清除历史",
|
||||
]
|
||||
|
||||
|
||||
def build_help_text() -> str:
|
||||
"""Return formatted help text."""
|
||||
return "\n".join(HELP_LINES)
|
||||
50
function_calls/services/insult.py
Normal file
50
function_calls/services/insult.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Group insult helper utilities."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from commands.context import MessageContext
|
||||
from function.func_insult import generate_random_insult
|
||||
|
||||
|
||||
@dataclass
|
||||
class InsultResult:
|
||||
success: bool
|
||||
message: str
|
||||
|
||||
|
||||
def build_insult(ctx: MessageContext, target_name: str) -> InsultResult:
|
||||
if not ctx.is_group:
|
||||
return InsultResult(success=True, message="❌ 骂人功能只支持群聊哦~")
|
||||
|
||||
cleaned = target_name.strip()
|
||||
if not cleaned:
|
||||
return InsultResult(success=False, message="❌ 需要提供要骂的对象")
|
||||
|
||||
actual_target = cleaned
|
||||
target_wxid: Optional[str] = None
|
||||
|
||||
try:
|
||||
members = ctx.room_members
|
||||
if members:
|
||||
for wxid, name in members.items():
|
||||
if cleaned == name:
|
||||
target_wxid = wxid
|
||||
actual_target = name
|
||||
break
|
||||
if target_wxid is None:
|
||||
for wxid, name in members.items():
|
||||
if cleaned in name and wxid != ctx.robot_wxid:
|
||||
target_wxid = wxid
|
||||
actual_target = name
|
||||
break
|
||||
except Exception as exc:
|
||||
if ctx.logger:
|
||||
ctx.logger.error(f"查找群成员信息时出错: {exc}")
|
||||
|
||||
if target_wxid and target_wxid == ctx.robot_wxid:
|
||||
return InsultResult(success=True, message="😅 不行,我不能骂我自己。")
|
||||
|
||||
insult_text = generate_random_insult(actual_target)
|
||||
return InsultResult(success=True, message=insult_text)
|
||||
36
function_calls/services/news.py
Normal file
36
function_calls/services/news.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""News related service helpers for Function Call handlers."""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from function.func_news import News
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class NewsResult:
|
||||
success: bool
|
||||
message: str
|
||||
is_today: Optional[bool] = None
|
||||
|
||||
|
||||
def get_news_digest() -> NewsResult:
|
||||
"""Fetch latest news digest."""
|
||||
try:
|
||||
news_instance = News()
|
||||
is_today, content = news_instance.get_important_news()
|
||||
if is_today:
|
||||
message = f"📰 今日要闻来啦:\n{content}"
|
||||
else:
|
||||
if content:
|
||||
message = f"ℹ️ 今日新闻暂未发布,为您找到最近的一条新闻:\n{content}"
|
||||
else:
|
||||
message = "❌ 获取新闻失败,请稍后重试"
|
||||
return NewsResult(success=True, message=message, is_today=is_today)
|
||||
except Exception as exc:
|
||||
logger.error(f"获取新闻失败: {exc}")
|
||||
return NewsResult(success=False, message="❌ 获取新闻时发生错误")
|
||||
62
function_calls/services/perplexity.py
Normal file
62
function_calls/services/perplexity.py
Normal file
@@ -0,0 +1,62 @@
|
||||
"""Perplexity integration helpers."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from commands.context import MessageContext
|
||||
|
||||
|
||||
@dataclass
|
||||
class PerplexityResult:
|
||||
success: bool
|
||||
messages: List[str]
|
||||
handled_externally: bool = False
|
||||
|
||||
|
||||
def run_perplexity(ctx: MessageContext, query: str) -> PerplexityResult:
|
||||
query = query.strip()
|
||||
if not query:
|
||||
at = ctx.msg.sender if ctx.is_group else ""
|
||||
return PerplexityResult(success=True, messages=["请告诉我你想搜索什么内容"], handled_externally=False)
|
||||
|
||||
perplexity_instance = getattr(ctx.robot, 'perplexity', None)
|
||||
if not perplexity_instance:
|
||||
return PerplexityResult(success=True, messages=["❌ Perplexity搜索功能当前不可用"], handled_externally=False)
|
||||
|
||||
content_for_perplexity = f"ask {query}"
|
||||
chat_id = ctx.get_receiver()
|
||||
sender_wxid = ctx.msg.sender
|
||||
room_id = ctx.msg.roomid if ctx.is_group else None
|
||||
|
||||
was_handled, fallback_prompt = perplexity_instance.process_message(
|
||||
content=content_for_perplexity,
|
||||
chat_id=chat_id,
|
||||
sender=sender_wxid,
|
||||
roomid=room_id,
|
||||
from_group=ctx.is_group,
|
||||
send_text_func=ctx.send_text
|
||||
)
|
||||
|
||||
if was_handled:
|
||||
return PerplexityResult(success=True, messages=[], handled_externally=True)
|
||||
|
||||
if fallback_prompt:
|
||||
chat_model = getattr(ctx, 'chat', None) or (getattr(ctx.robot, 'chat', None) if ctx.robot else None)
|
||||
if chat_model:
|
||||
try:
|
||||
import time
|
||||
current_time = time.strftime("%H:%M", time.localtime())
|
||||
formatted_question = f"[{current_time}] {ctx.sender_name}: {query}"
|
||||
answer = chat_model.get_answer(
|
||||
question=formatted_question,
|
||||
wxid=ctx.get_receiver(),
|
||||
system_prompt_override=fallback_prompt
|
||||
)
|
||||
if answer:
|
||||
return PerplexityResult(success=True, messages=[answer], handled_externally=False)
|
||||
except Exception as exc:
|
||||
if ctx.logger:
|
||||
ctx.logger.error(f"默认AI处理失败: {exc}")
|
||||
|
||||
return PerplexityResult(success=True, messages=["❌ Perplexity搜索时发生错误"], handled_externally=False)
|
||||
101
function_calls/services/reminder.py
Normal file
101
function_calls/services/reminder.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""Reminder related services for Function Call handlers."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from function.func_reminder import ReminderManager
|
||||
|
||||
|
||||
@dataclass
|
||||
class ReminderServiceResult:
|
||||
success: bool
|
||||
messages: List[str]
|
||||
|
||||
|
||||
_WEEKDAY_LABELS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
|
||||
_TYPE_LABELS = {"once": "一次性", "daily": "每日", "weekly": "每周"}
|
||||
|
||||
|
||||
def _format_schedule(data: Dict[str, Any]) -> str:
|
||||
reminder_type = data.get("type", "once")
|
||||
time_str = data.get("time", "?")
|
||||
|
||||
if reminder_type == "once":
|
||||
return f"{time_str} (一次性)"
|
||||
if reminder_type == "daily":
|
||||
return f"每天 {time_str}"
|
||||
if reminder_type == "weekly":
|
||||
weekday = data.get("weekday")
|
||||
if isinstance(weekday, int) and 0 <= weekday < len(_WEEKDAY_LABELS):
|
||||
return f"每周{_WEEKDAY_LABELS[weekday]} {time_str}"
|
||||
return f"每周 {time_str}"
|
||||
return f"{time_str}"
|
||||
|
||||
|
||||
def create_reminder(
|
||||
manager: ReminderManager,
|
||||
sender_wxid: str,
|
||||
data: Dict[str, Any],
|
||||
roomid: Optional[str]
|
||||
) -> ReminderServiceResult:
|
||||
payload = {
|
||||
"type": data["type"],
|
||||
"time": data["time"],
|
||||
"content": data["content"],
|
||||
}
|
||||
if data.get("weekday") is not None:
|
||||
payload["weekday"] = data["weekday"]
|
||||
|
||||
success, info = manager.add_reminder(sender_wxid, payload, roomid=roomid)
|
||||
if not success:
|
||||
return ReminderServiceResult(success=False, messages=[f"❌ 设置提醒失败:{info}"])
|
||||
|
||||
schedule = payload.copy()
|
||||
message = (
|
||||
"✅ 已为您设置{type_label}提醒\n"
|
||||
"时间: {schedule}\n"
|
||||
"内容: {content}"
|
||||
).format(
|
||||
type_label=_TYPE_LABELS.get(payload["type"], ""),
|
||||
schedule=_format_schedule(payload),
|
||||
content=payload["content"],
|
||||
)
|
||||
return ReminderServiceResult(success=True, messages=[message])
|
||||
|
||||
|
||||
def list_reminders(
|
||||
manager: ReminderManager,
|
||||
sender_wxid: str,
|
||||
contacts: Dict[str, str]
|
||||
) -> ReminderServiceResult:
|
||||
reminders = manager.list_reminders(sender_wxid)
|
||||
if not reminders:
|
||||
return ReminderServiceResult(success=True, messages=["您还没有设置任何提醒。"])
|
||||
|
||||
lines: List[str] = ["📝 您设置的提醒列表(包括私聊和群聊):"]
|
||||
for idx, reminder in enumerate(reminders, start=1):
|
||||
schedule_display = _format_schedule({
|
||||
"type": reminder.get("type"),
|
||||
"time": reminder.get("time_str"),
|
||||
"weekday": reminder.get("weekday"),
|
||||
})
|
||||
if reminder.get("type") == "once":
|
||||
schedule_display = reminder.get("time_str", "?")
|
||||
scope = "[私聊]"
|
||||
roomid = reminder.get("roomid")
|
||||
if roomid:
|
||||
room_name = contacts.get(roomid) or roomid[:8]
|
||||
scope = f"[群:{room_name}]"
|
||||
lines.append(
|
||||
f"{idx}. [ID: {reminder.get('id', '')[:6]}] {scope}{schedule_display}: {reminder.get('content', '')}"
|
||||
)
|
||||
|
||||
return ReminderServiceResult(success=True, messages=["\n".join(lines)])
|
||||
|
||||
|
||||
def delete_reminder(manager: ReminderManager, sender_wxid: str, reminder_id: str) -> ReminderServiceResult:
|
||||
success, info = manager.delete_reminder(sender_wxid, reminder_id)
|
||||
if success:
|
||||
return ReminderServiceResult(success=True, messages=[f"✅ {info}"])
|
||||
return ReminderServiceResult(success=False, messages=[f"❌ {info}"])
|
||||
65
function_calls/services/weather.py
Normal file
65
function_calls/services/weather.py
Normal file
@@ -0,0 +1,65 @@
|
||||
"""Weather related service helpers for Function Call handlers."""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
from function.func_weather import Weather
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class WeatherResult:
|
||||
success: bool
|
||||
message: str
|
||||
city: Optional[str] = None
|
||||
|
||||
|
||||
def _load_city_codes() -> dict[str, str]:
|
||||
"""Load mapping between city names and weather codes."""
|
||||
city_code_path = os.path.join(os.path.dirname(__file__), '..', '..', 'function', 'main_city.json')
|
||||
with open(city_code_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def get_weather_report(city_name: str) -> WeatherResult:
|
||||
"""Return a weather report for a given city.
|
||||
|
||||
Args:
|
||||
city_name: City provided by the user.
|
||||
|
||||
Returns:
|
||||
WeatherResult containing success status and message.
|
||||
"""
|
||||
city = city_name.strip()
|
||||
if not city:
|
||||
return WeatherResult(success=False, message="🤔 请告诉我你想查询哪个城市的天气")
|
||||
|
||||
try:
|
||||
city_codes = _load_city_codes()
|
||||
except Exception as exc: # pragma: no cover - IO failure is rare
|
||||
logger.error(f"加载城市代码失败: {exc}")
|
||||
return WeatherResult(success=False, message="⚠️ 抱歉,天气功能暂时不可用")
|
||||
|
||||
code = city_codes.get(city)
|
||||
if not code:
|
||||
for name, value in city_codes.items():
|
||||
if city in name:
|
||||
code = value
|
||||
city = name
|
||||
break
|
||||
|
||||
if not code:
|
||||
return WeatherResult(success=False, message=f"😕 找不到城市 '{city_name}' 的天气信息")
|
||||
|
||||
try:
|
||||
weather_text = Weather(code).get_weather(include_forecast=True)
|
||||
return WeatherResult(success=True, message=weather_text, city=city)
|
||||
except Exception as exc: # pragma: no cover - upstream API failure
|
||||
logger.error(f"获取天气数据失败: {exc}")
|
||||
return WeatherResult(success=False, message=f"😥 获取 {city} 天气时遇到问题")
|
||||
Reference in New Issue
Block a user