modified: bot.py

This commit is contained in:
SimolZimol
2026-01-09 21:27:54 +01:00
parent 6fe722f057
commit cd64b5f1ee

169
bot.py
View File

@@ -7,6 +7,7 @@ from datetime import datetime
import json import json
import mysql.connector import mysql.connector
import aiohttp import aiohttp
from typing import List
# Load environment variables # Load environment variables
load_dotenv() load_dotenv()
@@ -52,15 +53,6 @@ def init_db():
archived BOOLEAN DEFAULT FALSE archived BOOLEAN DEFAULT FALSE
) )
''') ''')
# Add new columns if missing
try:
cursor.execute("ALTER TABLE tickets ADD COLUMN referenced_message_content TEXT")
except mysql.connector.errors.ProgrammingError:
pass
try:
cursor.execute("ALTER TABLE tickets ADD COLUMN referenced_message_author VARCHAR(255)")
except mysql.connector.errors.ProgrammingError:
pass
cursor.execute(''' cursor.execute('''
CREATE TABLE IF NOT EXISTS server_settings ( CREATE TABLE IF NOT EXISTS server_settings (
guild_id BIGINT PRIMARY KEY, guild_id BIGINT PRIMARY KEY,
@@ -94,43 +86,14 @@ def load_server_settings(guild_id):
conn.close() conn.close()
return result return result
def save_ticket(ticket_id, ticket):
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute('''
REPLACE INTO tickets (ticket_id, message_id, channel_id, title, project, status, creator, created_at, reference_message_id, archived, referenced_message_content, referenced_message_author)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
''', (
ticket_id,
ticket['message_id'],
ticket['channel_id'],
ticket['title'],
ticket['project'],
ticket['status'],
ticket['creator'],
ticket['created_at'],
ticket.get('reference_message_id'),
ticket.get('archived', False),
ticket.get('referenced_message_content'),
ticket.get('referenced_message_author')
))
conn.commit()
cursor.close()
conn.close()
def load_ticket(ticket_id):
conn = get_db_connection()
cursor = conn.cursor(dictionary=True)
cursor.execute('SELECT * FROM tickets WHERE ticket_id = %s', (ticket_id,))
result = cursor.fetchone()
cursor.close()
conn.close()
return result
# Ticket storage (in production, use a database) # Ticket storage (in production, use a database)
tickets = {} tickets = {}
ticket_counter = 1 ticket_counter = 1
# Project cache for autocomplete
project_cache = []
last_project_fetch = None
# Ticket statuses # Ticket statuses
class TicketStatus: class TicketStatus:
PENDING = "⏳ Pending" PENDING = "⏳ Pending"
@@ -138,43 +101,49 @@ class TicketStatus:
COMPLETED = "✅ Completed" COMPLETED = "✅ Completed"
CANCELLED = "❌ Cancelled" CANCELLED = "❌ Cancelled"
# Dynamically fetch project names from devanturas.net/versions async def fetch_projects() -> List[str]:
PROJECTS_URL = "https://devanturas.net/versions" """Fetch available projects from devanturas.net/versions"""
_project_cache = [] global project_cache, last_project_fetch
async def fetch_project_names(): # Cache for 1 hour
global _project_cache from datetime import timedelta
if _project_cache: if last_project_fetch and (datetime.utcnow() - last_project_fetch) < timedelta(hours=1):
return _project_cache return project_cache
try: try:
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
async with session.get(PROJECTS_URL) as resp: async with session.get('https://devanturas.net/versions') as response:
data = await resp.json() if response.status == 200:
keys = list(data.keys()) data = await response.json()
_project_cache = keys project_cache = list(data.keys())
return keys last_project_fetch = datetime.utcnow()
except Exception: return project_cache
return [] except Exception as e:
print(f"Error fetching projects: {e}")
# Fallback to cached data or empty list
return project_cache if project_cache else []
async def fetch_project_display_name(project_key): async def project_autocomplete(
try: interaction: discord.Interaction,
async with aiohttp.ClientSession() as session: current: str,
async with session.get(PROJECTS_URL) as resp: ) -> List[app_commands.Choice[str]]:
data = await resp.json() """Autocomplete for project names"""
return data.get(project_key, {}).get("name", project_key) projects = await fetch_projects()
except Exception:
return project_key # Filter projects based on current input
filtered = [p for p in projects if current.lower() in p.lower()]
# Return up to 25 choices (Discord limit)
return [
app_commands.Choice(name=project, value=project)
for project in filtered[:25]
]
@bot.event @bot.event
async def on_ready(): async def on_ready():
print(f'{bot.user} has connected to Discord!') print(f'{bot.user} has connected to Discord!')
init_db() init_db()
# Preload project names
try:
import asyncio
asyncio.create_task(fetch_project_names())
except Exception:
pass
try: try:
synced = await bot.tree.sync() synced = await bot.tree.sync()
print(f"Synced {len(synced)} command(s)") print(f"Synced {len(synced)} command(s)")
@@ -205,25 +174,17 @@ async def setup(
@bot.tree.command(name="ticket", description="Create a new project update ticket") @bot.tree.command(name="ticket", description="Create a new project update ticket")
@app_commands.describe( @app_commands.describe(
project_name="Select the project key for this ticket (e.g. friends, joinme)", project_name="The project name from devanturas.net/projects",
title="Brief title for this update", title="Brief title for this update",
message_id="Optional: Discord message ID to reference" message_id="(Optional) Discord message ID to reference"
) )
@app_commands.autocomplete(project_name=project_autocomplete)
async def create_ticket( async def create_ticket(
interaction: discord.Interaction, interaction: discord.Interaction,
project_name: str, project_name: str,
title: str, title: str,
message_id: str = None message_id: str = None
): ):
global ticket_counter
valid_projects = await fetch_project_names()
if project_name not in valid_projects:
await interaction.response.send_message(
f"❌ Invalid project key. Please use one of: {', '.join(valid_projects)}",
ephemeral=True
)
return
display_name = await fetch_project_display_name(project_name)
guild_id = interaction.guild.id if interaction.guild else None guild_id = interaction.guild.id if interaction.guild else None
settings = load_server_settings(guild_id) settings = load_server_settings(guild_id)
if not settings or not settings.get("active_channel_id"): if not settings or not settings.get("active_channel_id"):
@@ -233,40 +194,44 @@ async def create_ticket(
) )
return return
active_channel = bot.get_channel(settings["active_channel_id"]) active_channel = bot.get_channel(settings["active_channel_id"])
if not active_channel:
await interaction.response.send_message(
"❌ Active channel not found! Please reconfigure with `/setup`.",
ephemeral=True
)
return
# Create ticket ID
ticket_id = f"TICKET-{ticket_counter:04d}" ticket_id = f"TICKET-{ticket_counter:04d}"
ticket_counter += 1 ticket_counter += 1
referenced_message_content = None
referenced_message_author = None # Create embed for the ticket
if message_id:
try:
ref_message = await active_channel.fetch_message(int(message_id))
referenced_message_content = ref_message.content
referenced_message_author = ref_message.author.mention
except Exception:
referenced_message_content = None
referenced_message_author = None
embed = discord.Embed( embed = discord.Embed(
title=f"🎫 {ticket_id}: {title}", title=f"🎫 {ticket_id}: {title}",
description=f"**Project Update Request**", description=f"**Project Update Request**",
color=discord.Color.blue(), color=discord.Color.blue(),
timestamp=datetime.utcnow() timestamp=datetime.utcnow()
) )
embed.add_field(name="📋 Project", value=f"{display_name} ({project_name})", inline=True)
embed.add_field(name="📋 Project", value=project_name, inline=True)
embed.add_field(name="📊 Status", value=TicketStatus.PENDING, inline=True) embed.add_field(name="📊 Status", value=TicketStatus.PENDING, inline=True)
embed.add_field(name="👤 Created By", value=interaction.user.mention, inline=True) embed.add_field(name="👤 Created By", value=interaction.user.mention, inline=True)
if message_id: if message_id:
embed.add_field(name="🔗 Message ID", value=f"`{message_id}`", inline=False) embed.add_field(name="🔗 Reference Message ID", value=f"`{message_id}`", inline=False)
if referenced_message_content:
embed.add_field(name="💬 Request Message", value=referenced_message_content, inline=False)
if referenced_message_author:
embed.add_field(name="🙋 Request By", value=referenced_message_author, inline=True)
embed.add_field( embed.add_field(
name="🌐 Project Links", name="🌐 Project Links",
value=f"[All Projects](https://devanturas.net/projects) | [Project Page](https://devanturas.net/projects/{project_name})", value=f"[All Projects](https://devanturas.net/projects) | [Project Details](https://devanturas.net/projects/{project_name})",
inline=False inline=False
) )
embed.set_footer(text=f"Ticket ID: {ticket_id}") embed.set_footer(text=f"Ticket ID: {ticket_id}")
# Send to active channel
ticket_message = await active_channel.send(embed=embed) ticket_message = await active_channel.send(embed=embed)
# Store ticket data
ticket = { ticket = {
"message_id": ticket_message.id, "message_id": ticket_message.id,
"channel_id": active_channel.id, "channel_id": active_channel.id,
@@ -275,11 +240,11 @@ async def create_ticket(
"status": TicketStatus.PENDING, "status": TicketStatus.PENDING,
"creator": interaction.user.id, "creator": interaction.user.id,
"created_at": datetime.utcnow().isoformat(), "created_at": datetime.utcnow().isoformat(),
"reference_message_id": message_id, "reference_message_id": message_id
"referenced_message_content": referenced_message_content,
"referenced_message_author": referenced_message_author
} }
save_ticket(ticket_id, ticket) save_ticket(ticket_id, ticket)
# Confirm to user
await interaction.response.send_message( await interaction.response.send_message(
f"✅ Ticket created successfully!\n" f"✅ Ticket created successfully!\n"
f"**Ticket ID:** `{ticket_id}`\n" f"**Ticket ID:** `{ticket_id}`\n"