new file: .env.example
new file: .gitignore new file: Dockerfile new file: README.md new file: bot.py new file: requirements.txt
This commit is contained in:
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Discord Bot Token
|
||||||
|
# Get your token from https://discord.com/developers/applications
|
||||||
|
DISCORD_TOKEN=your_bot_token_here
|
||||||
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Environment variables
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
*.so
|
||||||
|
.Python
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
ENV/
|
||||||
|
|
||||||
|
# Bot data
|
||||||
|
config.json
|
||||||
|
tickets.json
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Base image with Python
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
libffi-dev \
|
||||||
|
libnacl-dev \
|
||||||
|
python3-dev \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy requirements and install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy project files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
ENV DISCORD_TOKEN=$DISCORD_TOKEN
|
||||||
|
|
||||||
|
# Start the bot
|
||||||
|
CMD ["python", "bot.py"]
|
||||||
198
README.md
Normal file
198
README.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# Discord Ticket Management Bot
|
||||||
|
|
||||||
|
A Discord bot for creating and managing project update tickets with integration to devanturas.net projects.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- ✅ Create tickets with slash commands
|
||||||
|
- 📋 Track project updates from devanturas.net
|
||||||
|
- 🔄 Update ticket status (Pending, In Progress, Completed, Cancelled)
|
||||||
|
- 📦 Automatic archiving of completed tickets
|
||||||
|
- 🎫 List and view all active tickets
|
||||||
|
- 📊 Beautiful embed messages with ticket information
|
||||||
|
|
||||||
|
## Setup Instructions
|
||||||
|
|
||||||
|
### 1. Create a Discord Bot
|
||||||
|
|
||||||
|
1. Go to [Discord Developer Portal](https://discord.com/developers/applications)
|
||||||
|
2. Click "New Application" and give it a name
|
||||||
|
3. Go to the "Bot" section
|
||||||
|
4. Click "Add Bot"
|
||||||
|
5. Under "Privileged Gateway Intents", enable:
|
||||||
|
- MESSAGE CONTENT INTENT
|
||||||
|
- SERVER MEMBERS INTENT
|
||||||
|
6. Click "Reset Token" and copy your bot token
|
||||||
|
|
||||||
|
### 2. Install Dependencies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Configure the Bot
|
||||||
|
|
||||||
|
1. Copy `.env.example` to `.env`:
|
||||||
|
```bash
|
||||||
|
copy .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Edit `.env` and add your bot token:
|
||||||
|
```
|
||||||
|
DISCORD_TOKEN=your_bot_token_here
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Invite the Bot to Your Server
|
||||||
|
|
||||||
|
1. In the Discord Developer Portal, go to "OAuth2" > "URL Generator"
|
||||||
|
2. Select scopes:
|
||||||
|
- `bot`
|
||||||
|
- `applications.commands`
|
||||||
|
3. Select bot permissions:
|
||||||
|
- Send Messages
|
||||||
|
- Embed Links
|
||||||
|
- Read Message History
|
||||||
|
- Manage Messages
|
||||||
|
4. Copy the generated URL and open it in your browser
|
||||||
|
5. Select your server and authorize
|
||||||
|
|
||||||
|
### 5. Run the Bot
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python bot.py
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Configure Channels
|
||||||
|
|
||||||
|
In Discord, use the `/setup` command to configure the channels:
|
||||||
|
|
||||||
|
```
|
||||||
|
/setup active_channel:#active-tickets archive_channel:#archived-tickets
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### `/ticket` - Create a New Ticket
|
||||||
|
|
||||||
|
Create a new project update ticket.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `message_id`: Discord message ID to reference
|
||||||
|
- `project_name`: Project name from devanturas.net/versions
|
||||||
|
- `title`: Brief description of the update
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
/ticket message_id:123456789 project_name:MyProject title:Version 2.0 Update
|
||||||
|
```
|
||||||
|
|
||||||
|
### `/status` - Update Ticket Status
|
||||||
|
|
||||||
|
Update the status of an existing ticket.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `ticket_id`: The ticket ID (e.g., TICKET-0001)
|
||||||
|
- `new_status`: Choose from:
|
||||||
|
- ⏳ Pending
|
||||||
|
- 🔄 In Progress
|
||||||
|
- ✅ Completed
|
||||||
|
- ❌ Cancelled
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
/status ticket_id:TICKET-0001 new_status:In Progress
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** When a ticket is marked as "Completed", it will automatically be moved to the archive channel.
|
||||||
|
|
||||||
|
### `/list` - View Active Tickets
|
||||||
|
|
||||||
|
Lists all currently active (non-archived) tickets.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
/list
|
||||||
|
```
|
||||||
|
|
||||||
|
### `/info` - Get Ticket Details
|
||||||
|
|
||||||
|
Get detailed information about a specific ticket.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `ticket_id`: The ticket ID to view
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
/info ticket_id:TICKET-0001
|
||||||
|
```
|
||||||
|
|
||||||
|
### `/setup` - Configure Bot Channels
|
||||||
|
|
||||||
|
Configure the active and archive channels for tickets (Admin only).
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `active_channel`: Channel for active tickets
|
||||||
|
- `archive_channel`: Channel for completed tickets
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
/setup active_channel:#active-tickets archive_channel:#archived-tickets
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ticket Workflow
|
||||||
|
|
||||||
|
1. **Create**: Use `/ticket` to create a new ticket in the active channel
|
||||||
|
2. **Update**: Use `/status` to update the ticket status as you work
|
||||||
|
3. **Complete**: Mark ticket as "Completed" to automatically archive it
|
||||||
|
4. **Archive**: Completed tickets are moved to the archive channel
|
||||||
|
|
||||||
|
## Project Integration
|
||||||
|
|
||||||
|
The bot integrates with devanturas.net:
|
||||||
|
- All projects: https://devanturas.net/versions
|
||||||
|
- Specific project: https://devanturas.net/projects/{project_name}
|
||||||
|
|
||||||
|
Each ticket embed includes direct links to the project pages.
|
||||||
|
|
||||||
|
## Data Storage
|
||||||
|
|
||||||
|
Currently, tickets are stored in memory. For production use, consider implementing:
|
||||||
|
- Database storage (SQLite, PostgreSQL, etc.)
|
||||||
|
- Persistent ticket data between restarts
|
||||||
|
- Backup and recovery mechanisms
|
||||||
|
|
||||||
|
## Docker Support
|
||||||
|
|
||||||
|
Build and run with Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Build the image
|
||||||
|
docker build -t discord-ticket-bot .
|
||||||
|
|
||||||
|
# Run the container
|
||||||
|
docker run -d --name ticket-bot --env-file .env discord-ticket-bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Bot doesn't respond to commands
|
||||||
|
- Make sure the bot is online
|
||||||
|
- Check that you've enabled the correct intents in the Developer Portal
|
||||||
|
- Verify the bot has permissions in your server
|
||||||
|
|
||||||
|
### Commands don't appear
|
||||||
|
- Wait a few minutes for commands to sync
|
||||||
|
- Try kicking and re-inviting the bot
|
||||||
|
- Check the bot logs for sync errors
|
||||||
|
|
||||||
|
### Tickets aren't archiving
|
||||||
|
- Verify you've run `/setup` to configure channels
|
||||||
|
- Check that the bot has permissions in both channels
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - feel free to modify and use for your projects!
|
||||||
|
|
||||||
|
## Author
|
||||||
|
|
||||||
|
Created for SimolZimol
|
||||||
377
bot.py
Normal file
377
bot.py
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
import discord
|
||||||
|
from discord import app_commands
|
||||||
|
from discord.ext import commands
|
||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from datetime import datetime
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Load environment variables
|
||||||
|
load_dotenv()
|
||||||
|
TOKEN = os.getenv('DISCORD_TOKEN')
|
||||||
|
|
||||||
|
# Bot setup
|
||||||
|
intents = discord.Intents.default()
|
||||||
|
intents.message_content = True
|
||||||
|
intents.guilds = True
|
||||||
|
|
||||||
|
bot = commands.Bot(command_prefix='!', intents=intents)
|
||||||
|
|
||||||
|
# Ticket storage (in production, use a database)
|
||||||
|
tickets = {}
|
||||||
|
ticket_counter = 1
|
||||||
|
|
||||||
|
# Ticket statuses
|
||||||
|
class TicketStatus:
|
||||||
|
PENDING = "⏳ Pending"
|
||||||
|
IN_PROGRESS = "🔄 In Progress"
|
||||||
|
COMPLETED = "✅ Completed"
|
||||||
|
CANCELLED = "❌ Cancelled"
|
||||||
|
|
||||||
|
# Channel IDs (configure these in config.json)
|
||||||
|
config = {}
|
||||||
|
|
||||||
|
def load_config():
|
||||||
|
global config
|
||||||
|
try:
|
||||||
|
with open('config.json', 'r') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
config = {
|
||||||
|
"active_channel_id": None,
|
||||||
|
"archive_channel_id": None
|
||||||
|
}
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
def save_config():
|
||||||
|
with open('config.json', 'w') as f:
|
||||||
|
json.dump(config, f, indent=4)
|
||||||
|
|
||||||
|
@bot.event
|
||||||
|
async def on_ready():
|
||||||
|
print(f'{bot.user} has connected to Discord!')
|
||||||
|
load_config()
|
||||||
|
try:
|
||||||
|
synced = await bot.tree.sync()
|
||||||
|
print(f"Synced {len(synced)} command(s)")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error syncing commands: {e}")
|
||||||
|
|
||||||
|
@bot.tree.command(name="setup", description="Setup the bot channels")
|
||||||
|
@app_commands.describe(
|
||||||
|
active_channel="The channel for active tickets",
|
||||||
|
archive_channel="The channel for archived tickets"
|
||||||
|
)
|
||||||
|
async def setup(
|
||||||
|
interaction: discord.Interaction,
|
||||||
|
active_channel: discord.TextChannel,
|
||||||
|
archive_channel: discord.TextChannel
|
||||||
|
):
|
||||||
|
"""Setup the channels for ticket management"""
|
||||||
|
config["active_channel_id"] = active_channel.id
|
||||||
|
config["archive_channel_id"] = archive_channel.id
|
||||||
|
save_config()
|
||||||
|
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"✅ Configuration saved!\n"
|
||||||
|
f"Active tickets: {active_channel.mention}\n"
|
||||||
|
f"Archive: {archive_channel.mention}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@bot.tree.command(name="ticket", description="Create a new project update ticket")
|
||||||
|
@app_commands.describe(
|
||||||
|
message_id="The Discord message ID to reference",
|
||||||
|
project_name="The project name from devanturas.net/versions",
|
||||||
|
title="Brief title for this update"
|
||||||
|
)
|
||||||
|
async def create_ticket(
|
||||||
|
interaction: discord.Interaction,
|
||||||
|
message_id: str,
|
||||||
|
project_name: str,
|
||||||
|
title: str
|
||||||
|
):
|
||||||
|
"""Create a new ticket for project updates"""
|
||||||
|
global ticket_counter
|
||||||
|
|
||||||
|
# Check if channels are configured
|
||||||
|
if not config.get("active_channel_id"):
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"❌ Bot not configured! Please use `/setup` first.",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get the active channel
|
||||||
|
active_channel = bot.get_channel(config["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_counter += 1
|
||||||
|
|
||||||
|
# Create embed for the ticket
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"🎫 {ticket_id}: {title}",
|
||||||
|
description=f"**Project Update Request**",
|
||||||
|
color=discord.Color.blue(),
|
||||||
|
timestamp=datetime.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(name="📋 Project", value=project_name, 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="🔗 Message ID", value=f"`{message_id}`", inline=False)
|
||||||
|
embed.add_field(
|
||||||
|
name="🌐 Project Links",
|
||||||
|
value=f"[All Projects](https://devanturas.net/versions) | [Project Page](https://devanturas.net/projects/{project_name})",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.set_footer(text=f"Ticket ID: {ticket_id}")
|
||||||
|
|
||||||
|
# Send to active channel
|
||||||
|
ticket_message = await active_channel.send(embed=embed)
|
||||||
|
|
||||||
|
# Store ticket data
|
||||||
|
tickets[ticket_id] = {
|
||||||
|
"message_id": ticket_message.id,
|
||||||
|
"channel_id": active_channel.id,
|
||||||
|
"title": title,
|
||||||
|
"project": project_name,
|
||||||
|
"status": TicketStatus.PENDING,
|
||||||
|
"creator": interaction.user.id,
|
||||||
|
"created_at": datetime.utcnow().isoformat(),
|
||||||
|
"reference_message_id": message_id
|
||||||
|
}
|
||||||
|
|
||||||
|
# Confirm to user
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"✅ Ticket created successfully!\n"
|
||||||
|
f"**Ticket ID:** `{ticket_id}`\n"
|
||||||
|
f"**Channel:** {active_channel.mention}\n"
|
||||||
|
f"Use `/status {ticket_id}` to update the status.",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@bot.tree.command(name="status", description="Update ticket status")
|
||||||
|
@app_commands.describe(
|
||||||
|
ticket_id="The ticket ID (e.g., TICKET-0001)",
|
||||||
|
new_status="The new status for the ticket"
|
||||||
|
)
|
||||||
|
@app_commands.choices(new_status=[
|
||||||
|
app_commands.Choice(name="⏳ Pending", value="pending"),
|
||||||
|
app_commands.Choice(name="🔄 In Progress", value="in_progress"),
|
||||||
|
app_commands.Choice(name="✅ Completed", value="completed"),
|
||||||
|
app_commands.Choice(name="❌ Cancelled", value="cancelled"),
|
||||||
|
])
|
||||||
|
async def update_status(
|
||||||
|
interaction: discord.Interaction,
|
||||||
|
ticket_id: str,
|
||||||
|
new_status: str
|
||||||
|
):
|
||||||
|
"""Update the status of a ticket"""
|
||||||
|
|
||||||
|
# Convert to uppercase for consistency
|
||||||
|
ticket_id = ticket_id.upper()
|
||||||
|
|
||||||
|
# Check if ticket exists
|
||||||
|
if ticket_id not in tickets:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"❌ Ticket `{ticket_id}` not found!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
ticket = tickets[ticket_id]
|
||||||
|
|
||||||
|
# Map status choice to display text
|
||||||
|
status_map = {
|
||||||
|
"pending": TicketStatus.PENDING,
|
||||||
|
"in_progress": TicketStatus.IN_PROGRESS,
|
||||||
|
"completed": TicketStatus.COMPLETED,
|
||||||
|
"cancelled": TicketStatus.CANCELLED
|
||||||
|
}
|
||||||
|
|
||||||
|
old_status = ticket["status"]
|
||||||
|
new_status_text = status_map[new_status]
|
||||||
|
ticket["status"] = new_status_text
|
||||||
|
|
||||||
|
# Get the ticket message
|
||||||
|
channel = bot.get_channel(ticket["channel_id"])
|
||||||
|
if not channel:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"❌ Channel not found!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = await channel.fetch_message(ticket["message_id"])
|
||||||
|
embed = message.embeds[0]
|
||||||
|
|
||||||
|
# Update the status field
|
||||||
|
for i, field in enumerate(embed.fields):
|
||||||
|
if field.name == "📊 Status":
|
||||||
|
embed.set_field_at(i, name="📊 Status", value=new_status_text, inline=True)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Change color based on status
|
||||||
|
if new_status == "completed":
|
||||||
|
embed.color = discord.Color.green()
|
||||||
|
elif new_status == "in_progress":
|
||||||
|
embed.color = discord.Color.orange()
|
||||||
|
elif new_status == "cancelled":
|
||||||
|
embed.color = discord.Color.red()
|
||||||
|
|
||||||
|
# Add status update log
|
||||||
|
embed.add_field(
|
||||||
|
name=f"📝 Status Updated",
|
||||||
|
value=f"{interaction.user.mention} changed status from {old_status} to {new_status_text}",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
await message.edit(embed=embed)
|
||||||
|
|
||||||
|
# If completed, archive the ticket
|
||||||
|
if new_status == "completed":
|
||||||
|
await archive_ticket(interaction, ticket_id, message, channel)
|
||||||
|
else:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"✅ Ticket `{ticket_id}` status updated to {new_status_text}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
except discord.NotFound:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"❌ Ticket message not found!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"❌ Error updating ticket: {str(e)}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
async def archive_ticket(interaction, ticket_id, message, current_channel):
|
||||||
|
"""Archive a completed ticket"""
|
||||||
|
|
||||||
|
archive_channel_id = config.get("archive_channel_id")
|
||||||
|
if not archive_channel_id:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"⚠️ Status updated, but no archive channel configured. Use `/setup` to configure.",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
archive_channel = bot.get_channel(archive_channel_id)
|
||||||
|
if not archive_channel:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"⚠️ Archive channel not found!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Copy message to archive
|
||||||
|
embed = message.embeds[0]
|
||||||
|
embed.add_field(
|
||||||
|
name="📦 Archived",
|
||||||
|
value=f"Moved to archive on {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
await archive_channel.send(embed=embed)
|
||||||
|
|
||||||
|
# Delete from active channel
|
||||||
|
await message.delete()
|
||||||
|
|
||||||
|
# Update ticket storage
|
||||||
|
tickets[ticket_id]["channel_id"] = archive_channel.id
|
||||||
|
tickets[ticket_id]["archived"] = True
|
||||||
|
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"✅ Ticket `{ticket_id}` marked as completed and archived to {archive_channel.mention}",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
|
||||||
|
@bot.tree.command(name="list", description="List all active tickets")
|
||||||
|
async def list_tickets(interaction: discord.Interaction):
|
||||||
|
"""List all active tickets"""
|
||||||
|
|
||||||
|
active_tickets = [
|
||||||
|
(tid, t) for tid, t in tickets.items()
|
||||||
|
if not t.get("archived", False)
|
||||||
|
]
|
||||||
|
|
||||||
|
if not active_tickets:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
"📭 No active tickets found.",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title="🎫 Active Tickets",
|
||||||
|
color=discord.Color.blue(),
|
||||||
|
timestamp=datetime.utcnow()
|
||||||
|
)
|
||||||
|
|
||||||
|
for ticket_id, ticket in active_tickets[:25]: # Discord limit
|
||||||
|
embed.add_field(
|
||||||
|
name=f"{ticket_id}: {ticket['title']}",
|
||||||
|
value=f"Project: {ticket['project']}\nStatus: {ticket['status']}",
|
||||||
|
inline=False
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(active_tickets) > 25:
|
||||||
|
embed.set_footer(text=f"Showing 25 of {len(active_tickets)} tickets")
|
||||||
|
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
@bot.tree.command(name="info", description="Get detailed information about a ticket")
|
||||||
|
@app_commands.describe(ticket_id="The ticket ID (e.g., TICKET-0001)")
|
||||||
|
async def ticket_info(interaction: discord.Interaction, ticket_id: str):
|
||||||
|
"""Get detailed information about a specific ticket"""
|
||||||
|
|
||||||
|
ticket_id = ticket_id.upper()
|
||||||
|
|
||||||
|
if ticket_id not in tickets:
|
||||||
|
await interaction.response.send_message(
|
||||||
|
f"❌ Ticket `{ticket_id}` not found!",
|
||||||
|
ephemeral=True
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
ticket = tickets[ticket_id]
|
||||||
|
creator = await bot.fetch_user(ticket["creator"])
|
||||||
|
|
||||||
|
embed = discord.Embed(
|
||||||
|
title=f"🎫 {ticket_id} Details",
|
||||||
|
description=ticket["title"],
|
||||||
|
color=discord.Color.blue()
|
||||||
|
)
|
||||||
|
|
||||||
|
embed.add_field(name="📋 Project", value=ticket["project"], inline=True)
|
||||||
|
embed.add_field(name="📊 Status", value=ticket["status"], inline=True)
|
||||||
|
embed.add_field(name="👤 Creator", value=creator.mention, inline=True)
|
||||||
|
embed.add_field(name="📅 Created", value=ticket["created_at"][:10], inline=True)
|
||||||
|
embed.add_field(name="🔗 Reference Message", value=f"`{ticket['reference_message_id']}`", inline=False)
|
||||||
|
embed.add_field(
|
||||||
|
name="📦 Archived",
|
||||||
|
value="Yes" if ticket.get("archived") else "No",
|
||||||
|
inline=True
|
||||||
|
)
|
||||||
|
|
||||||
|
await interaction.response.send_message(embed=embed, ephemeral=True)
|
||||||
|
|
||||||
|
# Run the bot
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if not TOKEN:
|
||||||
|
print("ERROR: DISCORD_TOKEN not found in .env file!")
|
||||||
|
else:
|
||||||
|
bot.run(TOKEN)
|
||||||
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
discord.py>=2.3.0
|
||||||
|
python-dotenv>=1.0.0
|
||||||
|
aiohttp>=3.9.0
|
||||||
Reference in New Issue
Block a user