Files
popa_minecraft_launcher_api/app/services/marketplace.py

244 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import uuid
from datetime import datetime
from fastapi import HTTPException
from app.db.database import db
from app.services.coins import CoinsService
from app.services.server.command import CommandService
# Коллекция для хранения товаров на торговой площадке
marketplace_collection = db.marketplace_items
# Добавьте эту функцию
def _serialize_mongodb_doc(doc):
"""Преобразует MongoDB документ для JSON сериализации"""
if doc is None:
return None
result = {}
for key, value in doc.items():
# Обработка ObjectId
if key == "_id":
result["_id"] = str(value)
continue
# Обработка ISODate
if isinstance(value, datetime):
result[key] = value.isoformat()
# Обработка вложенных словарей
elif isinstance(value, dict):
if "$date" in value:
# Это ISODate
result[key] = datetime.fromisoformat(value["$date"].replace("Z", "+00:00")).isoformat()
else:
result[key] = _serialize_mongodb_doc(value)
# Обработка списков
elif isinstance(value, list):
result[key] = [_serialize_mongodb_doc(item) if isinstance(item, dict) else item for item in value]
else:
result[key] = value
return result
class MarketplaceService:
async def list_items(self, server_ip: str = None, page: int = 1, limit: int = 20):
"""Получить список предметов на торговой площадке"""
query = {}
if server_ip:
query["server_ip"] = server_ip
total = await marketplace_collection.count_documents(query)
items_cursor = marketplace_collection.find(query) \
.sort("created_at", -1) \
.skip((page - 1) * limit) \
.limit(limit)
items = await items_cursor.to_list(limit)
# Преобразуем каждый документ
serialized_items = [_serialize_mongodb_doc(item) for item in items]
return {
"items": serialized_items,
"total": total,
"page": page,
"pages": (total + limit - 1) // limit
}
async def get_item(self, item_id: str):
"""Получить информацию о конкретном предмете"""
item = await marketplace_collection.find_one({"id": item_id})
if not item:
raise HTTPException(status_code=404, detail="Предмет не найден")
return _serialize_mongodb_doc(item)
async def add_item(self, username: str, slot_index: int, amount: int, price: int, server_ip: str):
"""Выставить предмет на продажу"""
# 1. Получаем инвентарь игрока
cmd_service = CommandService()
inventory_result = await cmd_service.get_player_inventory(username, server_ip, timeout=15)
if inventory_result["status"] != "success":
raise HTTPException(status_code=400,
detail=f"Не удалось получить инвентарь игрока. Игрок должен быть онлайн.")
# 2. Находим предмет в указанном слоте
item_data = None
for item in inventory_result.get("inventory", []):
if item.get("slot") == slot_index:
item_data = item
break
if not item_data or item_data.get("material") == "AIR" or item_data.get("amount") < amount:
raise HTTPException(status_code=400,
detail=f"В указанном слоте нет предмета или недостаточное количество")
# 3. Создаем запись о предмете на торговой площадке
item_id = str(uuid.uuid4())
marketplace_item = {
"id": item_id,
"material": item_data.get("material"),
"amount": amount,
"price": price,
"seller_name": username,
"server_ip": server_ip,
"display_name": item_data.get("display_name"),
"lore": item_data.get("lore"),
"enchants": item_data.get("enchants"),
"item_data": item_data,
"created_at": datetime.utcnow()
}
await marketplace_collection.insert_one(marketplace_item)
# 4. Удаляем предмет из инвентаря игрока
# Определяем тип слота и его номер для команды replaceitem
slot_type = "inventory"
slot_num = slot_index
# Преобразуем слот инвентаря в соответствующий формат для команды
if 0 <= slot_index <= 8:
# Панель быстрого доступа (хотбар)
slot_type = "hotbar"
slot_num = slot_index
elif 9 <= slot_index <= 35:
# Основной инвентарь
slot_type = "inventory"
slot_num = slot_index - 9
elif slot_index == 36:
# Ботинки
slot_type = "armor.feet"
slot_num = 0
elif slot_index == 37:
# Поножи
slot_type = "armor.legs"
slot_num = 0
elif slot_index == 38:
# Нагрудник
slot_type = "armor.chest"
slot_num = 0
elif slot_index == 39:
# Шлем
slot_type = "armor.head"
slot_num = 0
elif slot_index == 40:
# Вторая рука
slot_type = "weapon.offhand"
slot_num = 0
# Выполняем команду
# Для Minecraft 1.16.5+
command = f"item replace entity {username} {slot_type}.{slot_num} with air"
# Для более старых версий (1.13-1.16)
# command = f"replaceitem entity {username} {slot_type}.{slot_num} air"
from app.models.server.command import ServerCommand
cmd = ServerCommand(
command=command,
server_ip=server_ip,
require_online_player=True,
target_message=f"Вы выставили на продажу {amount} шт. предмета {item_data.get('display_name', item_data.get('material'))} за {price} монет"
)
await cmd_service.add_command(cmd)
return {"status": "success", "item_id": item_id}
async def buy_item(self, buyer_username: str, item_id: str):
"""Купить предмет с торговой площадки"""
# 1. Находим предмет
item = await marketplace_collection.find_one({"id": item_id})
if not item:
raise HTTPException(status_code=404, detail="Предмет не найден")
# 2. Проверяем, что покупатель не является продавцом
if item["seller_name"] == buyer_username:
raise HTTPException(status_code=400, detail="Вы не можете купить свой же предмет")
# 3. Проверяем баланс покупателя
coins_service = CoinsService()
buyer_balance = await coins_service.get_balance(buyer_username)
if buyer_balance < item["price"]:
raise HTTPException(status_code=400,
detail=f"Недостаточно монет. Требуется: {item['price']}, имеется: {buyer_balance}")
# 4. Проверяем, что покупатель онлайн на сервере
cmd_service = CommandService()
try:
await cmd_service.get_player_inventory(buyer_username, item["server_ip"], timeout=5)
except:
raise HTTPException(status_code=400,
detail=f"Вы должны быть онлайн на сервере для совершения покупки")
# 5. Списываем деньги с покупателя
await coins_service.decrease_balance(buyer_username, item["price"])
# 6. Начисляем деньги продавцу
await coins_service.increase_balance(item["seller_name"], item["price"])
# 7. Добавляем предмет в инвентарь покупателя
material = item["material"]
amount = item["amount"]
# Создаем команду с учетом всех свойств предмета
command_base = f"give {buyer_username} {material} {amount}"
# Если у предмета есть мета-данные, добавляем их через NBT
nbt_tags = []
if item.get("display_name"):
nbt_tags.append(f'display:{{Name:\'[{{"text":"{item["display_name"]}","italic":false}}]\'}}')
if item.get("lore"):
lore_json = ','.join([f'[{{"text":"{line}","italic":false}}]' for line in item["lore"]])
nbt_tags.append(f'display:{{Lore:[{lore_json}]}}')
if item.get("enchants"):
enchant_tags = []
for ench_id, level in item["enchants"].items():
enchant_tags.append(f'{{id:"{ench_id}",lvl:{level}s}}')
nbt_tags.append(f'Enchantments:[{",".join(enchant_tags)}]')
if nbt_tags:
command_base += " " + "{" + ",".join(nbt_tags) + "}"
from app.models.server.command import ServerCommand
cmd = ServerCommand(
command=command_base,
server_ip=item["server_ip"],
require_online_player=True,
target_message=f"Вы купили {amount} шт. предмета {item.get('display_name', material)} за {item['price']} монет"
)
await cmd_service.add_command(cmd)
# 8. Удаляем предмет с торговой площадки
await marketplace_collection.delete_one({"id": item_id})
return {
"status": "success",
"message": f"Вы купили {amount} шт. предмета {item.get('display_name', material)}",
"remaining_balance": buyer_balance - item["price"]
}