const express = require("express");
const bodyParser = require("body-parser");
const multer = require("multer");
const cors = require("cors");
require("dotenv").config();
const fs = require("fs");
const http = require("http");

const app = express();
const PORT = process.env.PORT || 8080;
app.use(
    cors({
        origin: "*",
        methods: ["GET", "POST", "PUT", "DELETE"],
        allowedHeaders: ["Content-Type", "Authorization"],
        maxAge: 3600,
    })
);

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const server = http.createServer(app);

const io = require("socket.io")(server, {
    cors: {
        origin: "*",
        methods: ["GET", "POST"],
    },
    transports: ["websocket", "polling"],
});
let users = [];
let cornerSectors = [];

const storage = multer.memoryStorage();
const upload = multer({ storage: storage });
app.get("/", (req, res) => {
    res.send("It`s my server");
});

app.post("/api/upload_photo", upload.single("photo"), (req, res) => {
    const fileBuffer = req.file.buffer;
    const filePath = "./uploads/bg.png";
    fs.writeFileSync(filePath, fileBuffer);
    console.log(`Файл сохранен: ${filePath}`);
    res.send("Файл сохранен!");
});

app.get("/api/get_photo", (req, res) => {
    const filePath = "./uploads/bg.png";
    console.log("success");
    fs.readFile(filePath, (err, data) => {
        if (err) {
            res.status(404).send("Фото не найдено");
        } else {
            res.set("Content-Type", "image/png");
            res.send(data);
        }
    });
});

app.post("/api/cornerSectors", (req, res) => {
    cornerSectors = req.body;
    res.sendStatus(200);
});

app.post("/api/data", (req, res) => {
    const data = req.body;
    users = data.map((row, index) => ({
        user_name: row[0],
        code: row[1].toString(),
        player_id: row[2],
        total_user_points: 0,
        total_user_fish: 0,
        circles: row.slice(3).map((pair, i) => {
            const [opponent_number, sector] = pair;
            const opponent = data.find((r) => r[2] === opponent_number) || {};
            const opponent_name = opponent[0] || "Unknown Opponent";
            return {
                number: i + 1,
                status: i === 0 ? "active" : "inactive",
                index_circle: i + 1,
                second_circle: false,
                playerGame: {
                    approveState: 1,
                    number: row[2],
                    name: row[0],
                    fishCount: null,
                    points: 0,
                    total_points: 0,
                    sector: sector,
                },
                opponentGame: {
                    approveState: 1,
                    number: opponent_number,
                    name: opponent_name,
                    fishCount: null,
                    points: 0,
                    sector: sector,
                    total_points: 0,
                },
            };
        }),
    }));

    const usersWithEmptyName = users.filter(
        (user) => user.user_name === "Empty"
    );

    if (usersWithEmptyName.length > 0) {
        let finalNewUsers = users.map((user) => ({ ...user, circles: [] }));

        for (let i = 0; i < users[0].circles.length; i++) {
            let newUsers = users.map((user) => ({ ...user, circles: [] }));
            for (let j = 0; j < newUsers.length; j++) {
                const circle = users[j].circles[i];
                const newCircle = {
                    number: i + 1,
                    status: i === 0 ? "active" : "inactive",
                    index_circle: i + 1,
                    second_circle: false,
                    playerGame: circle.playerGame,
                    opponentGame: circle.opponentGame,
                };
                newUsers[j].circles.push(newCircle);
            }
            for (let j = 0; j < newUsers.length; j++) {
                const user = newUsers[j];
                const circle = newUsers[j].circles[0];
                if (circle.opponentGame.name === "Empty") {
                    const sectorEmpty = circle.opponentGame.sector;
                    const changeSectorEmpty = cornerSectors.includes(
                        sectorEmpty
                    )
                        ? 1
                        : -1;
                    let userNewOpponent;
                    let userNewOpponentIndex;
                    for (let k = 0; k < newUsers.length; k++) {
                        const opponent = newUsers[k];
                        if (
                            changeSectorEmpty === -1 &&
                            opponent.circles[0].playerGame.sector ===
                                sectorEmpty - 1 &&
                            opponent.player_id % 2 !== (i + 1) % 2
                        ) {
                            userNewOpponent = opponent;
                            userNewOpponentIndex = k;
                            break;
                        }
                    }
                    if (!userNewOpponent) {
                        for (let k = 0; k < newUsers.length; k++) {
                            const opponent = newUsers[k];
                            if (
                                changeSectorEmpty === 1 &&
                                opponent.circles[0].playerGame.sector ===
                                    sectorEmpty + 1 &&
                                opponent.player_id % 2 === (i + 1) % 2
                            ) {
                                userNewOpponent = opponent;
                                userNewOpponentIndex = k;
                                break;
                            }
                        }
                    }

                    if (userNewOpponent) {
                        const newCircleOpponent = {
                            number: i + 1,
                            index_circle: i + 1,
                            second_circle: true,
                            status: i === 0 ? "active" : "inactive",
                            playerGame: userNewOpponent.circles[0].playerGame,
                            opponentGame: circle.playerGame,
                        };
                        newUsers[userNewOpponentIndex].circles.push(
                            newCircleOpponent
                        );
                        newUsers[j].circles[0].opponentGame =
                            userNewOpponent.circles[0].playerGame;
                    } else {
                        console.log(
                            "Не найден оппонент для пользователя",
                            user.user_name
                        );
                    }
                }
            }
            for (let j = 0; j < newUsers.length; j++) {
                for (let k = 0; k < newUsers[j].circles.length; k++) {
                    finalNewUsers[j].circles.push(newUsers[j].circles[k]);
                }
            }
        }
        for (let j = 0; j < finalNewUsers.length; j++) {
            for (let k = 0; k < finalNewUsers[j].circles.length; k++) {
                const circle = finalNewUsers[j].circles[k];
                const newCircle = {
                    number: k + 1,
                    index_circle: circle.index_circle,
                    second_circle: circle.second_circle,
                    status: circle.status,
                    playerGame: circle.playerGame,
                    opponentGame: circle.opponentGame,
                };
                finalNewUsers[j].circles[k] = newCircle;
            }
        }
        users = finalNewUsers;
    }
    res.sendStatus(200);
});

app.get("/api/users", (req, res) => {
    res.json(users);
});

app.get("/api/user/:code", (req, res) => {
    const code = req.params.code.toString();
    const user = users.find((user) => user.code === code);
    console.log("success");
    if (user) {
        res.json(user);
    } else {
        res.status(404).json({ message: "Пользователь не найден" });
    }
});

app.get("/api/unready_players", (req, res) => {
    const usersForUnreadyPlayers = [...users];
    const unreadyPlayers = usersForUnreadyPlayers
        .filter(
            (user) =>
                user.user_name !== "Empty" &&
                user.circles.some(
                    (circle) =>
                        circle.status === "active" &&
                        circle.playerGame.approveState !== 4 &&
                        circle.playerGame.approveState !== 2
                )
        )
        .map((user) => `#${user.player_id} ${user.user_name}`);

    if (unreadyPlayers.length > 0) {
        res.json(unreadyPlayers);
    } else {
        res.json({ message: "Нет неготовых игроков" });
    }
});

app.get("/api/stats", (req, res) => {
    const usersStats = [...users];
    const resultArray = usersStats
        .filter((user) => user.user_name !== "Empty")
        .map((user) => {
            const circlePoints = user.circles
                .filter((circle) => !circle.second_circle)
                .map((circle) => circle.playerGame.points);
            return [
                user.player_id,
                user.user_name,
                user.total_user_points,
                user.total_user_fish,
                ...circlePoints,
            ];
        })
        .sort((a, b) => {
            if (a[2] === b[2]) {
                return b[3] - a[3];
            } else {
                return b[2] - a[2];
            }
        });

    res.json(resultArray);
});

app.get("/api/circles", (req, res) => {
    res.json(users.map((user) => user.circles));
});

app.post("/api/update-score", (req, res) => {
    const { userData, activeCircleNumber } = req.body;
    let updatedUserData = userData;
    const circle = updatedUserData.circles.find(
        (circle) => circle.number === activeCircleNumber
    );
    const userIndex = users.findIndex(
        (user) => user.code === updatedUserData.code
    );
    if (userIndex === -1) {
        return res.status(404).json({ message: "User not found" });
    }
    users[userIndex] = updatedUserData;
    const opponentIndex = users.findIndex(
        (user) => user.player_id === circle.opponentGame.number
    );
    if (opponentIndex !== -1) {
        const opponentCircleIndex = users[opponentIndex].circles.findIndex(
            (c) =>
                c.opponentGame.number === updatedUserData.player_id &&
                c.status === "active"
        );
        if (opponentCircleIndex !== -1) {
            users[opponentIndex].circles[
                opponentCircleIndex
            ].opponentGame.fishCount = circle.playerGame.fishCount;
        }
    }
    updatedUserData = users[userIndex];
    io.emit("userUpdated", { updatedUserData, activeCircleNumber });

    res.sendStatus(200);
});

app.post("/api/update-approve-state", (req, res) => {
    const { userData, activeCircleNumber } = req.body;
    let updatedUserData = userData;
    let needToCheckNewTour = false;
    const circle = updatedUserData.circles.find(
        (c) => c.number === activeCircleNumber
    );
    const userIndex = users.findIndex(
        (user) => user.code === updatedUserData.code
    );
    const opponentIndex = users.findIndex(
        (user) => user.player_id === circle.opponentGame.number
    );
    if (opponentIndex !== -1) {
        const opponent = users[opponentIndex];
        const opponentCircleIndex = opponent.circles.findIndex(
            (c) =>
                c.opponentGame.number === updatedUserData.player_id &&
                c.status === "active"
        );
        const playerCircleIndex = updatedUserData.circles.findIndex(
            (c) => c.number === circle.number
        );
        if (opponentCircleIndex !== -1) {
            opponent.circles[opponentCircleIndex].opponentGame.approveState =
                circle.playerGame.approveState;
            users[opponentIndex] = opponent;
            let playerApproveState = circle.playerGame.approveState;
            let playerOppApproveState = circle.playerGame.approveState;
            let opponentApproveState =
                opponent.circles[opponentCircleIndex].playerGame.approveState;
            let opponentOppApproveState =
                opponent.circles[opponentCircleIndex].opponentGame.approveState;

            if (playerApproveState === 1 && opponentApproveState === 1) {
                playerApproveState = 2;
                playerOppApproveState = 3;

                opponentApproveState = 3;
                opponentOppApproveState = 2;
            } else if (playerApproveState === 2 && opponentApproveState === 3) {
                playerApproveState = 1;
                playerOppApproveState = 1;

                opponentApproveState = 1;
                opponentOppApproveState = 1;
            } else if (playerApproveState === 3 && opponentApproveState === 2) {
                playerApproveState = 4;
                playerOppApproveState = 4;

                opponentApproveState = 4;
                opponentOppApproveState = 4;
                needToCheckNewTour = true;
            } else if (playerApproveState === 4 && opponentApproveState === 4) {
                playerApproveState = 3;
                playerOppApproveState = 2;

                opponentApproveState = 2;
                opponentOppApproveState = 3;
            } else {
                playerApproveState = 1;
                playerOppApproveState = 1;

                opponentApproveState = 1;
                opponentOppApproveState = 1;
            }
            users[userIndex].circles = users[userIndex].circles.map(
                (circle, index) => {
                    if (index === playerCircleIndex) {
                        return {
                            ...circle,
                            playerGame: {
                                ...circle.playerGame,
                                approveState: playerApproveState,
                            },
                            opponentGame: {
                                ...circle.opponentGame,
                                approveState: playerOppApproveState,
                            },
                        };
                    }
                    return circle;
                }
            );

            users[opponentIndex].circles = users[opponentIndex].circles.map(
                (circle, index) => {
                    if (index === opponentCircleIndex) {
                        return {
                            ...circle,
                            playerGame: {
                                ...circle.playerGame,
                                approveState: opponentApproveState,
                            },
                            opponentGame: {
                                ...circle.opponentGame,
                                approveState: opponentOppApproveState,
                            },
                        };
                    }
                    return circle;
                }
            );

            updatedUserData = { ...users[userIndex] };
        } else {
            console.error(
                `Active circle not found for opponent with player_id ${circle.opponentGame.number}`
            );
        }
    } else {
        console.error(
            `Opponent with player_id ${circle.opponentGame.number} not found`
        );
    }
    io.emit("userUpdated", { updatedUserData, activeCircleNumber });
    if (needToCheckNewTour) {
        checkAndUpdateRoundStatus();
    }
    res.sendStatus(200);
});

io.on("connection", (socket) => {
    socket.on("userUpdate", (data) => {
        const { updatedUserData, activeCircleNumber } = data;
        io.emit("userUpdated", { updatedUserData, activeCircleNumber });
    });

    socket.on("allUsersUpdated", (users) => {
        io.emit("allUsersUpdated", users);
    });

    socket.on("disconnect", () => {});
});

function checkAndUpdateRoundStatus() {
    let allCirclesApproved = true;
    users.forEach((user) => {
        user.circles.forEach((circle) => {
            if (
                circle.status === "active" &&
                circle.playerGame.approveState !== 4 &&
                circle.playerGame.name !== "Empty"
            ) {
                allCirclesApproved = false;
            }
        });
    });
    if (allCirclesApproved) {
        users.forEach((user, userIndex) => {
            const activeCircleIndex = user.circles.findIndex(
                (c) => c.status === "active"
            );
            if (activeCircleIndex !== -1) {
                let newRoundPoints = 0;
                const playerFish =
                    user.circles[activeCircleIndex].playerGame.fishCount;
                const opponentFish =
                    user.circles[activeCircleIndex].opponentGame.fishCount;
                if (playerFish === 0 && opponentFish === 0) {
                    newRoundPoints = 1;
                } else if (playerFish === opponentFish && playerFish > 0) {
                    newRoundPoints = 2;
                } else if (playerFish > opponentFish) {
                    newRoundPoints = 4;
                } else if (playerFish < opponentFish && playerFish > 0) {
                    newRoundPoints = 1;
                } else if (playerFish < opponentFish && playerFish === 0) {
                    newRoundPoints = 0;
                }
                users[userIndex].total_user_points =
                    users[userIndex].total_user_points + newRoundPoints;
                users[userIndex].total_user_fish =
                    users[userIndex].total_user_fish + playerFish;
                users[userIndex].circles[activeCircleIndex].playerGame.points =
                    newRoundPoints;
                users[userIndex].circles[
                    activeCircleIndex
                ].playerGame.total_points = users[userIndex].total_user_points;
                users.forEach((userOpponent, userIndexOpponent) => {
                    userOpponent.circles.forEach(
                        (circleOpponent, circleIndexOpponent) => {
                            if (
                                circleOpponent.opponentGame.number ===
                                user.player_id
                            ) {
                                if (circleOpponent.status === "active") {
                                    users[userIndexOpponent].circles[
                                        circleIndexOpponent
                                    ].opponentGame.points =
                                        users[userIndex].circles[
                                            activeCircleIndex
                                        ].playerGame.points;
                                }
                                users[userIndexOpponent].circles[
                                    circleIndexOpponent
                                ].opponentGame.total_points =
                                    users[userIndex].circles[
                                        activeCircleIndex
                                    ].playerGame.total_points;
                            } else if (
                                circleOpponent.playerGame.number ===
                                user.player_id
                            ) {
                                users[userIndexOpponent].circles[
                                    circleIndexOpponent
                                ].playerGame.total_points =
                                    users[userIndex].circles[
                                        activeCircleIndex
                                    ].playerGame.total_points;
                            }
                        }
                    );
                });
            }
        });
        /*
        Выше я для каждого пользователя сначала обновил его очки и рыбы глобально, затем всем его оппонентам в кругах обновил его данные об общих очках
        И если мы встретили текущего оппонента, ему обновляем наши points текущие для этого раунда
        Таким образом мы каждому игроку обновили его глобальные данные, данные в его кругах, данные о нем в кругах всех его оппонентов
        И после прохода по всем игрокам будет обновлена информация у всех про всех
        Ниже происходит обновление статуса кругов и emit в socket.
        */
        users.forEach((user, userIndex) => {
            const activeCircles = user.circles.filter(
                (c) => c.status === "active"
            );

            // Найти все неактивные круги
            const inactiveCircles = user.circles.filter(
                (c) => c.status === "inactive"
            );

            activeCircles.forEach((activeCircle) => {
                const nextCircleIndex = user.circles.indexOf(activeCircle) + 1;
                let nextInactiveCircleIndex;
                let secondInactiveCircle;
                if (nextCircleIndex < user.circles.length) {
                    const nextCircle = user.circles[nextCircleIndex];
                    if (nextCircle.status === "inactive") {
                        nextInactiveCircleIndex = nextCircleIndex + 1;
                        nextCircle.status = "active";
                        nextCircle.playerGame.approveState = 1;
                        nextCircle.opponentGame.approveState = 1;
                        nextCircle.playerGame.fishCount = null;
                        nextCircle.opponentGame.fishCount = null;
                    }
                }

                if (nextInactiveCircleIndex < user.circles.length) {
                    secondInactiveCircle =
                        user.circles[nextInactiveCircleIndex];
                }
                if (
                    secondInactiveCircle &&
                    secondInactiveCircle.second_circle
                ) {
                    if (secondInactiveCircle.status === "inactive") {
                        secondInactiveCircle.status = "active";
                        secondInactiveCircle.playerGame.approveState = 1;
                        secondInactiveCircle.opponentGame.approveState = 1;
                        secondInactiveCircle.playerGame.fishCount = null;
                        secondInactiveCircle.opponentGame.fishCount = null;
                    }
                }
            });

            activeCircles.forEach((activeCircle) => {
                activeCircle.status = "completed";
            });
        });
        io.emit("allUsersUpdated", users);
    }
}

server.listen(PORT, () => {
    console.log(`Server is running on http://localhost:${PORT}`);
});

Это мой сервер
import asyncio
import requests
import pandas as pd
from aiogram import F
from aiogram import Bot, Dispatcher, types
from aiogram.filters import Command
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.types import FSInputFile
from datetime import datetime
import numpy as np

import random
import string
import aiohttp
import os
import json

from dotenv import load_dotenv
from aiohttp import FormData

load_dotenv()

server = os.getenv("WEB_APP_URL")
bot_token = os.getenv("BOT_TOKEN")
web_url = os.getenv("WEB_URL")


def load_config():
    with open("config.json", "r") as file:
        return json.load(file)


def save_config(config):
    with open("config.json", "w") as file:
        json.dump(config, file, indent=4)


config = load_config()
TOKEN = bot_token
bot = Bot(token=TOKEN)

# Диспетчер
dp = Dispatcher()

admin_ids = config["admin_ids"]
user_props = config.get("user_props", [])
messages_to_delete = {}
user_props_lock = asyncio.Lock()
user_state = {}
registration_open = False


def update_admin_ids(new_admin_ids):
    config["admin_ids"] = new_admin_ids
    save_config(config)


def update_user_props(new_user_props):
    config["user_props"] = new_user_props
    save_config(config)


async def update_stats():
    previous_stats = None
    while True:
        await asyncio.sleep(90)
        try:
            async with aiohttp.ClientSession() as session:
                async with session.get(server + "/api/stats") as response:
                    if response.status == 200:
                        result_array = await response.json()
                        if result_array != previous_stats and any(result_array):
                            previous_stats = result_array
                            columns = ["№ участника", "Участник", "Баллы", "Рыбы"] + [
                                f"Баллы" for _ in range(len(result_array[0]) - 4)
                            ]
                            df = pd.DataFrame(result_array, columns=columns)
                            df.loc[-1] = columns
                            df.index = df.index + 1
                            df = df.sort_index()

                            # Сохранение в Excel файл
                            file_path = "./statistics.xlsx"
                            df.to_excel(file_path, index=False, header=False)
                            document = FSInputFile("./statistics.xlsx")
                            for admin_id in admin_ids:
                                await bot.send_document(
                                    chat_id=admin_id, document=document
                                )
                            os.remove(file_path)
        except aiohttp.ClientError as e:
            print(f"Ошибка: {e}")
            await asyncio.sleep(1)
        except Exception as e:
            print(f"Ошибка: {e}")
            await asyncio.sleep(1)


async def check_registered_users():
    previous_user_count = len(user_props)
    while True:
        await asyncio.sleep(60)
        current_user_count = len(user_props)
        if current_user_count != previous_user_count and current_user_count > 0:
            previous_user_count = current_user_count
            # Создать эксель-файл
            df = pd.DataFrame()
            df["User"] = [user_props[i]["name"] for i in range(len(user_props))]
            df["Code"] = [user_props[i]["code"] for i in range(len(user_props))]
            df["Index"] = [i + 1 for i in range(len(user_props))]
            df["Time"] = [user_props[i]["timestamp"] for i in range(len(user_props))]
            file_path = "./registration.xlsx"
            df.to_excel(file_path, index=False, header=False)
            document = FSInputFile(file_path)
            for admin_id in admin_ids:
                await bot.send_document(chat_id=admin_id, document=document)
            # Удалить файл
            os.remove(file_path)


@dp.message(Command("start"))
async def cmd_start(message: types.Message):
    if message.from_user.id in admin_ids:
        builder = InlineKeyboardBuilder()
        builder.row(
            InlineKeyboardButton(text="Прислать жеребьевку", callback_data="send_draw")
        )
        builder.row(InlineKeyboardButton(text="Узнать ID", callback_data="get_id"))
        builder.row(
            InlineKeyboardButton(text="Добавить админа", callback_data="add_admin")
        )
        builder.row(
            InlineKeyboardButton(
                text="Начать регистрацию", callback_data="start_registration"
            )
        )
        builder.row(
            InlineKeyboardButton(
                text="Закрыть регистрацию", callback_data="close_registration"
            )
        )
        builder.row(
            InlineKeyboardButton(
                text="Получить ссылку для зрителей", callback_data="get_email"
            )
        )
        builder.row(
            InlineKeyboardButton(
                text="Неготовые игроки", callback_data="get_unready_players"
            )
        )
        builder.row(
            InlineKeyboardButton(
                text="Очистить регистрацию", callback_data="clear_registration"
            )
        )
        builder.row(
            InlineKeyboardButton(text="Новый фон сайта", callback_data="new_background")
        )
        await message.answer("Панель управления", reply_markup=builder.as_markup())
    else:
        await message.answer("Пожалуйста, отправьте свою Фамилию и имя.")


@dp.callback_query(F.data == "start_registration")
async def process_start_registration(callback_query: types.CallbackQuery):
    global registration_open
    registration_open = True
    await callback_query.answer("Регистрация начата.")


@dp.callback_query(F.data == "close_registration")
async def process_close_registration(callback_query: types.CallbackQuery):
    global registration_open
    registration_open = False
    await callback_query.answer("Регистрация закрыта.")


@dp.callback_query(F.data == "get_email")
async def process_callback_button(callback_query: types.CallbackQuery):
    await callback_query.message.answer(text=web_url)


@dp.callback_query(F.data == "clear_registration")
async def process_callback_button(callback_query: types.CallbackQuery):
    async with user_props_lock:
        config["user_props"] = []
        save_config(config)
        user_props.clear()
        await callback_query.answer("Регистрация очищена!")


@dp.callback_query(F.data == "new_background")
async def process_callback_button(callback_query: types.CallbackQuery):
    user_id = callback_query.from_user.id
    user_state[user_id] = "awaiting_background_image"
    request_message = await callback_query.message.answer(
        "Пожалуйста, отправьте изображение для нового фона сайта.",
        reply_markup=InlineKeyboardMarkup(
            inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data="back")]]
        ),
    )
    messages_to_delete[callback_query.message.chat.id] = [request_message.message_id]

    await callback_query.answer()


@dp.message(lambda message: message.photo)
async def handle_photo(message: types.Message):
    user_id = message.from_user.id
    if user_state.get(user_id) == "awaiting_background_image":
        photo = message.photo[-1]
        file_id = photo.file_id
        file_path = await bot.get_file(file_id)
        file_url = f"https://api.telegram.org/file/bot{TOKEN}/{file_path.file_path}"
        async with aiohttp.ClientSession() as session:
            async with session.get(file_url) as response:
                file_data = await response.read()
        async with aiohttp.ClientSession() as session:
            form_data = FormData()
            form_data.add_field("photo", file_data, filename="photo.png")
            async with session.post(
                server + "/api/upload_photo",
                data=form_data,
            ) as response:
                if response.status == 200:
                    print("Фото отправлено на сервер")
                    await message.answer("Фото отправлено на сервер!")
        chat_id = message.chat.id
        message_id = messages_to_delete[chat_id][0]
        await bot.delete_message(chat_id=chat_id, message_id=message_id)
        user_state[user_id] = None


@dp.message(lambda message: message.text and message.from_user.id not in admin_ids)
async def handle_user_info(message: types.Message):
    global registration_open

    if registration_open:
        async with user_props_lock:
            # Проверка, существует ли уже пользователь с таким именем
            if not any(user["name"] == message.text for user in user_props):
                user_info = {
                    "id": message.from_user.id,
                    "code": (
                        str(len(user_props) + 1)
                        + "".join(
                            random.choices(string.ascii_letters + string.digits, k=3)
                        )
                    ),
                    "name": message.text,
                    "timestamp": str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
                }
                user_props.append(user_info)
                update_user_props(user_props)
                await message.answer("Спасибо, ваши данные сохранены. Ниже ваш код")
                await message.answer(f"{user_info['code']}")
            else:
                await message.answer("Пользователь с таким именем уже зарегистрирован.")
    else:
        await message.answer("Регистрация в данный момент закрыта.")


@dp.callback_query(F.data == "send_draw")
async def process_callback_button(callback_query: types.CallbackQuery):
    user_id = callback_query.from_user.id
    user_state[user_id] = "awaiting_excel_file"
    request_message = await callback_query.message.answer(
        "Пожалуйста, отправьте Excel файл.",
        reply_markup=InlineKeyboardMarkup(
            inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data="back")]]
        ),
    )
    messages_to_delete[callback_query.message.chat.id] = [request_message.message_id]

    await callback_query.answer()


@dp.message(
    lambda message: message.document
    and message.document.mime_type
    == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
async def handle_excel(message: types.Message):
    user_id = message.from_user.id
    if user_state.get(user_id) == "awaiting_excel_file":
        document_id = message.document.file_id
        file = await bot.get_file(document_id)
        file_path = file.file_path
        file_data = await bot.download_file(file_path)
        df = pd.read_excel(file_data, header=None)
        data = df.values.tolist()

        first_row = [x for x in data[0] if isinstance(x, int)]
        response = requests.post(server + "/api/cornerSectors", json=first_row)

        data = data[1:]
        num_rows = len(data)
        num_cols = len(data[0])

        for j in range(3, num_cols):
            sector_dict = {}
            for i in range(num_rows):
                sector = data[i][j]
                player_number = data[i][2]
                if sector in sector_dict:
                    first_index = sector_dict[sector]
                    first_player_number = data[first_index][2]
                    first_sector = data[first_index][j]

                    data[first_index][j] = [player_number, sector]
                    data[i][j] = [first_player_number, first_sector]
                else:
                    sector_dict[sector] = i

        result_string = "\n".join([", ".join(map(str, row)) for row in data])

        response = requests.post(server + "/api/data", json=data)
        if response.status_code == 200:
            result_string = "Excel файл успешно отправлен на сервер."
        else:
            result_string = "Ошибка!!!"

        sent_message = await message.answer(
            text=result_string,
            reply_markup=InlineKeyboardMarkup(
                inline_keyboard=[
                    [InlineKeyboardButton(text="Назад", callback_data="back")]
                ]
            ),
        )

        if user_id in messages_to_delete:
            messages_to_delete[user_id].append(sent_message.message_id)
        else:
            messages_to_delete[user_id] = [sent_message.message_id]
        user_state[user_id] = None
    messages_to_delete[sent_message.message_id] = message.message_id


@dp.callback_query(lambda query: query.data == "back")
async def process_back_button(callback_query: types.CallbackQuery):
    user_id = callback_query.from_user.id
    user_state[user_id] = None
    message_ids_to_delete = messages_to_delete.get(callback_query.message.chat.id, [])

    for message_id in message_ids_to_delete:
        await bot.delete_message(callback_query.message.chat.id, message_id)

    await bot.delete_message(
        callback_query.message.chat.id, callback_query.message.message_id
    )

    messages_to_delete[callback_query.message.chat.id] = []

    await callback_query.answer()


@dp.callback_query(F.data == "get_id")
async def get_id(callback_query: types.CallbackQuery):
    user_id = callback_query.from_user.id
    await callback_query.message.answer(f"Ваш ID: {user_id}")
    await callback_query.answer()


@dp.callback_query(F.data == "get_unready_players")
async def get_unready_players(callback_query: types.CallbackQuery):
    response = requests.get(server + "/api/unready_players")
    if response.status_code == 200:
        unready_players = response.json()
        result_string = "\n".join([str(player) for player in unready_players])
        if len(unready_players) == 0 and result_string != "message":
            await callback_query.message.answer("Все игроки готовы")
        elif result_string != "message":
            await callback_query.message.answer(f"Неготовые игроки:\n{result_string}")
        else:
            await callback_query.message.answer(f"Проверьте, начался ли турнир")
    else:
        await callback_query.message.answer(
            f"Не удалось получить данные с сервера.\n Проверьте, идет ли турнир"
        )
    await callback_query.answer()


@dp.callback_query(F.data == "add_admin")
async def add_admin(callback_query: types.CallbackQuery):
    user_id = callback_query.from_user.id
    user_state[user_id] = "awaiting_admin_id"

    request_message = await callback_query.message.answer(
        "Пожалуйста, отправьте ID нового админа.",
        reply_markup=InlineKeyboardMarkup(
            inline_keyboard=[[InlineKeyboardButton(text="Назад", callback_data="back")]]
        ),
    )
    messages_to_delete[callback_query.message.chat.id] = [request_message.message_id]
    await callback_query.answer()


@dp.message(lambda message: message.text.isdigit())
async def handle_admin_id(message: types.Message):
    user_id = message.from_user.id
    if user_state.get(user_id) == "awaiting_admin_id":
        new_admin_id = int(message.text)
        if new_admin_id not in admin_ids:
            admin_ids.append(new_admin_id)
            update_admin_ids(admin_ids)
            await message.answer(f"Новый админ добавлен, ID: {new_admin_id}")
        else:
            await message.answer(f"Этот админ уже добавлен, ID: {new_admin_id}")
        user_state[user_id] = None


async def main():
    asyncio.create_task(check_registered_users())
    asyncio.create_task(update_stats())
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

Это мой бот
В бота я хочу в админ панель добавить пару кнопок - первая кнопка "обновить счет игрока", при нажатии на эту кнопку бот должен присылать пользователю сообщение "присылайте 3 числа через пробел, где первое число -  номер этапа, второе - номер игрока, третье - новый счет" с кнопкой назад и  должно быть новое состояние у пользователя - ожидание отправки сообщения об изменении счета. Если пользователь нажал кнопку назад после этого - сообщение просто удаляется и ничего не происходит, если же пользователь прислал сообщение и оно содержит 3 числа, то нужно отправлять эти 3 числа на сервер, на сервере нужно принять эти 3 числа, найти в users user с номером, соответствующим номеру игрока из бота, у него найти все круги circle в  user.circles[], у которых .index_circle равен отправленному из бота номеру этапа, таких кругов может быть не более 2, у одного .second_circle true, у другого круга false, мне нужно сначала запомнить в обоих кругах или в одном points (записать в 2 или 3 переменные circle.playerGame.points из одного из 2 кругов и в 2 остальные переменные записать 2 значения circle.opponentGame.points для 2 кругов), далее нужно для моего user в обоих или в одном circle обновить circle.playergame.fishcount на значение "новый счет" из бота, затем для одного или двух кругов перерасчитать .playerGame.points и opponentGame.points, рассчитываются очки по правилу: если circle.playerGame.fishcount === circle.playerGame.fishcount === 0, то у обоих .points = 1, если их fishcount равны и больше 0, то у обоих points = 2, если же один fishcount больше другого, то points = 4 у того, у кого fishcount больше, а у кого fishcount меньше есть всего одно условие: если fishcount меньше и не равен 0, то points = 1, иначе 0, после того как мы рассчитали новые points для одного или двух кругов нужно найти одного или двух user в users, у которых user.circles[i].opponentGame.number === номеру игрока из бота и обновить им opponentGame.fishcount на присланное значение из бота и аналогично перерасчитать points
В процессе нужно обновить .playerGame.total_points и .opponentGame.total_points путем вычитания предыдущих значений points и прибавления нового, но это при условии, что круг у нашего оппонента, в котором изменяемый нами игрок является оппонентом, не имеет .second_circle === true, в таком случае нам нужно .total_points поменять только у нашего user.circles]
И вот после всего этого нам нужно наши новые рассчитанные total_points записать 2-3 нашим игрокам - измененному