● Arxitektura Hujjati

Discord Ticket + Auto-Role Bot

Foydalanuvchi ticket qoldiradi → admin tasdiqlaydi → bot avtomatik rol beradi.

discord.schedule.uz · 2026-05-18

1. Umumiy g'oya

Discord serverda yangi a'zo kelganda u /ticket komandasi orqali ariza yuboradi (masalan ism, guruh, sabab). Bot maxsus moderator kanaliga embed yuboradi — Tasdiqlash / Rad etish tugmalari bilan. Admin tugmani bossa, bot foydalanuvchiga avtomatik rol beradi va DM bilan xabar qiladi. Hammasi audit log'ga yoziladi.

Asosiy funksiyalar

  • Slash komanda /ticket — modal forma
  • Tugmali tasdiqlash (Button interaction)
  • Avto-rol berish (guild.member.add_roles)
  • DM xabarnoma foydalanuvchiga
  • SQLite audit log + admin panel

Cheklovlar

  • Bir foydalanuvchi 1 marta arz beradi (cooldown)
  • Rad etilgan bo'lsa 24 soatdan keyin qayta urinish
  • Bot rol ierarxiyasi yuqori bo'lishi shart
  • Privileged Intent: Server Members yoqilgan

2. Texnologiya stack

QatlamTanlovSabab
TilPython 3.12discord.py async, kuchli ekosistem
Discord SDKdiscord.py 2.4+slash + buttons + modals native
DBSQLite (yoki Postgres)oddiy audit log uchun yetarli
Web adminFastAPI + HTMXtickets list, statistika
Process managersystemdVPSda barqaror ishlash
Reverse proxynginx + certbotHTTPS, statik fayllar

3. To'liq oqim (flow)

┌──────────────┐ /ticket ┌──────────────────┐ │ Foydalanuvchi│ ─────────────────▶ │ Discord Bot │ └──────────────┘ │ (discord.py) │ ▲ └─────────┬────────┘ │ DM "tasdiqlandi" │ │ + rol berildi │ Modal forma │ ▼ │ ┌──────────────────┐ │ │ Foydalanuvchi │ │ │ formani to'ldir │ │ └─────────┬────────┘ │ │ │ ▼ │ ┌──────────────────┐ │ │ #tickets kanalga │ │ │ embed + tugmalar │ │ └─────────┬────────┘ │ │ │ [Tasdiqlash]│[Rad etish] │ │ │ ▼ │ ┌──────────────────┐ │ │ Admin bosadi │ │ └─────────┬────────┘ │ │ │ ▼ │ ┌──────────────────┐ └────────────────────────────│ Bot rol beradi │ │ + audit log │ │ + DB yangilanadi │ └──────────────────┘

Bosqichlar tafsil

1
Foydalanuvchi /ticket chaqiradi. Bot Modal ochadi — 3 ta input: Ism-familiya, Guruh/sabab, Qo'shimcha izoh.
2
Submit → DBga pending holatida yoziladi, tickets kanalga embed yuboriladi. Embed ichida foydalanuvchi mention, ariza maydonlari, va 2 ta tugma: approve_<id>, reject_<id>.
3
Admin tugmani bosadi. Bot interaction handler ishga tushadi → admin huquqi tekshiriladi (faqat MODERATOR_ROLE_ID egasi).
4
Approve bo'lsamember.add_roles(verified_role). Embed yangilanadi: yashil rang, "✅ Tasdiqlandi by @admin". DB status = approved.
5
Foydalanuvchiga DM yuboriladi: "Arizangiz qabul qilindi. Sizga <Verified> roli berildi."
6
Reject bo'lsa — embed qizil, sabab so'raladi (yana modal), DM bilan sabab yuboriladi, DB status = rejected.

4. Discord tomonida sozlash

  1. discord.com/developers/applications — yangi Application yarat.
  2. Bot tabida → Privileged Gateway Intents:
    • ✅ Server Members Intent
    • ✅ Message Content Intent (agar prefix komanda kerak bo'lsa)
  3. OAuth2 → URL Generator: scopes bot, applications.commands. Permissions: Manage Roles, Send Messages, Embed Links, Read Message History.
  4. Server'da rollar yarating:
    • @Verified — tasdiqlangan a'zolar uchun
    • @Moderator — tugmalarni bosa oladigan adminlar
    Muhim: Bot roli @Verified rolidan yuqorida turishi shart, aks holda Missing Permissions xatosi keladi.
  5. Kanallar: #tickets (faqat moderatorlarga ko'rinadigan), #audit-log (ixtiyoriy).

5. Ma'lumotlar bazasi sxemasi

CREATE TABLE tickets (
  id            INTEGER PRIMARY KEY AUTOINCREMENT,
  discord_id    BIGINT  NOT NULL,
  username      TEXT    NOT NULL,
  full_name     TEXT    NOT NULL,
  group_info    TEXT,
  reason        TEXT,
  status        TEXT    NOT NULL DEFAULT 'pending',
                       -- pending | approved | rejected
  reviewed_by   BIGINT,
  reject_reason TEXT,
  created_at    TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  reviewed_at   TIMESTAMP,
  UNIQUE(discord_id, status) -- bir user 1 ta pending
);

CREATE INDEX idx_tickets_status ON tickets(status);
CREATE INDEX idx_tickets_user   ON tickets(discord_id);

6. Kod skeleti

Loyiha tuzilmasi

dsbot/
├── bot/
│   ├── __init__.py
│   ├── main.py              # entry point
│   ├── config.py            # env vars
│   ├── db.py                # SQLAlchemy / aiosqlite
│   ├── cogs/
│   │   ├── ticket.py        # /ticket komandasi
│   │   └── moderation.py    # button handlers
│   └── views/
│       ├── ticket_modal.py  # forma
│       └── review_view.py   # approve/reject tugmalar
├── web/
│   ├── main.py              # FastAPI admin
│   └── templates/
├── .env                     # DISCORD_TOKEN, GUILD_ID, ROLE_ID...
├── requirements.txt
└── systemd/
    └── dsbot.service

Modal (ticket forma)

import discord
from discord import ui

class TicketModal(ui.Modal, title="Ariza yuborish"):
    full_name  = ui.TextInput(label="Ism Familiya", max_length=100)
    group_info = ui.TextInput(label="Guruh / sabab", max_length=200)
    reason     = ui.TextInput(
        label="Qo'shimcha izoh",
        style=discord.TextStyle.long,
        required=False, max_length=500
    )

    async def on_submit(self, interaction: discord.Interaction):
        ticket_id = await db.create_ticket(
            interaction.user.id,
            interaction.user.name,
            self.full_name.value,
            self.group_info.value,
            self.reason.value,
        )
        embed = build_review_embed(ticket_id, interaction.user, ...)
        review_channel = interaction.guild.get_channel(REVIEW_CHANNEL_ID)
        await review_channel.send(embed=embed, view=ReviewView(ticket_id))
        await interaction.response.send_message(
            "✅ Arizangiz qabul qilindi. Moderatorlar ko'rib chiqishadi.",
            ephemeral=True,
        )

Tugmali View

class ReviewView(ui.View):
    def __init__(self, ticket_id: int):
        super().__init__(timeout=None)  # persistent
        self.ticket_id = ticket_id

    @ui.button(label="Tasdiqlash", style=discord.ButtonStyle.success,
               custom_id="ticket:approve")
    async def approve(self, interaction, button):
        if MODERATOR_ROLE_ID not in [r.id for r in interaction.user.roles]:
            return await interaction.response.send_message(
                "❌ Sizda ruxsat yo'q.", ephemeral=True)

        ticket = await db.get_ticket(self.ticket_id)
        member = interaction.guild.get_member(ticket.discord_id)
        role   = interaction.guild.get_role(VERIFIED_ROLE_ID)
        await member.add_roles(role, reason=f"Approved by {interaction.user}")
        await db.mark_approved(self.ticket_id, interaction.user.id)

        try:
            await member.send(f"✅ Arizangiz tasdiqlandi! Sizga `{role.name}` roli berildi.")
        except discord.Forbidden:
            pass  # DM yopiq

        embed = interaction.message.embeds[0]
        embed.color = discord.Color.green()
        embed.add_field(name="Holat", value=f"✅ Tasdiqlandi by {interaction.user.mention}")
        await interaction.response.edit_message(embed=embed, view=None)

Slash komanda

@bot.tree.command(name="ticket", description="Verifikatsiya uchun ariza yuborish")
async def ticket_cmd(interaction: discord.Interaction):
    existing = await db.get_pending(interaction.user.id)
    if existing:
        return await interaction.response.send_message(
            "Sizda allaqachon ko'rib chiqilayotgan ariza bor.", ephemeral=True)
    await interaction.response.send_modal(TicketModal())

main.py

import discord
from discord.ext import commands

intents = discord.Intents.default()
intents.members = True

bot = commands.Bot(command_prefix="!", intents=intents)

@bot.event
async def on_ready():
    bot.add_view(ReviewView(0))  # persistent buttons
    await bot.tree.sync(guild=discord.Object(id=GUILD_ID))
    print(f"Logged in as {bot.user}")

bot.run(DISCORD_TOKEN)

7. Deploy (VPS + systemd + nginx)

systemd unit — /etc/systemd/system/dsbot.service

[Unit]
Description=Discord Ticket Bot
After=network.target

[Service]
Type=simple
User=dsbot
WorkingDirectory=/opt/dsbot
EnvironmentFile=/opt/dsbot/.env
ExecStart=/opt/dsbot/.venv/bin/python -m bot.main
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

nginx — discord.schedule.uz

server {
    listen 443 ssl http2;
    server_name discord.schedule.uz;

    ssl_certificate     /etc/letsencrypt/live/discord.schedule.uz/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/discord.schedule.uz/privkey.pem;

    # Admin panel (FastAPI)
    location /admin/ {
        proxy_pass http://127.0.0.1:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $remote_addr;
    }

    # Bu hujjat (static)
    location / {
        root /var/www/discord.schedule.uz;
        try_files $uri $uri/ /index.html;
    }
}

Kommandalar ketma-ketligi

sudo adduser --system --group dsbot
sudo mkdir -p /opt/dsbot && cd /opt/dsbot
sudo -u dsbot python3 -m venv .venv
sudo -u dsbot .venv/bin/pip install discord.py aiosqlite python-dotenv fastapi uvicorn
sudo systemctl daemon-reload
sudo systemctl enable --now dsbot
sudo certbot --nginx -d discord.schedule.uz

8. Xavfsizlik tekshiruvlari


9. Keyingi qadamlar

  1. Discord Developer Portal'da Application yarating, tokenni oling.
  2. Test serverda GUILD_ID, VERIFIED_ROLE_ID, MODERATOR_ROLE_ID, REVIEW_CHANNEL_ID ni .env ga yozing.
  3. Bu repoda bot/ skeleton'ini quring (yuqoridagi kodlar bilan).
  4. Local'da test qiling — /ticket → modal → embed → tugma → rol.
  5. VPSga deploy: systemd + nginx + certbot.
  6. Admin panel: tickets ro'yxati, filterlar, statistika.