diff --git a/app/api/pranks.py b/app/api/pranks.py new file mode 100644 index 0000000..6c3e786 --- /dev/null +++ b/app/api/pranks.py @@ -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 + ) diff --git a/app/models/server/command.py b/app/models/server/command.py index d4e0f90..39170a5 100644 --- a/app/models/server/command.py +++ b/app/models/server/command.py @@ -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 # Сообщение для остальных diff --git a/app/models/server/prank.py b/app/models/server/prank.py new file mode 100644 index 0000000..7fc047a --- /dev/null +++ b/app/models/server/prank.py @@ -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 diff --git a/app/services/server/command.py b/app/services/server/command.py index 1debedb..0692c9d 100644 --- a/app/services/server/command.py +++ b/app/services/server/command.py @@ -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 ] diff --git a/app/services/server/event.py b/app/services/server/event.py index 0f46e7d..275c0fd 100644 --- a/app/services/server/event.py +++ b/app/services/server/event.py @@ -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 not event_type: + raise HTTPException(status_code=400, detail="Missing event 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}") + 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) - - print(f"[{datetime.now()}] Игрок {player_name} провел на сервере: {duration} секунд " - f"IP сервера: {server_ip}") - - 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"] + if not player_id or not player_name: + raise HTTPException(status_code=400, detail="Missing player data") - # Обновляем монеты через выделенный сервис - 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)}") - raise HTTPException(status_code=400, detail="Invalid event type") - - return {"status": "success", "message": "Event processed"} + # Обрабатываем информацию о сессии + await self._process_player_session(server_ip, player_id, player_name, duration) + return {"status": "success"} + # Если тип события не распознан + print(f"[{datetime.now()}] Неизвестное событие: {event_data}") + raise HTTPException(status_code=400, detail="Invalid event type") + + 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} сек.") diff --git a/app/services/server/prank.py b/app/services/server/prank.py new file mode 100644 index 0000000..e5bccb3 --- /dev/null +++ b/app/services/server/prank.py @@ -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"] + } diff --git a/main.py b/main.py index 3988aae..7fdadd0 100644 --- a/main.py +++ b/main.py @@ -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")