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);
});
Это жеребьевка с нуля на сервере express.js, вот мои библиотеки
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 = [];
Ниже привожу своего бота
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")
        )
        # Add the new button here
        builder.row(
            InlineKeyboardButton(
                text="Обновить счет игрока", callback_data="update_player_score"
            )
        )
        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 == "update_player_score")
async def update_player_score(callback_query: types.CallbackQuery):
    user_id = callback_query.from_user.id
    user_state[user_id] = "awaiting_score_update"
    request_message = await callback_query.message.answer(
        "Пожалуйста, присылайте 3 числа через пробел, где первое число - номер этапа, второе - номер игрока, третье - новый счет.",
        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: user_state.get(message.from_user.id) == "awaiting_score_update"
)
async def handle_score_update(message: types.Message):
    user_id = message.from_user.id
    if user_state.get(user_id) == "awaiting_score_update":
        numbers = message.text.strip().split()
        if len(numbers) == 3:
            try:
                stage_number = int(numbers[0])
                player_number = int(numbers[1])
                new_score = int(numbers[2])

                # Send these numbers to the server
                data = {
                    "stage_number": stage_number,
                    "player_number": player_number,
                    "new_score": new_score,
                }
                async with aiohttp.ClientSession() as session:
                    async with session.post(
                        server + "/api/update_player_score", json=data
                    ) as response:
                        if response.status == 200:
                            await message.answer("Счет игрока обновлен.")
                        else:
                            await message.answer("Ошибка при обновлении счета игрока.")

            except ValueError:
                await message.answer(
                    "Ошибка: пожалуйста, отправьте 3 ЧИСЛА через пробел."
                )
        else:
            await message.answer("Ошибка: пожалуйста, отправьте 3 числа через пробел.")
        # Reset user state
        user_state[user_id] = None


@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(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


@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()


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())
Теперь смотри, я хочу в бота в админ панель добавить новую кнопку с новым состоянием - кнопка "обновить угловые сектора", при нажатии на нее должно приходить сообщение "присылайте номера всех угловых секторов через пробел" с кнопкой назад и должен делаться статус для админа на ожидание присылания угловых секторов, если после этого админ присылает угловые сектора через пробел, надо проверять, что из сообщения можно сделать массив чисел и отправлять его на сервер. Если нельзя сделать массив чисел, нужно просто сбросить статус админа и ничего не присылать, если кнопка назад нажимается, то у меня уже есть хендлер back.
И вот, если данные успешно отправлены на сервер, нужно их грамотно обработать. Сначала нужно перезаписать массив cornerSectors с новыми данными
Я тебе прислал формирование массива users, ты понимаешь как у меня создается он, теперь же как мы перезаписали cornerSectors нам надо переделать все круги особым образом, а именно нужно переприсоединить круги у которых second_circle === true и которые имеют статус inactive, то есть этапы с кругами со статусами active и completed трогать не надо, номер этапа круга circle - circle.index_circle