feat: add discord wakeup test endpoint

This commit is contained in:
2026-04-04 21:03:48 +00:00
parent 79c6c32a78
commit 57681c674f
3 changed files with 110 additions and 0 deletions

View File

@@ -22,6 +22,7 @@ from app.services.monitoring import (
get_server_states_view, get_server_states_view,
test_provider_connection, test_provider_connection,
) )
from app.services.discord_wakeup import create_private_wakeup_channel
router = APIRouter(prefix='/monitor', tags=['Monitor']) router = APIRouter(prefix='/monitor', tags=['Monitor'])
SUPPORTED_PROVIDERS = {'anthropic', 'openai', 'minimax', 'kimi', 'qwen'} SUPPORTED_PROVIDERS = {'anthropic', 'openai', 'minimax', 'kimi', 'qwen'}
@@ -42,6 +43,12 @@ class MonitoredServerCreate(BaseModel):
display_name: str | None = None display_name: str | None = None
class DiscordWakeupTestRequest(BaseModel):
discord_user_id: str
title: str = "HarborForge Wakeup"
message: str = "A HarborForge slot is ready to start."
def require_admin(current_user: models.User = Depends(get_current_user_or_apikey)): def require_admin(current_user: models.User = Depends(get_current_user_or_apikey)):
if not current_user.is_admin: if not current_user.is_admin:
raise HTTPException(status_code=403, detail='Admin required') raise HTTPException(status_code=403, detail='Admin required')
@@ -175,6 +182,11 @@ def revoke_api_key(server_id: int, db: Session = Depends(get_db), _: models.User
return None return None
@router.post('/admin/discord-wakeup/test')
def discord_wakeup_test(payload: DiscordWakeupTestRequest, _: models.User = Depends(require_admin)):
return create_private_wakeup_channel(payload.discord_user_id, payload.title, payload.message)
class TelemetryPayload(BaseModel): class TelemetryPayload(BaseModel):
identifier: str identifier: str
openclaw_version: str | None = None openclaw_version: str | None = None

View File

@@ -0,0 +1,72 @@
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any
import requests
from fastapi import HTTPException
from app.services.harborforge_config import get_discord_wakeup_config
DISCORD_API_BASE = "https://discord.com/api/v10"
WAKEUP_CATEGORY_NAME = "HarborForge Wakeup"
def _headers(bot_token: str) -> dict[str, str]:
return {
"Authorization": f"Bot {bot_token}",
"Content-Type": "application/json",
}
def _ensure_category(guild_id: str, bot_token: str) -> str | None:
resp = requests.get(f"{DISCORD_API_BASE}/guilds/{guild_id}/channels", headers=_headers(bot_token), timeout=15)
if not resp.ok:
raise HTTPException(status_code=502, detail=f"Discord list channels failed: {resp.text}")
for ch in resp.json():
if ch.get("type") == 4 and ch.get("name") == WAKEUP_CATEGORY_NAME:
return ch.get("id")
payload = {"name": WAKEUP_CATEGORY_NAME, "type": 4}
created = requests.post(f"{DISCORD_API_BASE}/guilds/{guild_id}/channels", headers=_headers(bot_token), json=payload, timeout=15)
if not created.ok:
raise HTTPException(status_code=502, detail=f"Discord create category failed: {created.text}")
return created.json().get("id")
def create_private_wakeup_channel(discord_user_id: str, title: str, message: str) -> dict[str, Any]:
cfg = get_discord_wakeup_config()
guild_id = cfg.get("guild_id")
bot_token = cfg.get("bot_token")
if not guild_id or not bot_token:
raise HTTPException(status_code=400, detail="Discord wakeup config is incomplete")
category_id = _ensure_category(guild_id, bot_token)
channel_name = f"wake-{discord_user_id[-6:]}-{int(datetime.now(timezone.utc).timestamp())}"
payload = {
"name": channel_name,
"type": 0,
"parent_id": category_id,
"permission_overwrites": [
{"id": guild_id, "type": 0, "deny": "1024"},
{"id": discord_user_id, "type": 1, "allow": "1024"},
],
"topic": title,
}
created = requests.post(f"{DISCORD_API_BASE}/guilds/{guild_id}/channels", headers=_headers(bot_token), json=payload, timeout=15)
if not created.ok:
raise HTTPException(status_code=502, detail=f"Discord create channel failed: {created.text}")
channel = created.json()
sent = requests.post(
f"{DISCORD_API_BASE}/channels/{channel['id']}/messages",
headers=_headers(bot_token),
json={"content": message},
timeout=15,
)
if not sent.ok:
raise HTTPException(status_code=502, detail=f"Discord send message failed: {sent.text}")
return {
"guild_id": guild_id,
"channel_id": channel.get("id"),
"channel_name": channel.get("name"),
"message_id": sent.json().get("id"),
}

View File

@@ -0,0 +1,26 @@
import json
import os
from typing import Any
CONFIG_DIR = os.getenv("CONFIG_DIR", "/config")
CONFIG_FILE = os.getenv("CONFIG_FILE", "harborforge.json")
def load_runtime_config() -> dict[str, Any]:
config_path = os.path.join(CONFIG_DIR, CONFIG_FILE)
if not os.path.exists(config_path):
return {}
try:
with open(config_path, "r") as f:
return json.load(f)
except Exception:
return {}
def get_discord_wakeup_config() -> dict[str, str | None]:
cfg = load_runtime_config()
discord_cfg = cfg.get("discord") or {}
return {
"guild_id": discord_cfg.get("guild_id"),
"bot_token": discord_cfg.get("bot_token"),
}