feat: Add ticketing
All checks were successful
Build Docker / Build Docker (push) Successful in 22s

This commit is contained in:
Nathan Woodburn 2024-02-07 23:24:31 +11:00
parent c37ba86917
commit 7064f0a1f7
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
2 changed files with 385 additions and 3 deletions

109
bot.py
View File

@ -17,6 +17,8 @@ import tools
from tools import parse_time, read_reminders, store_reminder, write_reminders from tools import parse_time, read_reminders, store_reminder, write_reminders
import asyncio import asyncio
from discord.ext import tasks, commands from discord.ext import tasks, commands
from discord.ext.commands import has_permissions, MissingPermissions
import support
@ -233,9 +235,9 @@ async def ssl(ctx, domain: str, showcert: bool = False, notifymeonexpiry: bool =
expiry_date = cert_obj.not_valid_after expiry_date = cert_obj.not_valid_after
# Check if expiry date is past # Check if expiry date is past
if expiry_date < datetime.datetime.now(): if expiry_date < datetime.datetime.utcnow():
message = message + "\n## Expiry Date:\n:x: Certificate has expired\n" message = message + "\n## Expiry Date:\n:x: Certificate has expired\n"
elif expiry_date < datetime.datetime.now() + datetime.timedelta(days=7): elif expiry_date < datetime.datetime.utcnow() + datetime.timedelta(days=7):
message = message + "\n## Expiry Date:\n:warning: Certificate expires soon\n" message = message + "\n## Expiry Date:\n:warning: Certificate expires soon\n"
else: else:
message = message + "\n## Expiry Date:\n:white_check_mark: Certificate is valid\n" message = message + "\n## Expiry Date:\n:white_check_mark: Certificate is valid\n"
@ -363,7 +365,7 @@ async def checkForSSLExpiry():
# Get expiry date # Get expiry date
cert_obj = x509.load_pem_x509_certificate(cert.encode("utf-8"), default_backend()) cert_obj = x509.load_pem_x509_certificate(cert.encode("utf-8"), default_backend())
expiry_date = cert_obj.not_valid_after expiry_date = cert_obj.not_valid_after
if expiry_date < datetime.datetime.now() + datetime.timedelta(days=7): if expiry_date < datetime.datetime.utcnow() + datetime.timedelta(days=7):
user = await client.fetch_user(int(userid)) user = await client.fetch_user(int(userid))
if user: if user:
await user.send(f"SSL certificate for {domain} expires soon") await user.send(f"SSL certificate for {domain} expires soon")
@ -507,6 +509,105 @@ async def timestamp(ctx, when: str):
else: else:
await ctx.response.send_message("Invalid time format. Please use something like `1d 3h` or `4hr`. End with `ago` to convert to past time",ephemeral=True) await ctx.response.send_message("Invalid time format. Please use something like `1d 3h` or `4hr`. End with `ago` to convert to past time",ephemeral=True)
#region Tickets
@tree.command(name="ticket", description="Create a ticket")
async def ticket(ctx):
if (ctx.guild == None):
await ctx.response.send_message("This command can only be used in a server",ephemeral=True)
return
server = ctx.guild.id
if not support.is_server_valid(str(server)):
await ctx.response.send_message("This server is not registered",ephemeral=True)
return
await ctx.response.send_message("Creating ticket...",ephemeral=True)
await support.create_ticket(str(ctx.user.id), str(ctx.guild.id))
@tree.command(name="ticketaddserver", description="Add a server to the ticket system")
@commands.has_permissions(administrator=True)
async def ticketaddserver(ctx, category: str, modrole: discord.Role, closedcategory: str):
if (ctx.user.id != ADMINID):
await log("User: " + str(ctx.user.name) + " tried to use the ticketAddServer command")
await ctx.response.send_message("You don't have permission to use this command",ephemeral=True)
else:
await ctx.response.send_message("Adding server to ticket system",ephemeral=True)
result = await support.ticketAddServer(ctx.guild.id, category, modrole.id,closedcategory)
await ctx.channel.send(result)
@tree.command(name="adduser", description="Add a user to a ticket")
async def adduser(ctx, user: discord.User):
if (ctx.guild == None):
await ctx.response.send_message("This command can only be used in a server",ephemeral=True)
return
server = ctx.guild.id
if not support.is_server_valid(str(server)):
await ctx.response.send_message("This server is not registered",ephemeral=True)
return
result = await support.addMemberToTicket(user,str(ctx.channel.id), str(ctx.guild.id))
await ctx.response.send_message(result,ephemeral=True)
@tree.command(name="removeuser", description="Remove a user from a ticket")
async def removeuser(ctx, user: discord.User):
if (ctx.guild == None):
await ctx.response.send_message("This command can only be used in a server",ephemeral=True)
return
server = ctx.guild.id
if not support.is_server_valid(str(server)):
await ctx.response.send_message("This server is not registered",ephemeral=True)
return
result = await support.removeMemberFromTicket(user,str(ctx.channel.id), str(ctx.guild.id))
await ctx.response.send_message(result,ephemeral=True)
@tree.command(name="closeticket", description="Close a ticket")
async def closeticket(ctx):
if (ctx.guild == None):
await ctx.response.send_message("This command can only be used in a server",ephemeral=True)
return
server = ctx.guild.id
if not support.is_server_valid(str(server)):
await ctx.response.send_message("This server is not registered",ephemeral=True)
return
await ctx.response.send_message("Closing ticket",ephemeral=True)
await support.close_ticket(str(ctx.user.id),str(ctx.channel.id), str(ctx.guild.id))
@tree.command(name="reopenticket", description="Reopen a ticket")
async def reopenticket(ctx):
if (ctx.guild == None):
await ctx.response.send_message("This command can only be used in a server",ephemeral=True)
return
server = ctx.guild.id
if not support.is_server_valid(str(server)):
await ctx.response.send_message("This server is not registered",ephemeral=True)
return
await ctx.response.send_message("Reopening ticket",ephemeral=True)
await support.reopen_ticket(str(ctx.user.id),str(ctx.channel.id), str(ctx.guild.id))
@tree.command(name="renameticket", description="Rename a ticket")
async def renameticket(ctx, name: str):
if (ctx.guild == None):
await ctx.response.send_message("This command can only be used in a server",ephemeral=True)
return
server = ctx.guild.id
if not support.is_server_valid(str(server)):
await ctx.response.send_message("This server is not registered",ephemeral=True)
return
result = await support.rename_ticket(str(ctx.user.id),str(ctx.channel.id), str(ctx.guild.id),name)
await ctx.response.send_message(result,ephemeral=True)
#endregion
@tasks.loop(seconds=10) @tasks.loop(seconds=10)
async def check_reminders(): async def check_reminders():
now = datetime.datetime.now() now = datetime.datetime.now()
@ -529,6 +630,7 @@ async def check_reminders():
async def on_ready(): async def on_ready():
global ADMINID global ADMINID
ADMINID = client.application.owner.id ADMINID = client.application.owner.id
support.set_client(client)
await tree.sync() await tree.sync()
updateStatus() updateStatus()
check_reminders.start() check_reminders.start()
@ -538,3 +640,4 @@ async def on_ready():
client.run(TOKEN) client.run(TOKEN)

279
support.py Normal file
View File

@ -0,0 +1,279 @@
import datetime
import re
import discord
import json
import os
import dotenv
dotenv.load_dotenv()
TICKETS_FILE_PATH = '/mnt/tickets.json'
DISCORD_TOKEN = os.getenv('DISCORD_TOKEN')
intents = discord.Intents.default()
client = discord.Client(intents=intents)
def set_client(c):
global client
client = c
print("Client set")
if not os.path.exists(TICKETS_FILE_PATH):
with open(TICKETS_FILE_PATH, 'w') as f:
json.dump({"server": {}}, f)
def is_server_valid(server:str):
server = server
with open(TICKETS_FILE_PATH, 'r') as f:
ticketsData = json.load(f)
if server in ticketsData['server']:
return True
return False
async def create_ticket(user_id, server:str):
with open(TICKETS_FILE_PATH, 'r') as f:
ticketsData = json.load(f)
if server not in ticketsData['server']:
return "Server not found"
tickets = ticketsData['server'][server]['tickets']
if user_id != "admin":
for ticket in tickets:
if ticket['user_id'] == user_id and ticket['status'] == "open":
return "You already have an open ticket"
ticketNum = len(tickets) + 1
# Generate a ticket name
user = await client.fetch_user(int(user_id))
if user == None:
return "User not found"
# Create a new ticket channel
guild = await client.fetch_guild(int(server))
guild = client.get_guild(int(server))
ticketName = f"{ticketNum}-{user.name}-ticket"
category = discord.utils.get(guild.categories, id=int(ticketsData['server'][server]['category']))
if category == None:
return "Category not found"
# Create the ticket channel
ticketChannel = await guild.create_text_channel(ticketName, category=category)
# Remove the default permissions
await ticketChannel.set_permissions(guild.default_role, read_messages=False, send_messages=False)
# Add the user to the ticket channel
await ticketChannel.set_permissions(user, read_messages=True, send_messages=True)
# Add the modRole to the ticket channel
admin = guild.get_role(int(ticketsData['server'][server]['adminRole']))
await ticketChannel.set_permissions(admin, read_messages=True, send_messages=True)
# Add the ticket to the tickets list
tickets.append({
'user_id': user_id,
'channel_id': ticketChannel.id,
'status': "open",
'members': [user_id]
})
ticketsData['server'][server]['tickets'] = tickets
with open(TICKETS_FILE_PATH, 'w') as f:
json.dump(ticketsData, f)
await ticketChannel.send(f"G'day <@{user_id}>, your ticket has been created. Please let us know how we can help you.")
await ticketChannel.send(f"Commands: \n</adduser:1204746777639653386> <user> - Add a user to the ticket\n</removeuser:1204747212878520402> <user> - Remove a user from the ticket\n</renameticket:1204758890332291123> <new_name> - Rename the ticket\n</closeticket:1204742678005424131> - Close the ticket")
return f"Ticket <#{ticketChannel.id}> created"
async def ticketAddServer(server, category, adminRole,closedCategory):
with open(TICKETS_FILE_PATH, 'r') as f:
ticketsData = json.load(f)
ticketsData['server'][server] = {
'category': category,
'closedCategory': closedCategory,
'adminRole': adminRole,
'tickets': []
}
with open(TICKETS_FILE_PATH, 'w') as f:
json.dump(ticketsData, f)
return "Server added"
async def close_ticket(user_id,channel_id, server):
with open(TICKETS_FILE_PATH, 'r') as f:
ticketsData = json.load(f)
tickets = ticketsData['server'][server]['tickets']
validTicket = False
for ticket in tickets:
if str(ticket['channel_id']) == channel_id:
ticket['status'] = "closed"
validTicket = True
break
if not validTicket:
return "This ticket does not exist"
ticketsData['server'][server]['tickets'] = tickets
with open(TICKETS_FILE_PATH, 'w') as f:
json.dump(ticketsData, f)
# Remove read and send permissions for everyone
guild = await client.fetch_guild(int(server))
guild = client.get_guild(int(server))
ticketChannel = guild.get_channel(int(channel_id))
await ticketChannel.set_permissions(guild.default_role, read_messages=False, send_messages=False)
for member in ticket['members']:
user = await client.fetch_user(int(member))
await ticketChannel.set_permissions(user, read_messages=False, send_messages=False)
await ticketChannel.send(f"This ticket has been closed by <@{user_id}>")
# Move to the closed category
closedCategory = discord.utils.get(guild.categories, id=int(ticketsData['server'][server]['closedCategory']))
if closedCategory == None:
return "Category not found"
await ticketChannel.edit(category=closedCategory)
async def addMemberToTicket(user_id, channel_id, server):
guild = await client.fetch_guild(int(server))
guild = client.get_guild(int(server))
user = await fuzzyUser(user_id, guild)
if user == False:
return "User not found"
user_id = str(user.id)
with open(TICKETS_FILE_PATH, 'r') as f:
ticketsData = json.load(f)
tickets = ticketsData['server'][server]['tickets']
validTicket = False
for ticket in tickets:
if str(ticket['channel_id']) == channel_id:
if user_id in ticket['members']:
return "User already in ticket"
ticket['members'].append(user_id)
validTicket = True
break
if not validTicket:
return "This ticket does not exist"
ticketsData['server'][server]['tickets'] = tickets
with open(TICKETS_FILE_PATH, 'w') as f:
json.dump(ticketsData, f)
ticketChannel = guild.get_channel(int(channel_id))
await ticketChannel.set_permissions(user, read_messages=True, send_messages=True)
await ticketChannel.send(f"{user.mention} has been added to the ticket")
return "User added to ticket"
async def removeMemberFromTicket(user_id, channel_id, server):
guild = await client.fetch_guild(int(server))
guild = client.get_guild(int(server))
user = await fuzzyUser(user_id, guild)
if user == False:
return "User not found"
user_id = str(user.id)
with open(TICKETS_FILE_PATH, 'r') as f:
ticketsData = json.load(f)
tickets = ticketsData['server'][server]['tickets']
validTicket = False
for ticket in tickets:
if str(ticket['channel_id']) == channel_id:
if user_id not in ticket['members']:
return "User not found in ticket"
ticket['members'].remove(user_id)
validTicket = True
break
if not validTicket:
return "This ticket does not exist"
ticketsData['server'][server]['tickets'] = tickets
with open(TICKETS_FILE_PATH, 'w') as f:
json.dump(ticketsData, f)
ticketChannel = guild.get_channel(int(channel_id))
await ticketChannel.set_permissions(user, read_messages=False, send_messages=False)
await ticketChannel.send(f"{user.mention} has been removed from the ticket")
return "User removed from ticket"
async def reopen_ticket(user_id,channel_id,guild_id):
with open(TICKETS_FILE_PATH, 'r') as f:
ticketsData = json.load(f)
tickets = ticketsData['server'][guild_id]['tickets']
validTicket = False
for ticket in tickets:
if str(ticket['channel_id']) == channel_id:
if ticket['status'] == "open":
return "Ticket is already open"
ticket['status'] = "open"
validTicket = True
break
if not validTicket:
return "This ticket does not exist"
ticketsData['server'][guild_id]['tickets'] = tickets
with open(TICKETS_FILE_PATH, 'w') as f:
json.dump(ticketsData, f)
guild = await client.fetch_guild(int(guild_id))
guild = client.get_guild(int(guild_id))
channel = guild.get_channel(int(channel_id))
# Remove read and send permissions for everyone
await channel.set_permissions(guild.default_role, read_messages=False, send_messages=False)
for member in ticket['members']:
user = await client.fetch_user(int(member))
await channel.set_permissions(user, read_messages=True, send_messages=True)
await channel.send(f"This ticket has been reopened by <@{user_id}>")
# Move to the closed category
category = discord.utils.get(guild.categories, id=int(ticketsData['server'][guild_id]['category']))
if category == None:
return "Category not found"
await channel.edit(category=category)
return "Ticket reopened"
async def rename_ticket(user_id,channel_id,server_id,newName):
try:
guild = await client.fetch_guild(int(server_id))
guild = client.get_guild(int(server_id))
channel = guild.get_channel(int(channel_id))
await channel.edit(name=newName)
return "Ticket renamed"
except:
return "Error renaming ticket"
async def fuzzyUser(search, guild):
user = None
try:
user = await client.fetch_user(int(search))
except:
pass
if user == None:
user = discord.utils.get(guild.members, name=search)
if user == None:
user = discord.utils.get(guild.members, nick=search)
if user == None:
user = discord.utils.get(guild.members, global_name=search)
if user == None:
user = discord.utils.get(guild.members, global_name=search)
if user == None:
user = guild.get_member_named(search)
if user == None:
user = await guild.query_members(search)
if len(user) > 0:
user = user[0]
else:
return False
if user == None:
return False
return user