Foydalanuvchi ticket qoldiradi → admin tasdiqlaydi → bot avtomatik rol beradi.
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.
/ticket — modal formaguild.member.add_roles)Server Members yoqilgan| Qatlam | Tanlov | Sabab |
|---|---|---|
| Til | Python 3.12 | discord.py async, kuchli ekosistem |
| Discord SDK | discord.py 2.4+ | slash + buttons + modals native |
| DB | SQLite (yoki Postgres) | oddiy audit log uchun yetarli |
| Web admin | FastAPI + HTMX | tickets list, statistika |
| Process manager | systemd | VPSda barqaror ishlash |
| Reverse proxy | nginx + certbot | HTTPS, statik fayllar |
/ticket chaqiradi. Bot Modal ochadi — 3 ta input: Ism-familiya, Guruh/sabab, Qo'shimcha izoh.pending holatida yoziladi, tickets kanalga embed yuboriladi. Embed ichida foydalanuvchi mention, ariza maydonlari, va 2 ta tugma: approve_<id>, reject_<id>.MODERATOR_ROLE_ID egasi).member.add_roles(verified_role). Embed yangilanadi: yashil rang, "✅ Tasdiqlandi by @admin". DB status = approved.status = rejected.bot, applications.commands. Permissions: Manage Roles, Send Messages, Embed Links, Read Message History.@Verified — tasdiqlangan a'zolar uchun@Moderator — tugmalarni bosa oladigan adminlar@Verified rolidan yuqorida turishi shart, aks holda Missing Permissions xatosi keladi.
#tickets (faqat moderatorlarga ko'rinadigan), #audit-log (ixtiyoriy).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);
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
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,
)
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)
@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())
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)
/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
discord.schedule.uzserver {
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;
}
}
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
.env da, git'ga commit qilmang. chmod 600 .env.MODERATOR_ROLE_ID tekshiruvi shart.pending ticket'dan ko'p qila olmaydi (UNIQUE constraint).aiosqlite placeholders).403.timeout=None + custom_id bilan, bot restartdan keyin ham tugmalar ishlaydi.GUILD_ID, VERIFIED_ROLE_ID, MODERATOR_ROLE_ID, REVIEW_CHANNEL_ID ni .env ga yozing.bot/ skeleton'ini quring (yuqoridagi kodlar bilan)./ticket → modal → embed → tugma → rol.