Updated API
This commit is contained in:
22
README.md
22
README.md
@@ -69,6 +69,28 @@ docker build -t telegram-groupfactory .
|
||||
docker run telegram-groupfactory
|
||||
```
|
||||
|
||||
## REST API
|
||||
|
||||
The userbot also exposes an internal FastAPI surface on `API_HOST:API_PORT`
|
||||
for the separate conventional Telegram bot. Requests require `X-API-Key`.
|
||||
|
||||
Relevant bot-facing endpoints:
|
||||
|
||||
```text
|
||||
POST /api/groups
|
||||
GET /api/admin/default-users
|
||||
PUT /api/admin/default-users
|
||||
POST /api/admin/default-users
|
||||
DELETE /api/admin/default-users
|
||||
GET /api/admin/qr-backup?qr_group=default
|
||||
PUT /api/admin/qr-backup
|
||||
POST /api/admin/qr-backup/image
|
||||
GET /api/admin/qr-groups
|
||||
POST /api/admin/qr-groups/{qr_group}/assignments
|
||||
DELETE /api/admin/qr-groups/assignments
|
||||
POST /api/admin/qr-sync
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
- **[ADMIN_IMPLEMENTATION.md](ADMIN_IMPLEMENTATION.md)** - Admin-only configuration features
|
||||
|
||||
@@ -4,6 +4,7 @@ python-dotenv>=0.19.0
|
||||
fastapi>=0.95.0
|
||||
uvicorn>=0.21.0
|
||||
pydantic>=1.10.0
|
||||
python-multipart>=0.0.9
|
||||
numpy>=1.26.0
|
||||
opencv-python-headless>=4.9.0.80
|
||||
qrcode[pil]>=7.4.2
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi import File, Form, Query, UploadFile
|
||||
|
||||
from src.api.auth import verify_api_key
|
||||
from src.api.schemas import (
|
||||
AddUserRequest,
|
||||
CommandResponse,
|
||||
QrBackupRequest,
|
||||
QrGroupAssignmentRequest,
|
||||
QrSyncRequest,
|
||||
UserIdsRequest,
|
||||
)
|
||||
|
||||
@@ -14,6 +20,7 @@ def _envelope(message: str) -> CommandResponse:
|
||||
|
||||
|
||||
router = APIRouter(prefix="/api/admin", tags=["admin"], dependencies=[Depends(verify_api_key)])
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _admin_chat_id(request: Request) -> int:
|
||||
@@ -54,15 +61,93 @@ async def add_user_to_db(payload: AddUserRequest, request: Request):
|
||||
|
||||
|
||||
@router.get("/qr-backup", response_model=CommandResponse)
|
||||
async def get_qr_backup(request: Request):
|
||||
async def get_qr_backup(request: Request, qr_group: str = Query("default")):
|
||||
handler = request.app.state.admin_handler
|
||||
return _envelope(await handler.handle_get_qr_backup(_admin_chat_id(request)))
|
||||
return _envelope(await handler.handle_get_qr_backup(_admin_chat_id(request), qr_group=qr_group))
|
||||
|
||||
|
||||
@router.put("/qr-backup", response_model=CommandResponse)
|
||||
async def set_qr_backup(payload: QrBackupRequest, request: Request):
|
||||
handler = request.app.state.admin_handler
|
||||
return _envelope(await handler.handle_set_qr_backup(_admin_chat_id(request), payload.qr_data))
|
||||
return _envelope(await handler.handle_set_qr_backup(
|
||||
_admin_chat_id(request),
|
||||
payload.qr_data,
|
||||
qr_group=payload.qr_group,
|
||||
))
|
||||
|
||||
|
||||
@router.post("/qr-backup/image", response_model=CommandResponse)
|
||||
async def set_qr_backup_from_image(
|
||||
request: Request,
|
||||
qr_group: str = Form("default"),
|
||||
file: UploadFile = File(...),
|
||||
):
|
||||
handler = request.app.state.admin_handler
|
||||
image_bytes = await file.read()
|
||||
return _envelope(await handler.handle_set_qr_backup_from_image(
|
||||
_admin_chat_id(request),
|
||||
image_bytes,
|
||||
qr_group=qr_group,
|
||||
))
|
||||
|
||||
|
||||
@router.get("/qr-groups", response_model=CommandResponse)
|
||||
async def list_qr_groups(request: Request, qr_group: str = Query(None)):
|
||||
handler = request.app.state.admin_handler
|
||||
return _envelope(await handler.handle_list_qr_groups(_admin_chat_id(request), qr_group=qr_group))
|
||||
|
||||
|
||||
@router.post("/qr-groups/{qr_group}/assignments", response_model=CommandResponse)
|
||||
async def assign_qr_group(qr_group: str, payload: QrGroupAssignmentRequest, request: Request):
|
||||
handler = request.app.state.admin_handler
|
||||
return _envelope(await handler.handle_assign_qr_group(
|
||||
_admin_chat_id(request),
|
||||
qr_group,
|
||||
payload.group_ids,
|
||||
))
|
||||
|
||||
|
||||
@router.delete("/qr-groups/assignments", response_model=CommandResponse)
|
||||
async def remove_qr_group_assignments(payload: QrGroupAssignmentRequest, request: Request):
|
||||
handler = request.app.state.admin_handler
|
||||
return _envelope(await handler.handle_remove_qr_group_assignment(
|
||||
_admin_chat_id(request),
|
||||
payload.group_ids,
|
||||
))
|
||||
|
||||
|
||||
@router.post("/qr-sync", response_model=CommandResponse)
|
||||
async def sync_qr(payload: QrSyncRequest, request: Request):
|
||||
sync_group = (payload.qr_group or "default").strip().lower()
|
||||
current_task = request.app.state.api_background_tasks.get("grouphelp_qr_sync")
|
||||
if current_task and not current_task.done():
|
||||
return CommandResponse(ok=True, message="ℹ️ GroupHelp QR sync is already running.")
|
||||
|
||||
group_service = request.app.state.group_handler.group_service
|
||||
|
||||
async def run_sync():
|
||||
if sync_group == "all":
|
||||
await group_service.sync_all_grouphelp_qr_groups(delay_seconds=30)
|
||||
else:
|
||||
await group_service.sync_grouphelp_qr_to_owned_groups(
|
||||
delay_seconds=30,
|
||||
qr_group=sync_group,
|
||||
)
|
||||
|
||||
def sync_done(task):
|
||||
try:
|
||||
task.result()
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logger.error("API GroupHelp QR sync failed: %s", e)
|
||||
finally:
|
||||
request.app.state.api_background_tasks["grouphelp_qr_sync"] = None
|
||||
|
||||
task = asyncio.create_task(run_sync(), name="api-grouphelp-qr-sync")
|
||||
task.add_done_callback(sync_done)
|
||||
request.app.state.api_background_tasks["grouphelp_qr_sync"] = task
|
||||
return CommandResponse(ok=True, message=f"✅ GroupHelp QR sync `{sync_group}` started in background.")
|
||||
|
||||
|
||||
@router.get("/help", response_model=CommandResponse)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import List, Optional
|
||||
from typing import List, Optional, Union
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
@@ -25,8 +25,17 @@ class AddUsersToGroupRequest(BaseModel):
|
||||
|
||||
|
||||
class UserIdsRequest(BaseModel):
|
||||
user_ids: List[int] = Field(..., min_items=1)
|
||||
user_ids: List[Union[int, str]] = Field(..., min_items=1)
|
||||
|
||||
|
||||
class QrBackupRequest(BaseModel):
|
||||
qr_data: str = Field(..., min_length=1)
|
||||
qr_group: Optional[str] = None
|
||||
|
||||
|
||||
class QrGroupAssignmentRequest(BaseModel):
|
||||
group_ids: List[Union[int, str]] = Field(..., min_items=1)
|
||||
|
||||
|
||||
class QrSyncRequest(BaseModel):
|
||||
qr_group: str = "default"
|
||||
|
||||
@@ -28,6 +28,7 @@ def create_app(
|
||||
app.state.user_handler = user_handler
|
||||
app.state.group_handler = group_handler
|
||||
app.state.admin_handler = admin_handler
|
||||
app.state.api_background_tasks = {"grouphelp_qr_sync": None}
|
||||
|
||||
@app.get("/health", tags=["meta"])
|
||||
async def health():
|
||||
|
||||
Reference in New Issue
Block a user