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
|
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
|
## Documentation
|
||||||
|
|
||||||
- **[ADMIN_IMPLEMENTATION.md](ADMIN_IMPLEMENTATION.md)** - Admin-only configuration features
|
- **[ADMIN_IMPLEMENTATION.md](ADMIN_IMPLEMENTATION.md)** - Admin-only configuration features
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ python-dotenv>=0.19.0
|
|||||||
fastapi>=0.95.0
|
fastapi>=0.95.0
|
||||||
uvicorn>=0.21.0
|
uvicorn>=0.21.0
|
||||||
pydantic>=1.10.0
|
pydantic>=1.10.0
|
||||||
|
python-multipart>=0.0.9
|
||||||
numpy>=1.26.0
|
numpy>=1.26.0
|
||||||
opencv-python-headless>=4.9.0.80
|
opencv-python-headless>=4.9.0.80
|
||||||
qrcode[pil]>=7.4.2
|
qrcode[pil]>=7.4.2
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Request
|
||||||
|
from fastapi import File, Form, Query, UploadFile
|
||||||
|
|
||||||
from src.api.auth import verify_api_key
|
from src.api.auth import verify_api_key
|
||||||
from src.api.schemas import (
|
from src.api.schemas import (
|
||||||
AddUserRequest,
|
AddUserRequest,
|
||||||
CommandResponse,
|
CommandResponse,
|
||||||
QrBackupRequest,
|
QrBackupRequest,
|
||||||
|
QrGroupAssignmentRequest,
|
||||||
|
QrSyncRequest,
|
||||||
UserIdsRequest,
|
UserIdsRequest,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -14,6 +20,7 @@ def _envelope(message: str) -> CommandResponse:
|
|||||||
|
|
||||||
|
|
||||||
router = APIRouter(prefix="/api/admin", tags=["admin"], dependencies=[Depends(verify_api_key)])
|
router = APIRouter(prefix="/api/admin", tags=["admin"], dependencies=[Depends(verify_api_key)])
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def _admin_chat_id(request: Request) -> int:
|
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)
|
@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
|
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)
|
@router.put("/qr-backup", response_model=CommandResponse)
|
||||||
async def set_qr_backup(payload: QrBackupRequest, request: Request):
|
async def set_qr_backup(payload: QrBackupRequest, request: Request):
|
||||||
handler = request.app.state.admin_handler
|
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)
|
@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
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
|
||||||
@@ -25,8 +25,17 @@ class AddUsersToGroupRequest(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class UserIdsRequest(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):
|
class QrBackupRequest(BaseModel):
|
||||||
qr_data: str = Field(..., min_length=1)
|
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.user_handler = user_handler
|
||||||
app.state.group_handler = group_handler
|
app.state.group_handler = group_handler
|
||||||
app.state.admin_handler = admin_handler
|
app.state.admin_handler = admin_handler
|
||||||
|
app.state.api_background_tasks = {"grouphelp_qr_sync": None}
|
||||||
|
|
||||||
@app.get("/health", tags=["meta"])
|
@app.get("/health", tags=["meta"])
|
||||||
async def health():
|
async def health():
|
||||||
|
|||||||
Reference in New Issue
Block a user