modified: bot.py
This commit is contained in:
167
bot.py
167
bot.py
@@ -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
|
||||||
|
|
||||||
|
# Cache for 1 hour
|
||||||
|
from datetime import timedelta
|
||||||
|
if last_project_fetch and (datetime.utcnow() - last_project_fetch) < timedelta(hours=1):
|
||||||
|
return project_cache
|
||||||
|
|
||||||
async def fetch_project_names():
|
|
||||||
global _project_cache
|
|
||||||
if _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}")
|
||||||
|
|
||||||
async def fetch_project_display_name(project_key):
|
# Fallback to cached data or empty list
|
||||||
try:
|
return project_cache if project_cache else []
|
||||||
async with aiohttp.ClientSession() as session:
|
|
||||||
async with session.get(PROJECTS_URL) as resp:
|
async def project_autocomplete(
|
||||||
data = await resp.json()
|
interaction: discord.Interaction,
|
||||||
return data.get(project_key, {}).get("name", project_key)
|
current: str,
|
||||||
except Exception:
|
) -> List[app_commands.Choice[str]]:
|
||||||
return project_key
|
"""Autocomplete for project names"""
|
||||||
|
projects = await fetch_projects()
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|||||||
Reference in New Issue
Block a user