refactor: rearrange everything

This commit is contained in:
Neil Revin 2025-12-11 21:26:24 +05:30
parent 00d90dea36
commit 9bbf6aa801
No known key found for this signature in database
GPG key ID: 43A4FF4AFF21BEC9
12 changed files with 340 additions and 2 deletions

0
src/commands/__init__.py Normal file
View file

43
src/commands/manage.py Normal file
View file

@ -0,0 +1,43 @@
import discord
from discord.ext import commands
from discord import app_commands
from database import get_ticket
class ManageCommands(commands.Cog):
def __init__(self, bot):
self.bot = bot
@app_commands.command(name="add", description="Add a user to the ticket")
async def add(self, interaction: discord.Interaction, user: discord.Member):
ticket = await get_ticket(interaction.channel_id)
if not ticket:
await interaction.response.send_message("❌ This is not a ticket channel.", ephemeral=True)
return
if not interaction.user.guild_permissions.manage_channels:
await interaction.response.send_message("❌ You don't have permission to add users.", ephemeral=True)
return
await interaction.channel.set_permissions(user, read_messages=True, send_messages=True)
await interaction.response.send_message(f"{user.mention} has been added to the ticket.")
@app_commands.command(name="remove", description="Remove a user from the ticket")
async def remove(self, interaction: discord.Interaction, user: discord.Member):
ticket = await get_ticket(interaction.channel_id)
if not ticket:
await interaction.response.send_message("❌ This is not a ticket channel.", ephemeral=True)
return
if not interaction.user.guild_permissions.manage_channels:
await interaction.response.send_message("❌ You don't have permission to remove users.", ephemeral=True)
return
if user.id == ticket.user_id:
await interaction.response.send_message("❌ You cannot remove the ticket owner.", ephemeral=True)
return
await interaction.channel.set_permissions(user, overwrite=None)
await interaction.response.send_message(f"{user.mention} has been removed from the ticket.")
async def setup(bot):
await bot.add_cog(ManageCommands(bot))

42
src/commands/panel.py Normal file
View file

@ -0,0 +1,42 @@
import discord
from discord.ext import commands
from discord import app_commands
from database import set_config
class PanelView(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
@discord.ui.button(label="Create Ticket", style=discord.ButtonStyle.green, custom_id="create_ticket", emoji="🎫")
async def create_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
from ticket_handler import handle_ticket_create
await handle_ticket_create(interaction)
class PanelCommands(commands.Cog):
def __init__(self, bot):
self.bot = bot
bot.add_view(PanelView())
@app_commands.command(name="panel", description="Send the ticket creation panel")
@app_commands.default_permissions(administrator=True)
async def panel(self, interaction: discord.Interaction):
embed = discord.Embed(
title="🎫 Support Tickets",
description="Click the button below to create a support ticket.\nOur team will assist you shortly.",
color=discord.Color.blue()
)
embed.set_footer(text="Ticket System")
view = PanelView()
msg = await interaction.channel.send(embed=embed, view=view)
await set_config(
interaction.guild_id,
panel_channel_id=interaction.channel_id,
panel_message_id=msg.id
)
await interaction.response.send_message("✅ Panel sent!", ephemeral=True)
async def setup(bot):
await bot.add_cog(PanelCommands(bot))

11
src/config.py Normal file
View file

@ -0,0 +1,11 @@
import os
from dotenv import load_dotenv
load_dotenv()
TOKEN = os.getenv("BOT_TOKEN")
DB_TYPE = os.getenv("DB_TYPE", "sqlite")
DB_URL = os.getenv("DB_URL", "sqlite+aiosqlite:///tickets.db")
TICKET_CATEGORY_NAME = "Tickets"
TICKET_LOG_CHANNEL = "ticket-logs"

108
src/database.py Normal file
View file

@ -0,0 +1,108 @@
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import BigInteger, String, select
from typing import Optional
from config import DB_TYPE, DB_URL
engine = None
SessionLocal = None
class Base(DeclarativeBase):
pass
class Ticket(Base):
__tablename__ = "tickets"
id: Mapped[int] = mapped_column(primary_key=True)
guild_id: Mapped[int] = mapped_column(BigInteger)
channel_id: Mapped[int] = mapped_column(BigInteger, unique=True)
user_id: Mapped[int] = mapped_column(BigInteger)
ticket_number: Mapped[int]
class Config(Base):
__tablename__ = "config"
id: Mapped[int] = mapped_column(primary_key=True)
guild_id: Mapped[int] = mapped_column(BigInteger, unique=True)
panel_channel_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
panel_message_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
category_id: Mapped[Optional[int]] = mapped_column(BigInteger, nullable=True)
async def init_db():
global engine, SessionLocal
engine = create_async_engine(DB_URL, echo=False)
SessionLocal = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
def get_session():
return SessionLocal()
async def create_ticket(guild_id: int, channel_id: int, user_id: int) -> Ticket:
async with get_session() as session:
result = await session.execute(
select(Ticket).where(Ticket.guild_id == guild_id).order_by(Ticket.ticket_number.desc())
)
last_ticket = result.scalars().first()
ticket_num = (last_ticket.ticket_number + 1) if last_ticket else 1
ticket = Ticket(
guild_id=guild_id,
channel_id=channel_id,
user_id=user_id,
ticket_number=ticket_num
)
session.add(ticket)
await session.commit()
await session.refresh(ticket)
return type('TicketData', (), {
'ticket_number': ticket.ticket_number,
'channel_id': ticket.channel_id,
'user_id': ticket.user_id,
'guild_id': ticket.guild_id
})()
async def get_ticket(channel_id: int) -> Optional[Ticket]:
async with get_session() as session:
result = await session.execute(
select(Ticket).where(Ticket.channel_id == channel_id)
)
return result.scalars().first()
async def delete_ticket(channel_id: int):
async with get_session() as session:
result = await session.execute(
select(Ticket).where(Ticket.channel_id == channel_id)
)
ticket = result.scalars().first()
if ticket:
await session.delete(ticket)
await session.commit()
async def get_config(guild_id: int) -> Optional[Config]:
async with get_session() as session:
result = await session.execute(
select(Config).where(Config.guild_id == guild_id)
)
return result.scalars().first()
async def set_config(guild_id: int, **kwargs) -> Config:
async with get_session() as session:
result = await session.execute(
select(Config).where(Config.guild_id == guild_id)
)
config = result.scalars().first()
if not config:
config = Config(guild_id=guild_id)
session.add(config)
for key, value in kwargs.items():
setattr(config, key, value)
await session.commit()
await session.refresh(config)
return config

0
src/handlers/__init__.py Normal file
View file

View file

@ -0,0 +1,64 @@
import discord
from database import create_ticket, get_ticket, get_config, get_session, Ticket
from sqlalchemy import select
async def handle_ticket_create(interaction: discord.Interaction):
for channel in interaction.guild.channels:
if isinstance(channel, discord.TextChannel):
ticket = await get_ticket(channel.id)
if ticket and ticket.user_id == interaction.user.id:
await interaction.response.send_message(
f"❌ You already have an open ticket: {channel.mention}",
ephemeral=True
)
return
config = await get_config(interaction.guild_id)
category = None
if config and config.category_id:
category = interaction.guild.get_channel(config.category_id)
if not category:
category = discord.utils.get(interaction.guild.categories, name="Tickets")
if not category:
category = await interaction.guild.create_category("Tickets")
async with get_session() as session:
result = await session.execute(
select(Ticket).where(Ticket.guild_id == interaction.guild_id).order_by(Ticket.ticket_number.desc())
)
last_ticket = result.scalars().first()
ticket_num = (last_ticket.ticket_number + 1) if last_ticket else 1
overwrites = {
interaction.guild.default_role: discord.PermissionOverwrite(read_messages=False),
interaction.user: discord.PermissionOverwrite(read_messages=True, send_messages=True),
interaction.guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True)
}
channel = await interaction.guild.create_text_channel(
name=f"ticket-{ticket_num}",
category=category,
overwrites=overwrites
)
ticket = await create_ticket(
interaction.guild_id,
channel.id,
interaction.user.id
)
embed = discord.Embed(
title=f"Ticket #{ticket.ticket_number}",
description=f"Welcome {interaction.user.mention}!\nPlease describe your issue and a staff member will be with you shortly.",
color=discord.Color.green()
)
from views.ticket_view import TicketView
await channel.send(embed=embed, view=TicketView())
await interaction.response.send_message(
f"✅ Ticket created: {channel.mention}",
ephemeral=True
)

28
src/main.py Normal file
View file

@ -0,0 +1,28 @@
import discord
from discord.ext import commands
import os
from pathlib import Path
from database import init_db
from config import TOKEN, DB_TYPE, DB_URL
intents = discord.Intents.default()
intents.message_content = True
intents.guilds = True
intents.members = True
bot = commands.Bot(command_prefix="!", intents=intents)
@bot.event
async def on_ready():
await init_db()
commands_path = Path("commands")
for file in commands_path.glob("*.py"):
if file.stem != "__init__":
await bot.load_extension(f"commands.{file.stem}")
print(f"Bot ready as {bot.user}")
if __name__ == "__main__":
bot.run(TOKEN)

0
src/views/__init__.py Normal file
View file

42
src/views/ticket_view.py Normal file
View file

@ -0,0 +1,42 @@
import discord
from database import get_ticket, delete_ticket
import io
class TicketView(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
@discord.ui.button(label="Close", style=discord.ButtonStyle.red, custom_id="close_ticket", emoji="🔒")
async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button):
ticket = await get_ticket(interaction.channel_id)
if not ticket:
await interaction.response.send_message("❌ This is not a ticket channel.", ephemeral=True)
return
if not interaction.user.guild_permissions.manage_channels and interaction.user.id != ticket.user_id:
await interaction.response.send_message("❌ You don't have permission to close this ticket.", ephemeral=True)
return
await interaction.response.send_message("🔒 Closing ticket and saving transcript...")
messages = []
async for msg in interaction.channel.history(limit=None, oldest_first=True):
timestamp = msg.created_at.strftime("%Y-%m-%d %H:%M:%S")
messages.append(f"[{timestamp}] {msg.author}: {msg.content}")
transcript = "\n".join(messages)
file = discord.File(io.BytesIO(transcript.encode()), filename=f"ticket-{ticket.ticket_number}.txt")
user = interaction.guild.get_member(ticket.user_id)
if user:
try:
await user.send(f"Your ticket #{ticket.ticket_number} has been closed. Here's the transcript:", file=file)
except:
pass
await delete_ticket(interaction.channel_id)
await interaction.channel.delete()
async def setup(bot):
bot.add_view(TicketView())