Compare commits

...

2 Commits

Author SHA1 Message Date
99d9741e97 add websocket for coins 2025-12-29 13:10:39 +05:00
bddd68bc25 add material in prank 2025-12-29 12:11:14 +05:00
7 changed files with 100 additions and 5 deletions

25
app/api/coins_ws.py Normal file
View File

@ -0,0 +1,25 @@
# app/api/coins_ws.py
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Query
from app.realtime.coins_hub import coins_hub
router = APIRouter(tags=["Coins WS"])
@router.websocket("/ws/coins")
async def coins_ws(
websocket: WebSocket,
username: str = Query(...),
):
await coins_hub.connect(username, websocket)
try:
while True:
await websocket.receive_text() # ping / keep-alive
except WebSocketDisconnect:
pass
finally:
await coins_hub.disconnect(username, websocket)
@router.get("/ws/coins/ping")
async def ping():
return {"ok": True}

View File

@ -10,6 +10,7 @@ class PrankCommandCreate(BaseModel):
default=[],
description='Список серверов, где доступна команда. Использование ["*"] означает доступность на всех серверах'
)
material: str
targetDescription: Optional[str] = None # Сообщение для целевого игрока
globalDescription: Optional[str] = None # Сообщение для всех остальных

53
app/realtime/coins_hub.py Normal file
View File

@ -0,0 +1,53 @@
# app/realtime/coins_hub.py
from typing import Dict, Set
from fastapi import WebSocket
import asyncio
class CoinsHub:
def __init__(self):
# username -> set of websockets
self._connections: Dict[str, Set[WebSocket]] = {}
self._lock = asyncio.Lock()
async def connect(self, username: str, ws: WebSocket):
await ws.accept()
async with self._lock:
self._connections.setdefault(username, set()).add(ws)
async def disconnect(self, username: str, ws: WebSocket):
async with self._lock:
conns = self._connections.get(username)
if not conns:
return
conns.discard(ws)
if not conns:
self._connections.pop(username, None)
async def send_update(self, username: str, coins: int):
async with self._lock:
conns = list(self._connections.get(username, []))
if not conns:
return
payload = {
"event": "coins:update",
"coins": coins,
}
dead: list[WebSocket] = []
for ws in conns:
try:
await ws.send_json(payload)
except Exception:
dead.append(ws)
if dead:
async with self._lock:
for ws in dead:
self._connections.get(username, set()).discard(ws)
coins_hub = CoinsHub()

View File

@ -3,6 +3,7 @@ import re
from app.db.database import users_collection
from fastapi import HTTPException
from app.db.database import db
from app.realtime.coins_hub import coins_hub
MAX_MINUTES_PER_UPDATE = 120
@ -70,6 +71,8 @@ class CoinsService:
{"$set": {"coins": new_coins, "total_time_played": new_total_time}}
)
await coins_hub.send_update(user["username"], new_coins)
await coins_sessions_collection.insert_one({
"player_id": player_id,
"player_name": player_name,
@ -138,7 +141,9 @@ class CoinsService:
raise HTTPException(status_code=404, detail=f"Пользователь {username} не найден")
user = await users_collection.find_one({"username": username})
return user.get("coins", 0)
new_balance = user.get("coins", 0)
await coins_hub.send_update(username, new_balance)
return new_balance
async def decrease_balance(self, username: str, amount: int) -> int:
"""Уменьшить баланс пользователя"""
@ -154,4 +159,6 @@ class CoinsService:
raise HTTPException(status_code=404, detail=f"Пользователь {username} не найден")
user = await users_collection.find_one({"username": username})
return user.get("coins", 0)
new_balance = user.get("coins", 0)
await coins_hub.send_update(username, new_balance)
return new_balance

View File

@ -8,6 +8,8 @@ import random
from fastapi import HTTPException
from app.db.database import db, users_collection
from app.services.coins import CoinsService
TZ = ZoneInfo("Asia/Yekaterinburg") # как в dailyreward :contentReference[oaicite:1]{index=1}
coins_sessions_collection = db.coins_sessions
@ -168,7 +170,8 @@ class DailyQuestsService:
return {"claimed": False, "reason": "not_completed"}
# 2) начисляем coins
await users_collection.update_one({"username": username}, {"$inc": {"coins": reward}})
coins_service = CoinsService()
await coins_service.increase_balance(username, reward)
# 3) лог в coins_sessions (как daily_login) :contentReference[oaicite:4]{index=4}
await coins_sessions_collection.insert_one({
@ -219,7 +222,8 @@ class DailyQuestsService:
continue # уже claimed/не completed
# начисляем coins
await users_collection.update_one({"username": username}, {"$inc": {"coins": reward}})
coins_service = CoinsService()
await coins_service.increase_balance(username, reward)
total_added += reward
# лог (как в claim) :contentReference[oaicite:2]{index=2}

View File

@ -1,6 +1,7 @@
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
from app.db.database import users_collection, db
from app.realtime.coins_hub import coins_hub
coins_sessions_collection = db.coins_sessions
@ -71,6 +72,9 @@ class DailyRewardService:
user2 = await users_collection.find_one({"username": username})
return {"claimed": False, "reason": "already_claimed_today", "streak": user2.get("daily_streak", 0)}
new_balance = (await users_collection.find_one({"username": username})).get("coins", 0)
await coins_hub.send_update(username, new_balance)
await coins_sessions_collection.insert_one({
"player_name": username,
"update_type": "daily_login",

View File

@ -10,7 +10,7 @@ from app.core.config import CAPES_DIR, CAPES_STORE_DIR, SKINS_DIR
from app.services.promo import PromoService
from app.webhooks import telegram
from app.db.database import users_collection
from app.api import marketplace_ws
from app.api import marketplace_ws, coins_ws
from app.db.database import users_collection, sessions_collection
@ -68,6 +68,7 @@ app.include_router(store.router)
app.include_router(pranks.router)
app.include_router(marketplace.router)
app.include_router(marketplace_ws.router)
app.include_router(coins_ws.router)
app.include_router(case.router)
app.include_router(inventory.router)
app.include_router(bonuses.router)