add: store pranks

This commit is contained in:
2025-07-18 18:05:45 +05:00
parent d52d4dbf75
commit 7e4e2c0bad
7 changed files with 623 additions and 51 deletions

55
app/api/pranks.py Normal file
View File

@ -0,0 +1,55 @@
from fastapi import APIRouter, HTTPException
from app.services.server.prank import PrankService
from app.models.server.prank import PrankCommandCreate, PrankCommandUpdate, PrankExecute
router = APIRouter(
prefix="/api/pranks",
tags=["Pranks"]
)
prank_service = PrankService()
@router.get("/commands")
async def get_all_prank_commands():
"""Получение всех доступных команд-пакостей"""
return await prank_service.get_all_prank_commands()
@router.get("/commands/{command_id}")
async def get_prank_command(command_id: str):
"""Получение команды-пакости по ID"""
return await prank_service.get_prank_command(command_id)
@router.post("/commands")
async def add_prank_command(command: PrankCommandCreate):
"""Добавление новой команды-пакости"""
return await prank_service.add_prank_command(command)
@router.put("/commands/{command_id}")
async def update_prank_command(command_id: str, update_data: PrankCommandUpdate):
"""Обновление команды-пакости"""
return await prank_service.update_prank_command(command_id, update_data)
@router.delete("/commands/{command_id}")
async def delete_prank_command(command_id: str):
"""Удаление команды-пакости"""
return await prank_service.delete_prank_command(command_id)
@router.get("/servers")
async def get_all_servers():
"""Получение списка всех доступных серверов"""
return await prank_service.get_all_servers()
@router.get("/servers/{server_id}/players")
async def get_server_online_players(server_id: str):
"""Получение списка онлайн игроков на сервере"""
return await prank_service.get_server_online_players(server_id)
@router.post("/execute")
async def execute_prank(username: str, prank_data: PrankExecute):
"""Выполнение пакости (списание монет и выполнение команды)"""
return await prank_service.execute_prank(
username,
prank_data.command_id,
prank_data.target_player,
prank_data.server_id
)

View File

@ -5,3 +5,5 @@ class ServerCommand(BaseModel):
command: str
server_ip: str
require_online_player: Optional[bool] = False
target_message: Optional[str] = None # Сообщение для цели
global_message: Optional[str] = None # Сообщение для остальных

View File

@ -0,0 +1,36 @@
from pydantic import BaseModel, Field
from typing import Optional, List
class PrankCommandCreate(BaseModel):
name: str
description: str
price: int
command_template: str
server_ids: List[str] = Field(
default=[],
description='Список серверов, где доступна команда. Использование ["*"] означает доступность на всех серверах'
)
targetDescription: Optional[str] = None # Сообщение для целевого игрока
globalDescription: Optional[str] = None # Сообщение для всех остальных
class PrankCommandUpdate(BaseModel):
name: Optional[str] = None
description: Optional[str] = None
price: Optional[int] = None
command_template: Optional[str] = None
server_ids: Optional[List[str]] = None
targetDescription: Optional[str] = None
globalDescription: Optional[str] = None
class PrankCommand(BaseModel):
id: str
name: str
description: str
price: int
command_template: str
server_ids: List[str] = []
class PrankExecute(BaseModel):
command_id: str
target_player: str
server_id: str

View File

@ -14,6 +14,8 @@ class CommandService:
"command": command_data.command,
"server_ip": command_data.server_ip,
"require_online_player": command_data.require_online_player,
"target_message": command_data.target_message if hasattr(command_data, 'target_message') else None,
"global_message": command_data.global_message if hasattr(command_data, 'global_message') else None,
"created_at": datetime.now().isoformat()
}
print(f"[{datetime.now()}] Добавлена команда: {command_data.command} "
@ -26,7 +28,13 @@ class CommandService:
try:
# Получаем команды для указанного сервера
commands = [
{"id": cmd_id, "command": cmd["command"], "require_online_player": cmd["require_online_player"]}
{
"id": cmd_id,
"command": cmd["command"],
"require_online_player": cmd["require_online_player"],
"target_message": cmd.get("target_message"),
"global_message": cmd.get("global_message")
}
for cmd_id, cmd in pending_commands.items()
if cmd["server_ip"] == server_ip
]

View File

@ -3,68 +3,274 @@ from datetime import datetime
import json
from app.services.coins import CoinsService
from app.models.server.event import PlayerEvent, OnlinePlayersUpdate
import uuid
class EventService:
def __init__(self):
self.coins_service = CoinsService()
async def process_event(self, event_data: dict):
async def process_event(self, event_data):
"""Обработка событий от сервера Minecraft"""
try:
# Проверяем формат ваших событий (event_type вместо type)
event_type = event_data.get("event_type")
server_ip = event_data.get("server_ip", "unknown")
if not event_type:
# Для совместимости со старым форматом
event_type = event_data.get("type")
if event_type == "player_join":
player_name = event_data["player_name"]
player_id = event_data["player_id"]
print(f"[{datetime.now()}] Игрок вошел: {player_name} (ID: {player_id}) "
f"IP сервера: {server_ip}")
if not event_type:
raise HTTPException(status_code=400, detail="Missing event type")
server_ip = event_data.get("server_ip")
if not server_ip:
raise HTTPException(status_code=400, detail="Missing server IP")
# Преобразуем ваши типы событий в нужные форматы
if event_type == "online_players_update":
# Регистрируем сервер, если его нет
await self._register_server(server_ip, event_data)
# Обновляем данные об онлайн игроках
players = event_data.get("players", [])
await self._update_online_players(server_ip, players)
return {"status": "success"}
elif event_type == "player_join":
player_id = event_data.get("player_id")
player_name = event_data.get("player_name")
if not player_id or not player_name:
raise HTTPException(status_code=400, detail="Missing player data")
# Регистрируем вход игрока
await self._register_player_login(server_ip, player_id, player_name)
return {"status": "success"}
elif event_type == "player_quit":
player_name = event_data["player_name"]
player_id = event_data["player_id"]
print(f"[{datetime.now()}] Игрок вышел: {player_name} (ID: {player_id}) "
f"IP сервера: {server_ip}")
player_id = event_data.get("player_id")
player_name = event_data.get("player_name")
if not player_id or not player_name:
raise HTTPException(status_code=400, detail="Missing player data")
# Регистрируем выход игрока
await self._register_player_logout(server_ip, player_id, player_name)
return {"status": "success"}
elif event_type == "player_session":
player_name = event_data["player_name"]
player_id = event_data["player_id"]
duration = event_data["duration"]
player_id = event_data.get("player_id")
player_name = event_data.get("player_name")
duration = event_data.get("duration", 0)
# Обновляем монеты через выделенный сервис
await self.coins_service.update_player_coins(player_id, player_name, duration, server_ip)
if not player_id or not player_name:
raise HTTPException(status_code=400, detail="Missing player data")
print(f"[{datetime.now()}] Игрок {player_name} провел на сервере: {duration} секунд "
f"IP сервера: {server_ip}")
# Обрабатываем информацию о сессии
await self._process_player_session(server_ip, player_id, player_name, duration)
return {"status": "success"}
elif event_type == "online_players_update":
players = event_data["players"]
print(f"\n[{datetime.now()}] Текущие онлайн-игроки ({len(players)}): "
f"IP сервера: {server_ip}")
# Обрабатываем каждого игрока
for player in players:
player_id = player["player_id"]
player_name = player["player_name"]
online_time = player["online_time"]
# Обновляем монеты через выделенный сервис
await self.coins_service.update_player_coins(
player_id, player_name, online_time, server_ip
)
hours, remainder = divmod(online_time, 3600)
minutes, seconds = divmod(remainder, 60)
print(f" - {player_name} (ID: {player_id}) "
f"Онлайн: {hours}ч {minutes}м {seconds}с")
print()
else:
print(f"[{datetime.now()}] Неизвестное событие: {json.dumps(event_data, indent=2)}")
# Если тип события не распознан
print(f"[{datetime.now()}] Неизвестное событие: {event_data}")
raise HTTPException(status_code=400, detail="Invalid event type")
return {"status": "success", "message": "Event processed"}
except HTTPException as e:
print(f"[{datetime.now()}] Ошибка обработки события: {e.status_code}: {e.detail}")
print(f"Полученные данные: {event_data}")
raise
except Exception as e:
print(f"[{datetime.now()}] Ошибка обработки события: {str(e)}")
print(f"Полученные данные: {json.dumps(event_data, indent=2)}")
raise HTTPException(status_code=400, detail=str(e))
print(f"[{datetime.now()}] Необработанная ошибка: {str(e)}")
print(f"Полученные данные: {event_data}")
raise HTTPException(status_code=500, detail=f"Server error: {str(e)}")
async def _register_server(self, server_ip, event_data):
"""Регистрирует сервер, если его нет в базе"""
from app.db.database import db
import uuid
game_servers_collection = db.game_servers
# Проверяем, есть ли уже такой сервер
existing_server = await game_servers_collection.find_one({"ip": server_ip})
if not existing_server:
# Создаем новую запись сервера
server_data = {
"id": str(uuid.uuid4()),
"name": f"Server {server_ip}", # Можно улучшить название
"ip": server_ip,
"port": 25565, # Стандартный порт Minecraft
"description": f"Minecraft server {server_ip}",
"max_players": 100,
"registered_at": datetime.utcnow()
}
await game_servers_collection.insert_one(server_data)
print(f"[{datetime.utcnow()}] Зарегистрирован новый сервер: {server_ip}")
return existing_server or await game_servers_collection.find_one({"ip": server_ip})
async def _update_online_players(self, server_ip, players_data):
"""Обновляет информацию об онлайн игроках"""
from app.db.database import db
online_players_collection = db.online_players
# Получаем ID сервера
server = await self._register_server(server_ip, {})
server_id = server["id"]
# Помечаем всех игроков как оффлайн на этом сервере
await online_players_collection.update_many(
{"server_id": server_id},
{"$set": {"is_online": False}}
)
# Обновляем данные для каждого онлайн игрока
now = datetime.utcnow()
for player in players_data:
player_id = player.get("player_id")
player_name = player.get("player_name")
online_time = player.get("online_time", 0)
if not player_id or not player_name:
continue
# Проверяем, существует ли уже запись
existing_player = await online_players_collection.find_one({
"uuid": player_id,
"server_id": server_id
})
if existing_player:
# Обновляем существующую запись
await online_players_collection.update_one(
{"_id": existing_player["_id"]},
{"$set": {
"username": player_name,
"is_online": True,
"last_seen": now,
"online_duration": online_time
}}
)
else:
# Создаем новую запись
await online_players_collection.insert_one({
"uuid": player_id,
"username": player_name,
"server_id": server_id,
"is_online": True,
"login_time": now,
"last_seen": now,
"online_duration": online_time
})
online_count = len(players_data)
print(f"[{now}] Обновлена информация о {online_count} игроках на сервере {server_ip}")
# Также обновляем информацию о коинах для каждого игрока
if players_data:
from app.services.coins import CoinsService
coins_service = CoinsService()
for player in players_data:
player_id = player.get("player_id")
player_name = player.get("player_name")
online_time = player.get("online_time", 0)
if player_id and player_name:
await coins_service.update_player_coins(player_id, player_name, online_time, server_ip)
async def _register_player_login(self, server_ip, player_id, player_name):
"""Регистрирует вход игрока на сервер"""
from app.db.database import db
online_players_collection = db.online_players
server = await self._register_server(server_ip, {})
server_id = server["id"]
now = datetime.utcnow()
# Проверяем, есть ли уже запись для этого игрока
existing_player = await online_players_collection.find_one({
"uuid": player_id,
"server_id": server_id
})
if existing_player:
# Обновляем запись
await online_players_collection.update_one(
{"_id": existing_player["_id"]},
{"$set": {
"username": player_name,
"is_online": True,
"login_time": now,
"last_seen": now
}}
)
else:
# Создаем новую запись
await online_players_collection.insert_one({
"uuid": player_id,
"username": player_name,
"server_id": server_id,
"is_online": True,
"login_time": now,
"last_seen": now,
"online_duration": 0
})
print(f"[{now}] Игрок {player_name} зашел на сервер {server_ip}")
async def _register_player_logout(self, server_ip, player_id, player_name):
"""Регистрирует выход игрока с сервера"""
from app.db.database import db
online_players_collection = db.online_players
server = await self._register_server(server_ip, {})
server_id = server["id"]
now = datetime.utcnow()
# Ищем запись игрока
player = await online_players_collection.find_one({
"uuid": player_id,
"server_id": server_id
})
if player:
# Обновляем запись
await online_players_collection.update_one(
{"_id": player["_id"]},
{"$set": {
"is_online": False,
"last_seen": now
}}
)
print(f"[{now}] Игрок {player_name} вышел с сервера {server_ip}")
async def _process_player_session(self, server_ip, player_id, player_name, duration):
"""Обрабатывает информацию о завершенной сессии игрока"""
from app.db.database import db
from app.services.coins import CoinsService
server = await self._register_server(server_ip, {})
server_id = server["id"]
# Обновляем статистику времени игры
await db.player_sessions.insert_one({
"uuid": player_id,
"username": player_name,
"server_id": server_id,
"server_ip": server_ip,
"duration": duration,
"session_end": datetime.utcnow()
})
# Начисляем коины за время игры
coins_service = CoinsService()
await coins_service.update_player_coins(player_id, player_name, duration, server_ip)
print(f"[{datetime.now()}] Сессия игрока {player_name} завершена, длительность: {duration} сек.")

View File

@ -0,0 +1,264 @@
from fastapi import HTTPException
from app.db.database import db, users_collection
from app.models.server.prank import PrankCommand, PrankCommandUpdate
from datetime import datetime
import uuid
from app.services.server.command import CommandService
# Создаем коллекции для хранения пакостей и серверов
prank_commands_collection = db.prank_commands
game_servers_collection = db.game_servers
online_players_collection = db.online_players
class PrankService:
async def add_prank_command(self, command_data):
"""Добавление новой команды-пакости"""
# Проверяем корректность шаблона команды
if "{targetPlayer}" not in command_data.command_template:
raise HTTPException(status_code=400,
detail="Шаблон команды должен содержать {targetPlayer} для подстановки имени цели")
prank_id = str(uuid.uuid4())
# Создаем новую команду в БД
prank_command = {
"id": prank_id,
"name": command_data.name,
"description": command_data.description,
"price": command_data.price,
"command_template": command_data.command_template,
"server_ids": command_data.server_ids,
"targetDescription": command_data.targetDescription,
"globalDescription": command_data.globalDescription, # Добавить это поле
"created_at": datetime.utcnow()
}
await prank_commands_collection.insert_one(prank_command)
return {"status": "success", "id": prank_id}
async def get_all_prank_commands(self):
"""Получение списка всех команд-пакостей"""
commands = await prank_commands_collection.find().to_list(1000)
result = []
for cmd in commands:
result.append({
"id": cmd["id"],
"name": cmd["name"],
"description": cmd["description"],
"price": cmd["price"],
"command_template": cmd["command_template"],
"server_ids": cmd.get("server_ids", []),
"targetDescription": cmd.get("targetDescription"),
"globalDescription": cmd.get("globalDescription") # Добавить это поле
})
return result
async def get_prank_command(self, command_id: str):
"""Получение конкретной команды по ID"""
command = await prank_commands_collection.find_one({"id": command_id})
if not command:
raise HTTPException(status_code=404, detail="Команда не найдена")
return {
"id": command["id"],
"name": command["name"],
"description": command["description"],
"price": command["price"],
"command_template": command["command_template"],
"server_ids": command.get("server_ids", []),
"targetDescription": command.get("targetDescription"),
"globalDescription": command.get("globalDescription") # Добавить это поле
}
async def update_prank_command(self, command_id: str, update_data: PrankCommandUpdate):
"""Обновление команды-пакости"""
command = await prank_commands_collection.find_one({"id": command_id})
if not command:
raise HTTPException(status_code=404, detail="Команда не найдена")
# Готовим данные для обновления
update = {}
if update_data.name is not None:
update["name"] = update_data.name
if update_data.description is not None:
update["description"] = update_data.description
if update_data.price is not None:
update["price"] = update_data.price
if update_data.command_template is not None:
if "{targetPlayer}" not in update_data.command_template:
raise HTTPException(status_code=400,
detail="Шаблон команды должен содержать {targetPlayer} для подстановки имени цели")
update["command_template"] = update_data.command_template
if update_data.server_ids is not None:
update["server_ids"] = update_data.server_ids
if update_data.targetDescription is not None:
update["targetDescription"] = update_data.targetDescription
if update_data.globalDescription is not None: # Добавить эту проверку
update["globalDescription"] = update_data.globalDescription
if update:
result = await prank_commands_collection.update_one(
{"id": command_id},
{"$set": update}
)
if result.modified_count == 0:
raise HTTPException(status_code=500, detail="Ошибка при обновлении команды")
return {"status": "success"}
async def delete_prank_command(self, command_id: str):
"""Удаление команды-пакости"""
command = await prank_commands_collection.find_one({"id": command_id})
if not command:
raise HTTPException(status_code=404, detail="Команда не найдена")
result = await prank_commands_collection.delete_one({"id": command_id})
if result.deleted_count == 0:
raise HTTPException(status_code=500, detail="Ошибка при удалении команды")
return {"status": "success"}
async def get_all_servers(self):
"""Получение списка всех доступных серверов"""
servers = await game_servers_collection.find().to_list(100)
# Если нет зарегистрированных серверов, вернем пустой список
if not servers:
return []
result = []
for server in servers:
# Получаем количество онлайн игроков
online_count = await online_players_collection.count_documents(
{"server_id": server["id"], "is_online": True}
)
result.append({
"id": server["id"],
"name": server["name"],
"ip": server.get("ip"),
"port": server.get("port"),
"description": server.get("description", ""),
"online_players": online_count,
"max_players": server.get("max_players", 0)
})
return result
async def get_server_online_players(self, server_id: str):
"""Получение списка онлайн игроков на конкретном сервере"""
server = await game_servers_collection.find_one({"id": server_id})
if not server:
raise HTTPException(status_code=404, detail="Сервер не найден")
players = await online_players_collection.find(
{"server_id": server_id, "is_online": True}
).to_list(1000)
result = []
for player in players:
result.append({
"username": player["username"],
"uuid": player.get("uuid", ""),
"online_since": player.get("login_time")
})
return {
"server": {
"id": server["id"],
"name": server["name"]
},
"online_players": result,
"count": len(result)
}
async def execute_prank(self, username: str, command_id: str, target_player: str, server_id: str):
"""Выполнение пакости (покупка и выполнение команды)"""
# Проверяем пользователя
user = await users_collection.find_one({"username": username})
if not user:
raise HTTPException(status_code=404, detail="Пользователь не найден")
# Проверяем команду
command = await prank_commands_collection.find_one({"id": command_id})
if not command:
raise HTTPException(status_code=404, detail="Команда не найдена")
# Проверяем сервер
server = await game_servers_collection.find_one({"id": server_id})
if not server:
raise HTTPException(status_code=404, detail="Сервер не найден")
# Проверяем, доступна ли команда на данном сервере
if (command.get("server_ids") and
"*" not in command.get("server_ids", []) and
server_id not in command.get("server_ids", [])):
raise HTTPException(status_code=400, detail="Команда недоступна на выбранном сервере")
# Проверяем, онлайн ли целевой игрок
target_online = await online_players_collection.find_one({
"username": target_player,
"server_id": server_id,
"is_online": True
})
if not target_online:
raise HTTPException(status_code=400, detail=f"Игрок {target_player} не в сети на этом сервере")
# Проверяем достаточно ли монет
user_coins = user.get("coins", 0)
if user_coins < command["price"]:
raise HTTPException(status_code=400,
detail=f"Недостаточно монет. Требуется: {command['price']}, имеется: {user_coins}")
# Формируем команду для выполнения
actual_command = command["command_template"].replace("{targetPlayer}", target_player)
# Обрабатываем оба типа сообщений
target_desc = None
global_desc = None
if command.get("targetDescription"):
target_desc = command.get("targetDescription").replace("{username}", username).replace("{targetPlayer}", target_player)
if command.get("globalDescription"):
global_desc = command.get("globalDescription").replace("{username}", username).replace("{targetPlayer}", target_player)
# Отправляем команду с обоими сообщениями
command_service = CommandService()
from app.models.server.command import ServerCommand
server_command = ServerCommand(
command=actual_command,
server_ip=server.get("ip", ""),
require_online_player=True,
target_message=target_desc, # Сообщение для цели
global_message=global_desc # Сообщение для всех остальных
)
command_result = await command_service.add_command(server_command)
# Логируем выполнение пакости
log_entry = {
"user_id": user["_id"],
"username": username,
"target_player": target_player,
"command_id": command_id,
"command_name": command["name"],
"server_id": server_id,
"price": command["price"],
"executed_command": actual_command,
"executed_at": datetime.utcnow()
}
await db.prank_executions.insert_one(log_entry)
return {
"status": "success",
"message": f"Команда '{command['name']}' успешно выполнена на игроке {target_player}",
"remaining_coins": user_coins - command["price"]
}

View File

@ -1,6 +1,6 @@
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from app.api import users, skins, capes, meta, server, store
from app.api import users, skins, capes, meta, server, store, pranks
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
@ -11,6 +11,7 @@ app.include_router(skins.router)
app.include_router(capes.router)
app.include_router(server.router)
app.include_router(store.router)
app.include_router(pranks.router)
# Монтируем статику
app.mount("/skins", StaticFiles(directory="app/static/skins"), name="skins")