feat: add standalone wechat clawbot notifications

This commit is contained in:
jxxghp
2026-05-10 21:47:35 +08:00
parent b768ed8fed
commit 68706d3d5b
10 changed files with 2551 additions and 2 deletions

View File

@@ -2,7 +2,7 @@ from fastapi import APIRouter
from app.api.endpoints import login, user, webhook, message, site, subscribe, \
media, douban, search, plugin, tmdb, history, system, download, dashboard, \
transfer, mediaserver, bangumi, storage, discover, recommend, workflow, torrent, mcp, mfa, openai, anthropic, llm
transfer, mediaserver, bangumi, storage, discover, recommend, workflow, torrent, mcp, mfa, openai, anthropic, llm, notification
api_router = APIRouter()
api_router.include_router(login.router, prefix="/login", tags=["login"])
@@ -18,6 +18,7 @@ api_router.include_router(douban.router, prefix="/douban", tags=["douban"])
api_router.include_router(tmdb.router, prefix="/tmdb", tags=["tmdb"])
api_router.include_router(history.router, prefix="/history", tags=["history"])
api_router.include_router(system.router, prefix="/system", tags=["system"])
api_router.include_router(notification.router, prefix="/notification", tags=["notification"])
api_router.include_router(llm.router, prefix="/llm", tags=["llm"])
api_router.include_router(plugin.router, prefix="/plugin", tags=["plugin"])
api_router.include_router(download.router, prefix="/download", tags=["download"])

View File

@@ -0,0 +1,216 @@
from typing import Optional
from fastapi import APIRouter, Depends
from app import schemas
from app.core.module import ModuleManager
from app.db.models import User
from app.db.user_oper import get_current_active_superuser
from app.modules.wechatclawbot.wechatclawbot import WechatClawBot
router = APIRouter()
def _build_wechatclawbot_temp_client(
source: Optional[str] = None,
WECHATCLAWBOT_BASE_URL: Optional[str] = None,
WECHATCLAWBOT_DEFAULT_TARGET: Optional[str] = None,
WECHATCLAWBOT_ADMINS: Optional[str] = None,
WECHATCLAWBOT_POLL_TIMEOUT: Optional[int] = None,
):
"""基于当前表单配置创建一个临时客户端,用于未保存时的扫码状态预览。"""
source_name = str(source or "").strip()
if not source_name:
return None
return WechatClawBot(
name=source_name,
WECHATCLAWBOT_BASE_URL=WECHATCLAWBOT_BASE_URL,
WECHATCLAWBOT_DEFAULT_TARGET=WECHATCLAWBOT_DEFAULT_TARGET,
WECHATCLAWBOT_ADMINS=WECHATCLAWBOT_ADMINS,
WECHATCLAWBOT_POLL_TIMEOUT=WECHATCLAWBOT_POLL_TIMEOUT,
auto_start_polling=False,
)
def _get_wechatclawbot_client(
source: Optional[str] = None,
fallback_source: Optional[str] = None,
WECHATCLAWBOT_BASE_URL: Optional[str] = None,
WECHATCLAWBOT_DEFAULT_TARGET: Optional[str] = None,
WECHATCLAWBOT_ADMINS: Optional[str] = None,
WECHATCLAWBOT_POLL_TIMEOUT: Optional[int] = None,
allow_temporary: bool = False,
):
"""获取已加载的微信 ClawBot 客户端,必要时退回到临时客户端。"""
module = ModuleManager().get_running_module("WechatClawBotModule")
source_name = str(source or "").strip() or None
fallback_name = str(fallback_source or "").strip() or None
if module:
candidate_names = []
for candidate in (fallback_name, source_name):
if candidate and candidate not in candidate_names:
candidate_names.append(candidate)
if candidate_names:
for candidate in candidate_names:
config = module.get_config(candidate)
if not config:
continue
client = module.get_instance(config.name)
if client:
return client, None
else:
client = module.get_instance()
if client:
return client, None
if allow_temporary:
temp_client = _build_wechatclawbot_temp_client(
source=source_name or fallback_name,
WECHATCLAWBOT_BASE_URL=WECHATCLAWBOT_BASE_URL,
WECHATCLAWBOT_DEFAULT_TARGET=WECHATCLAWBOT_DEFAULT_TARGET,
WECHATCLAWBOT_ADMINS=WECHATCLAWBOT_ADMINS,
WECHATCLAWBOT_POLL_TIMEOUT=WECHATCLAWBOT_POLL_TIMEOUT,
)
if temp_client:
return temp_client, None
if source_name:
return None, f"未找到名为 {source_name} 的微信 ClawBot 通知配置"
return None, "微信 ClawBot 通知未启用或配置尚未保存,请先保存并启用当前渠道"
@router.get(
"/wechatclawbot/status",
summary="查询微信 ClawBot 登录状态",
response_model=schemas.Response,
)
def wechatclawbot_status(
source: Optional[str] = None,
fallback_source: Optional[str] = None,
refresh_remote: bool = True,
auto_generate_qrcode: bool = True,
WECHATCLAWBOT_BASE_URL: Optional[str] = None,
WECHATCLAWBOT_DEFAULT_TARGET: Optional[str] = None,
WECHATCLAWBOT_ADMINS: Optional[str] = None,
WECHATCLAWBOT_POLL_TIMEOUT: Optional[int] = None,
_: User = Depends(get_current_active_superuser),
):
"""查询微信 ClawBot 登录状态和二维码。"""
client, errmsg = _get_wechatclawbot_client(
source=source,
fallback_source=fallback_source,
WECHATCLAWBOT_BASE_URL=WECHATCLAWBOT_BASE_URL,
WECHATCLAWBOT_DEFAULT_TARGET=WECHATCLAWBOT_DEFAULT_TARGET,
WECHATCLAWBOT_ADMINS=WECHATCLAWBOT_ADMINS,
WECHATCLAWBOT_POLL_TIMEOUT=WECHATCLAWBOT_POLL_TIMEOUT,
allow_temporary=True,
)
if not client:
return schemas.Response(success=False, message=errmsg)
return schemas.Response(
success=True,
data=client.get_status(
refresh_remote=refresh_remote,
auto_generate_qrcode=auto_generate_qrcode,
),
)
@router.post(
"/wechatclawbot/refresh",
summary="刷新微信 ClawBot 二维码",
response_model=schemas.Response,
)
def refresh_wechatclawbot_qrcode(
source: Optional[str] = None,
fallback_source: Optional[str] = None,
WECHATCLAWBOT_BASE_URL: Optional[str] = None,
WECHATCLAWBOT_DEFAULT_TARGET: Optional[str] = None,
WECHATCLAWBOT_ADMINS: Optional[str] = None,
WECHATCLAWBOT_POLL_TIMEOUT: Optional[int] = None,
_: User = Depends(get_current_active_superuser),
):
"""刷新微信 ClawBot 二维码。"""
client, errmsg = _get_wechatclawbot_client(
source=source,
fallback_source=fallback_source,
WECHATCLAWBOT_BASE_URL=WECHATCLAWBOT_BASE_URL,
WECHATCLAWBOT_DEFAULT_TARGET=WECHATCLAWBOT_DEFAULT_TARGET,
WECHATCLAWBOT_ADMINS=WECHATCLAWBOT_ADMINS,
WECHATCLAWBOT_POLL_TIMEOUT=WECHATCLAWBOT_POLL_TIMEOUT,
allow_temporary=True,
)
if not client:
return schemas.Response(success=False, message=errmsg)
result = client.refresh_qrcode()
return schemas.Response(
success=bool(result.get("success")),
message=result.get("message"),
data=result,
)
@router.post(
"/wechatclawbot/logout",
summary="退出微信 ClawBot 登录",
response_model=schemas.Response,
)
def logout_wechatclawbot(
source: Optional[str] = None,
fallback_source: Optional[str] = None,
WECHATCLAWBOT_BASE_URL: Optional[str] = None,
WECHATCLAWBOT_DEFAULT_TARGET: Optional[str] = None,
WECHATCLAWBOT_ADMINS: Optional[str] = None,
WECHATCLAWBOT_POLL_TIMEOUT: Optional[int] = None,
_: User = Depends(get_current_active_superuser),
):
"""退出微信 ClawBot 登录。"""
client, errmsg = _get_wechatclawbot_client(
source=source,
fallback_source=fallback_source,
WECHATCLAWBOT_BASE_URL=WECHATCLAWBOT_BASE_URL,
WECHATCLAWBOT_DEFAULT_TARGET=WECHATCLAWBOT_DEFAULT_TARGET,
WECHATCLAWBOT_ADMINS=WECHATCLAWBOT_ADMINS,
WECHATCLAWBOT_POLL_TIMEOUT=WECHATCLAWBOT_POLL_TIMEOUT,
allow_temporary=True,
)
if not client:
return schemas.Response(success=False, message=errmsg)
result = client.logout()
return schemas.Response(
success=bool(result.get("success")),
message=result.get("message"),
data=result,
)
@router.get(
"/wechatclawbot/test",
summary="测试微信 ClawBot 连通性",
response_model=schemas.Response,
)
def test_wechatclawbot(
source: Optional[str] = None,
fallback_source: Optional[str] = None,
WECHATCLAWBOT_BASE_URL: Optional[str] = None,
WECHATCLAWBOT_DEFAULT_TARGET: Optional[str] = None,
WECHATCLAWBOT_ADMINS: Optional[str] = None,
WECHATCLAWBOT_POLL_TIMEOUT: Optional[int] = None,
_: User = Depends(get_current_active_superuser),
):
"""测试微信 ClawBot 当前登录态是否可用。"""
client, errmsg = _get_wechatclawbot_client(
source=source,
fallback_source=fallback_source,
WECHATCLAWBOT_BASE_URL=WECHATCLAWBOT_BASE_URL,
WECHATCLAWBOT_DEFAULT_TARGET=WECHATCLAWBOT_DEFAULT_TARGET,
WECHATCLAWBOT_ADMINS=WECHATCLAWBOT_ADMINS,
WECHATCLAWBOT_POLL_TIMEOUT=WECHATCLAWBOT_POLL_TIMEOUT,
allow_temporary=True,
)
if not client:
return schemas.Response(success=False, message=errmsg)
state, message = client.test_connection()
return schemas.Response(success=state, message=message)