From c7b44232c421ce896a7c57370c78799b14610f64 Mon Sep 17 00:00:00 2001 From: DJj123dj <80536295+DJj123dj@users.noreply.github.com> Date: Sun, 2 Nov 2025 20:09:38 +0100 Subject: [PATCH] Added transfer command (/transfer) + new stats With this command you're able to transfer the ticket ownership to another person in the server. --- src/actions/transferTicket.ts | 154 ++++++++++++++++++++++++++++ src/builders/embeds.ts | 16 ++- src/builders/messages.ts | 9 ++ src/commands/transfer.ts | 81 +++++++++++++++ src/core/api/defaults/action.ts | 6 ++ src/core/api/defaults/builder.ts | 6 +- src/core/api/defaults/client.ts | 2 + src/core/api/defaults/event.ts | 6 +- src/core/api/defaults/responder.ts | 1 + src/core/api/defaults/stat.ts | 2 + src/data/framework/commandLoader.ts | 45 ++++++++ src/data/framework/eventLoader.ts | 2 + src/data/framework/statLoader.ts | 34 +++--- src/index.ts | 2 + 14 files changed, 345 insertions(+), 21 deletions(-) create mode 100644 src/actions/transferTicket.ts create mode 100644 src/commands/transfer.ts diff --git a/src/actions/transferTicket.ts b/src/actions/transferTicket.ts new file mode 100644 index 0000000..85887dc --- /dev/null +++ b/src/actions/transferTicket.ts @@ -0,0 +1,154 @@ +/////////////////////////////////////// +//TICKET TRANSFER SYSTEM +/////////////////////////////////////// +import {opendiscord, api, utilities} from "../index" +import * as discord from "discord.js" + +const generalConfig = opendiscord.configs.get("opendiscord:general") + +export const registerActions = async () => { + opendiscord.actions.add(new api.ODAction("opendiscord:transfer-ticket")) + opendiscord.actions.get("opendiscord:transfer-ticket").workers.add([ + new api.ODWorker("opendiscord:transfer-ticket",2,async (instance,params,source,cancel) => { + const {guild,channel,user,ticket,reason,newCreator} = params + if (channel.isThread()) throw new api.ODSystemError("Unable to transfer ticket! Open Ticket doesn't support threads!") + + const oldCreator = await opendiscord.tickets.getTicketUser(ticket,"creator") ?? opendiscord.client.client.user + await opendiscord.events.get("onTicketTransfer").emit([ticket,user,channel,oldCreator,newCreator,reason]) + + //update ticket + const oldCreatorId = ticket.get("opendiscord:opened-by").value + if (oldCreatorId){ + ticket.get("opendiscord:previous-creators").value.push(oldCreatorId) + ticket.get("opendiscord:previous-creators").refreshDatabase() + } + if (!ticket.get("opendiscord:participants").value.find((p) => p.type == "user" && p.id == newCreator.id)){ + ticket.get("opendiscord:participants").value.push({type:"user",id:newCreator.id}) + ticket.get("opendiscord:participants").refreshDatabase() + } + ticket.get("opendiscord:opened-by").value = newCreator.id + if (["user-name","user-nickname","user-id"].includes(ticket.option.get("opendiscord:channel-suffix").value)){ + const newSuffix = await opendiscord.options.suffix.getSuffixFromOption(ticket.option,newCreator,guild) + if (newSuffix) ticket.get("opendiscord:channel-suffix").value = newSuffix + } + + //update stats + await opendiscord.stats.get("opendiscord:global").setStat("opendiscord:tickets-transferred",1,"increase") + await opendiscord.stats.get("opendiscord:user").setStat("opendiscord:tickets-transferred",user.id,1,"increase") + + //get new channel properties + const channelPrefix = ticket.option.get("opendiscord:channel-prefix").value + const channelSuffix = ticket.get("opendiscord:channel-suffix").value + + //handle permissions + const permissions: discord.OverwriteResolvable[] = [{ + type:discord.OverwriteType.Role, + id:guild.roles.everyone.id, + allow:[], + deny:["ViewChannel","SendMessages","ReadMessageHistory"] + }] + const globalAdmins = opendiscord.configs.get("opendiscord:general").data.globalAdmins + const optionAdmins = ticket.option.get("opendiscord:admins").value + const readonlyAdmins = ticket.option.get("opendiscord:admins-readonly").value + + globalAdmins.forEach((admin) => { + permissions.push({ + type:discord.OverwriteType.Role, + id:admin, + allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory","ManageMessages"], + deny:[] + }) + }) + optionAdmins.forEach((admin) => { + if (globalAdmins.includes(admin)) return + permissions.push({ + type:discord.OverwriteType.Role, + id:admin, + allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory","ManageMessages"], + deny:[] + }) + }) + readonlyAdmins.forEach((admin) => { + if (globalAdmins.includes(admin)) return + if (optionAdmins.includes(admin)) return + permissions.push({ + type:discord.OverwriteType.Role, + id:admin, + allow:["ViewChannel","ReadMessageHistory"], + deny:["SendMessages","AddReactions","AttachFiles","SendPolls"] + }) + }) + //transfer all old user-participants over to the new ticket (creator & participants) + ticket.get("opendiscord:participants").value.forEach((p) => { + if (p.type == "user") permissions.push({ + type:discord.OverwriteType.Member, + id:p.id, + allow:["ViewChannel","SendMessages","AddReactions","AttachFiles","SendPolls","ReadMessageHistory"], + deny:[] + }) + }) + try{ + await channel.permissionOverwrites.set(permissions) + }catch{ + opendiscord.log("Failed to reset channel permissions on ticket transfer!","error") + } + + //rename channel (and give error when crashed) + const pinEmoji = ticket.get("opendiscord:pinned").value ? generalConfig.data.system.pinEmoji : "" + const priorityEmoji = opendiscord.priorities.getFromPriorityLevel(ticket.get("opendiscord:priority").value).channelEmoji ?? "" + + const originalName = channel.name + const newName = pinEmoji+priorityEmoji+utilities.trimEmojis(channelPrefix+channelSuffix) + try{ + await utilities.timedAwait(channel.setName(newName),2500,(err) => { + opendiscord.log("Failed to rename channel on ticket transfer","error") + }) + }catch(err){ + await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:error-channel-rename").build("ticket-transfer",{guild,channel,user,originalName,newName:newName})).message) + } + + //update ticket message + const ticketMessage = await opendiscord.tickets.getTicketMessage(ticket) + if (ticketMessage){ + try{ + ticketMessage.edit((await opendiscord.builders.messages.getSafe("opendiscord:ticket-message").build("other",{guild,channel,user,ticket})).message) + }catch(e){ + opendiscord.log("Unable to edit ticket message on ticket transferring!","error",[ + {key:"channel",value:"#"+channel.name}, + {key:"channelid",value:channel.id,hidden:true}, + {key:"messageid",value:ticketMessage.id}, + {key:"option",value:ticket.option.id.value} + ]) + opendiscord.debugfile.writeErrorMessage(new api.ODError(e,"uncaughtException")) + } + } + + //reply with new message + if (params.sendMessage) await channel.send((await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(source,{guild,channel,user,ticket,oldCreator,newCreator,reason})).message) + ticket.get("opendiscord:busy").value = false + await opendiscord.events.get("afterTicketTransferred").emit([ticket,user,channel,oldCreator,newCreator,reason]) + + //update channel topic + await opendiscord.actions.get("opendiscord:update-ticket-topic").run("ticket-action",{guild,channel,user,ticket,sendMessage:false,newTopic:null}) + }), + new api.ODWorker("opendiscord:discord-logs",1,async (instance,params,source,cancel) => { + const {guild,channel,user,ticket,newCreator,reason} = params + }), + new api.ODWorker("opendiscord:logs",0,(instance,params,source,cancel) => { + const {guild,channel,user,ticket,newCreator} = params + + opendiscord.log(user.displayName+" transferred a ticket to '"+newCreator.displayName+"'!","info",[ + {key:"user",value:user.username}, + {key:"userid",value:user.id,hidden:true}, + {key:"channel",value:"#"+channel.name}, + {key:"channelid",value:channel.id,hidden:true}, + {key:"reason",value:params.reason ?? "/"}, + {key:"method",value:source} + ]) + }) + ]) + opendiscord.actions.get("opendiscord:transfer-ticket").workers.backupWorker = new api.ODWorker("opendiscord:cancel-busy",0,(instance,params) => { + //set busy to false in case of crash or cancel + params.ticket.get("opendiscord:busy").value = false + }) +} \ No newline at end of file diff --git a/src/builders/embeds.ts b/src/builders/embeds.ts index dfa5992..efde0cf 100644 --- a/src/builders/embeds.ts +++ b/src/builders/embeds.ts @@ -330,7 +330,7 @@ const errorEmbeds = () => { new api.ODWorker("opendiscord:error-channel-rename",0,async (instance,params,source) => { const {channel,user,originalName,newName} = params - const method = (source == "ticket-move" || source == "ticket-pin" || source == "ticket-rename" || source == "ticket-unpin" || source == "ticket-priority") ? source : getMethodFromSource(source) + const method = (source == "ticket-move" || source == "ticket-pin" || source == "ticket-rename" || source == "ticket-unpin" || source == "ticket-priority" || source == "ticket-transfer") ? source : getMethodFromSource(source) instance.setColor(generalConfig.data.system.useRedErrorEmbeds ? "Red" : generalConfig.data.mainColor) instance.setTitle(utilities.emojiTitle("❌",lang.getTranslation("errors.titles.channelRename"))) @@ -1353,4 +1353,18 @@ const extraEmbeds = () => { instance.setDescription("The current priority of this ticket is **"+priority.renderDisplayName()+"**!") //TODO TRANSLATION!!! }) ) + + //TRANSFER MESSAGE + embeds.add(new api.ODEmbed("opendiscord:transfer-message")) + embeds.get("opendiscord:transfer-message").workers.add( + new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,source) => { + const {user,oldCreator,newCreator,reason} = params + + instance.setAuthor(user.displayName,user.displayAvatarURL()) + instance.setColor(generalConfig.data.mainColor) + instance.setTitle(utilities.emojiTitle("🔀","Ticket Transferred")) //TODO TRANSLATION!!! + instance.setDescription("The ticket ownership has been transferred from "+discord.userMention(oldCreator.id)+" to "+discord.userMention(newCreator.id)+" by "+discord.userMention(user.id)+" successfully!") //TODO TRANSLATION!!! + if (reason) instance.addFields({name:lang.getTranslation("params.uppercase.reason")+":",value:"```"+reason+"```"}) + }) + ) } \ No newline at end of file diff --git a/src/builders/messages.ts b/src/builders/messages.ts index d08f283..0b89c1b 100644 --- a/src/builders/messages.ts +++ b/src/builders/messages.ts @@ -1102,4 +1102,13 @@ const extraMessages = () => { instance.addEmbed(await embeds.getSafe("opendiscord:priority-get").build(source,{guild,channel,user,ticket,priority})) }) ) + + //TRANSFER MESSAGE + messages.add(new api.ODMessage("opendiscord:transfer-message")) + messages.get("opendiscord:transfer-message").workers.add( + new api.ODWorker("opendiscord:transfer-message",0,async (instance,params,source) => { + const {guild,channel,user,ticket,oldCreator,newCreator,reason} = params + instance.addEmbed(await embeds.getSafe("opendiscord:transfer-message").build(source,{guild,channel,user,ticket,oldCreator,newCreator,reason})) + }) + ) } \ No newline at end of file diff --git a/src/commands/transfer.ts b/src/commands/transfer.ts new file mode 100644 index 0000000..18c8c8b --- /dev/null +++ b/src/commands/transfer.ts @@ -0,0 +1,81 @@ +/////////////////////////////////////// +//TRANSFER COMMAND +/////////////////////////////////////// +import {opendiscord, api, utilities} from "../index" +import * as discord from "discord.js" + +const generalConfig = opendiscord.configs.get("opendiscord:general") + +export const registerCommandResponders = async () => { + //TRANSFER COMMAND RESPONDER + opendiscord.responders.commands.add(new api.ODCommandResponder("opendiscord:transfer",generalConfig.data.prefix,"transfer")) + opendiscord.responders.commands.get("opendiscord:transfer").workers.add([ + new api.ODWorker("opendiscord:permissions",1,async (instance,params,source,cancel) => { + const permissionMode = generalConfig.data.system.permissions.transfer + + if (permissionMode == "none"){ + //no permissions + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build("button",{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) + return cancel() + }else if (permissionMode == "everyone") return + else if (permissionMode == "admin"){ + if (!opendiscord.permissions.hasPermissions("support",await opendiscord.permissions.getPermissions(instance.user,instance.channel,instance.guild))){ + //no permissions + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:["support"]})) + return cancel() + }else return + }else{ + if (!instance.guild || !instance.member){ + //error + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #1",layout:"advanced"})) + return cancel() + } + const role = await opendiscord.client.fetchGuildRole(instance.guild,permissionMode) + if (!role){ + //error + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,error:"Permission Error: Not in Server #2",layout:"advanced"})) + return cancel() + } + if (!role.members.has(instance.member.id)){ + //no permissions + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-no-permissions").build(source,{guild:instance.guild,channel:instance.channel,user:instance.user,permissions:[]})) + return cancel() + }else return + } + }), + new api.ODWorker("opendiscord:transfer",0,async (instance,params,source,cancel) => { + const {guild,channel,user} = instance + if (!guild){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-not-in-guild").build("button",{channel,user})) + return cancel() + } + const ticket = opendiscord.tickets.get(channel.id) + if (!ticket || channel.isDMBased()){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-unknown").build("button",{guild,channel,user})) + return cancel() + } + //return when busy + if (ticket.get("opendiscord:busy").value){ + instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:error-ticket-busy").build("button",{guild,channel,user})) + return cancel() + } + + const oldCreator = await opendiscord.tickets.getTicketUser(ticket,"creator") ?? opendiscord.client.client.user + const newCreator = instance.options.getUser("user",true) + const reason = instance.options.getString("reason",false) + + //start transferring ticket ownership + await instance.defer(false) + await opendiscord.actions.get("opendiscord:transfer-ticket").run(source,{guild,channel,user,ticket,reason,sendMessage:false,newCreator}) + await instance.reply(await opendiscord.builders.messages.getSafe("opendiscord:transfer-message").build(source,{guild,channel,user,ticket,oldCreator,newCreator,reason})) + }), + new api.ODWorker("opendiscord:logs",-1,(instance,params,source,cancel) => { + opendiscord.log(instance.user.displayName+" used the 'transfer' command!","info",[ + {key:"user",value:instance.user.username}, + {key:"userid",value:instance.user.id,hidden:true}, + {key:"channelid",value:instance.channel.id,hidden:true}, + {key:"method",value:source} + ]) + }) + ]) +} \ No newline at end of file diff --git a/src/core/api/defaults/action.ts b/src/core/api/defaults/action.ts index a3387ed..c86269b 100644 --- a/src/core/api/defaults/action.ts +++ b/src/core/api/defaults/action.ts @@ -125,6 +125,12 @@ export interface ODActionManagerIds_Default { result:{}, workers:"opendiscord:update-ticket-priority"|"opendiscord:discord-logs"|"opendiscord:logs" }, + "opendiscord:transfer-ticket":{ + source:"slash"|"text"|"other", + params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,newCreator:discord.User,reason:string|null,sendMessage:boolean}, + result:{}, + workers:"opendiscord:transfer-ticket"|"opendiscord:discord-logs"|"opendiscord:logs" + }, } /**## ODActionManager_Default `default_class` diff --git a/src/core/api/defaults/builder.ts b/src/core/api/defaults/builder.ts index 39e3b02..96c652b 100644 --- a/src/core/api/defaults/builder.ts +++ b/src/core/api/defaults/builder.ts @@ -239,7 +239,7 @@ export interface ODEmbedManagerIds_Default { "opendiscord:error-option-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, "opendiscord:error-panel-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, "opendiscord:error-not-in-guild":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, - "opendiscord:error-channel-rename":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, + "opendiscord:error-channel-rename":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, "opendiscord:error-ticket-busy":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, "opendiscord:help-menu":{source:"text"|"slash"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, @@ -299,6 +299,7 @@ export interface ODEmbedManagerIds_Default { "opendiscord:topic-set":{source:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, "opendiscord:priority-set":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, "opendiscord:priority-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, + "opendiscord:transfer-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, } /**## ODEmbedManager_Default `default_class` @@ -375,7 +376,7 @@ export interface ODMessageManagerIds_Default { "opendiscord:error-option-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-option-unknown"}, "opendiscord:error-panel-unknown":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-panel-unknown"}, "opendiscord:error-not-in-guild":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-not-in-guild"}, - "opendiscord:error-channel-rename":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, + "opendiscord:error-channel-rename":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"ticket-pin"|"ticket-unpin"|"ticket-rename"|"ticket-move"|"ticket-priority"|"ticket-transfer"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User,originalName:string,newName:string},workers:"opendiscord:error-channel-rename"}, "opendiscord:error-ticket-busy":{source:"slash"|"text"|"button"|"dropdown"|"modal"|"other",params:{guild:discord.Guild|null,channel:discord.TextBasedChannel,user:discord.User},workers:"opendiscord:error-ticket-busy"}, "opendiscord:help-menu":{source:"slash"|"text"|"button"|"other",params:{mode:"slash"|"text",page:number},workers:"opendiscord:help-menu"}, @@ -437,6 +438,7 @@ export interface ODMessageManagerIds_Default { "opendiscord:topic-set":{source:"slash"|"text"|"ticket-action"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,topic:string},workers:"opendiscord:topic-set"}, "opendiscord:priority-set":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel,reason:string|null},workers:"opendiscord:priority-set"}, "opendiscord:priority-get":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,priority:ODPriorityLevel},workers:"opendiscord:priority-get"}, + "opendiscord:transfer-message":{source:"slash"|"text"|"other",params:{guild:discord.Guild,channel:discord.GuildTextBasedChannel,user:discord.User,ticket:ODTicket,oldCreator:discord.User,newCreator:discord.User,reason:string|null},workers:"opendiscord:transfer-message"}, } /**## ODMessageManager_Default `default_class` diff --git a/src/core/api/defaults/client.ts b/src/core/api/defaults/client.ts index a56ad33..7b5a540 100644 --- a/src/core/api/defaults/client.ts +++ b/src/core/api/defaults/client.ts @@ -52,6 +52,7 @@ export interface ODSlashCommandManagerIds_Default { "opendiscord:autodelete":ODSlashCommand, "opendiscord:topic":ODSlashCommand, "opendiscord:priority":ODSlashCommand, + "opendiscord:transfer":ODSlashCommand, } /**## ODSlashCommandManager_Default `default_class` @@ -125,6 +126,7 @@ export interface ODTextCommandManagerIds_Default { "opendiscord:topic-set":ODTextCommand, "opendiscord:priority-set":ODTextCommand, "opendiscord:priority-get":ODTextCommand, + "opendiscord:transfer":ODTextCommand, } /**## ODTextCommandManager_Default `default_class` diff --git a/src/core/api/defaults/event.ts b/src/core/api/defaults/event.ts index 3315d08..86496fc 100644 --- a/src/core/api/defaults/event.ts +++ b/src/core/api/defaults/event.ts @@ -204,8 +204,10 @@ export interface ODEventIds_Default { "afterTicketsCleared": ODEvent_Default<(tickets:ODTicket[], clearer:discord.User, channel:discord.GuildTextBasedChannel, filter:ODTicketClearFilter) => ODPromiseVoid> "onTicketTopicChange": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => ODPromiseVoid> "afterTicketTopicChanged": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldTopic:string, newTopic:string) => ODPromiseVoid> - "onTicketPriorityChange": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel) => ODPromiseVoid> - "afterTicketPriorityChanged": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel) => ODPromiseVoid> + "onTicketPriorityChange": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => ODPromiseVoid> + "afterTicketPriorityChanged": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldPriority:ODPriorityLevel, newPriority:ODPriorityLevel, reason:string|null) => ODPromiseVoid> + "onTicketTransfer": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => ODPromiseVoid> + "afterTicketTransferred": ODEvent_Default<(ticket:ODTicket, changer:discord.User, channel:discord.GuildTextBasedChannel, oldCreator:discord.User, newCreator:discord.User, reason:string|null) => ODPromiseVoid> //roles "onRoleLoad": ODEvent_Default<(roles:ODRoleManager) => ODPromiseVoid> diff --git a/src/core/api/defaults/responder.ts b/src/core/api/defaults/responder.ts index 6023033..8d7b317 100644 --- a/src/core/api/defaults/responder.ts +++ b/src/core/api/defaults/responder.ts @@ -46,6 +46,7 @@ export interface ODCommandResponderManagerIds_Default { "opendiscord:clear":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:clear"|"opendiscord:logs"}, "opendiscord:topic":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:topic"|"opendiscord:logs"}, "opendiscord:priority":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:priority"|"opendiscord:logs"}, + "opendiscord:transfer":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:transfer"|"opendiscord:logs"}, "opendiscord:autoclose":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:autoclose"|"opendiscord:logs"}, "opendiscord:autodelete":{source:"slash"|"text",params:{},workers:"opendiscord:permissions"|"opendiscord:autodelete"|"opendiscord:logs"}, diff --git a/src/core/api/defaults/stat.ts b/src/core/api/defaults/stat.ts index 47e306c..dd8486e 100644 --- a/src/core/api/defaults/stat.ts +++ b/src/core/api/defaults/stat.ts @@ -60,6 +60,7 @@ export interface ODStatGlobalScopeIds_DefaultGlobal { "opendiscord:tickets-claimed":ODBasicStat, "opendiscord:tickets-pinned":ODBasicStat, "opendiscord:tickets-moved":ODBasicStat, + "opendiscord:tickets-transferred":ODBasicStat, "opendiscord:users-blacklisted":ODBasicStat, "opendiscord:transcripts-created":ODBasicStat, "opendiscord:ticket-volume":ODDynamicStat, @@ -204,6 +205,7 @@ export interface ODStatScopeIds_DefaultUser { "opendiscord:tickets-claimed":ODBasicStat, "opendiscord:tickets-pinned":ODBasicStat, "opendiscord:tickets-moved":ODBasicStat, + "opendiscord:tickets-transferred":ODBasicStat, "opendiscord:users-blacklisted":ODBasicStat, "opendiscord:transcripts-created":ODBasicStat, "opendiscord:current-tickets":ODDynamicStat, diff --git a/src/data/framework/commandLoader.ts b/src/data/framework/commandLoader.ts index d20df8f..a72d965 100644 --- a/src/data/framework/commandLoader.ts +++ b/src/data/framework/commandLoader.ts @@ -617,6 +617,29 @@ export const loadAllSlashCommands = async () => { //TODO: list (v4.2) ] })) + + //TRANSFER + if (allowedCommands.includes("transfer")) commands.add(new api.ODSlashCommand("opendiscord:transfer",{ + type:act.ChatInput, + name:"transfer", + description:"Transfer the ticket ownership from one user to another.", //TODO TRANSLATION!!! + contexts:[discord.InteractionContextType.Guild], + integrationTypes:[discord.ApplicationIntegrationType.GuildInstall], + options:[ + { + name:"user", + description:"The user to transfer to.", //TODO TRANSLATION!!! + type:acot.User, + required:true + }, + { + name:"reason", + description:lang.getTranslation("commands.reason"), + type:acot.String, + required:false + } + ] + })) } export const loadAllTextCommands = async () => { @@ -1169,6 +1192,28 @@ export const loadAllTextCommands = async () => { guildPermission:true, allowBots:false, })) + + //TRANSFER + if (allowedCommands.includes("transfer")) commands.add(new api.ODTextCommand("opendiscord:transfer",{ + name:"transfer", + prefix, + dmPermission:false, + guildPermission:true, + allowBots:false, + options:[ + { + name:"user", + type:"user", + required:true + }, + { + name:"reason", + type:"string", + required:false, + allowSpaces:true + } + ] + })) } export const loadAllContextMenus = async () => { diff --git a/src/data/framework/eventLoader.ts b/src/data/framework/eventLoader.ts index 257aa5e..cd195c5 100644 --- a/src/data/framework/eventLoader.ts +++ b/src/data/framework/eventLoader.ts @@ -159,6 +159,8 @@ export const loadAllEvents = () => { "afterTicketTopicChanged", "onTicketPriorityChange", "afterTicketPriorityChanged", + "onTicketTransfer", + "afterTicketTransferred", //roles "onRoleLoad", diff --git a/src/data/framework/statLoader.ts b/src/data/framework/statLoader.ts index 50de840..44263d4 100644 --- a/src/data/framework/statLoader.ts +++ b/src/data/framework/statLoader.ts @@ -19,15 +19,16 @@ export const loadAllStats = async () => { const global = stats.get("opendiscord:global") if (global){ - global.add(new api.ODBasicStat("opendiscord:tickets-created",12,lang.getTranslation("stats.properties.ticketsCreated"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-closed",11,lang.getTranslation("stats.properties.ticketsClosed"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-deleted",10,lang.getTranslation("stats.properties.ticketsDeleted"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-reopened",9,lang.getTranslation("stats.properties.ticketsReopened"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-autoclosed",8,lang.getTranslation("stats.properties.ticketsAutoclosed"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-autodeleted",7,"Tickets Autodeleted",0)) //TODO TRANSLATION!!! - global.add(new api.ODBasicStat("opendiscord:tickets-claimed",6,lang.getTranslation("stats.properties.ticketsClaimed"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-pinned",5,lang.getTranslation("stats.properties.ticketsPinned"),0)) - global.add(new api.ODBasicStat("opendiscord:tickets-moved",4,lang.getTranslation("stats.properties.ticketsMoved"),0)) + global.add(new api.ODBasicStat("opendiscord:tickets-created",13,lang.getTranslation("stats.properties.ticketsCreated"),0)) + global.add(new api.ODBasicStat("opendiscord:tickets-closed",12,lang.getTranslation("stats.properties.ticketsClosed"),0)) + global.add(new api.ODBasicStat("opendiscord:tickets-deleted",11,lang.getTranslation("stats.properties.ticketsDeleted"),0)) + global.add(new api.ODBasicStat("opendiscord:tickets-reopened",10,lang.getTranslation("stats.properties.ticketsReopened"),0)) + global.add(new api.ODBasicStat("opendiscord:tickets-autoclosed",9,lang.getTranslation("stats.properties.ticketsAutoclosed"),0)) + global.add(new api.ODBasicStat("opendiscord:tickets-autodeleted",8,"Tickets Autodeleted",0)) //TODO TRANSLATION!!! + global.add(new api.ODBasicStat("opendiscord:tickets-claimed",7,lang.getTranslation("stats.properties.ticketsClaimed"),0)) + global.add(new api.ODBasicStat("opendiscord:tickets-pinned",6,lang.getTranslation("stats.properties.ticketsPinned"),0)) + global.add(new api.ODBasicStat("opendiscord:tickets-moved",5,lang.getTranslation("stats.properties.ticketsMoved"),0)) + global.add(new api.ODBasicStat("opendiscord:tickets-transferred",4,"Tickets Transferred",0)) //TODO TRANSLATION!!! global.add(new api.ODBasicStat("opendiscord:users-blacklisted",3,lang.getTranslation("stats.properties.usersBlacklisted"),0)) global.add(new api.ODBasicStat("opendiscord:transcripts-created",2,lang.getTranslation("stats.properties.transcriptsCreated"),0)) global.add(new api.ODDynamicStat("opendiscord:ticket-volume",1,() => { @@ -71,13 +72,14 @@ export const loadAllStats = async () => { if (permissions.type == "support") return lang.getTranslation("params.uppercase.role")+": 💬 `Support Team`" //TODO TRANSLATION!!! else return lang.getTranslation("params.uppercase.role")+": 👤 `Member`" //TODO TRANSLATION!!! })) - user.add(new api.ODBasicStat("opendiscord:tickets-created",9,lang.getTranslation("stats.properties.ticketsCreated"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-closed",8,lang.getTranslation("stats.properties.ticketsClosed"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-deleted",7,lang.getTranslation("stats.properties.ticketsDeleted"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-reopened",6,lang.getTranslation("stats.properties.ticketsReopened"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-claimed",5,lang.getTranslation("stats.properties.ticketsClaimed"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-pinned",4,lang.getTranslation("stats.properties.ticketsPinned"),0)) - user.add(new api.ODBasicStat("opendiscord:tickets-moved",3,lang.getTranslation("stats.properties.ticketsMoved"),0)) + user.add(new api.ODBasicStat("opendiscord:tickets-created",10,lang.getTranslation("stats.properties.ticketsCreated"),0)) + user.add(new api.ODBasicStat("opendiscord:tickets-closed",9,lang.getTranslation("stats.properties.ticketsClosed"),0)) + user.add(new api.ODBasicStat("opendiscord:tickets-deleted",8,lang.getTranslation("stats.properties.ticketsDeleted"),0)) + user.add(new api.ODBasicStat("opendiscord:tickets-reopened",7,lang.getTranslation("stats.properties.ticketsReopened"),0)) + user.add(new api.ODBasicStat("opendiscord:tickets-claimed",6,lang.getTranslation("stats.properties.ticketsClaimed"),0)) + user.add(new api.ODBasicStat("opendiscord:tickets-pinned",5,lang.getTranslation("stats.properties.ticketsPinned"),0)) + user.add(new api.ODBasicStat("opendiscord:tickets-moved",4,lang.getTranslation("stats.properties.ticketsMoved"),0)) + user.add(new api.ODBasicStat("opendiscord:tickets-transferred",3,"Tickets Transferred",0)) //TODO TRANSLATION!!! user.add(new api.ODBasicStat("opendiscord:users-blacklisted",2,lang.getTranslation("stats.properties.usersBlacklisted"),0)) user.add(new api.ODBasicStat("opendiscord:transcripts-created",1,lang.getTranslation("stats.properties.transcriptsCreated"),0)) user.add(new api.ODDynamicStat("opendiscord:current-tickets",0,async (scopeId,guild,channel,user) => { diff --git a/src/index.ts b/src/index.ts index 39f29fc..d2b5de5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -673,6 +673,7 @@ const main = async () => { await (await import("./commands/autodelete.js")).registerCommandResponders() await (await import("./commands/topic.js")).registerCommandResponders() await (await import("./commands/priority.js")).registerCommandResponders() + await (await import("./commands/transfer.js")).registerCommandResponders() } await opendiscord.events.get("onCommandResponderLoad").emit([opendiscord.responders.commands,opendiscord.responders,opendiscord.actions]) await opendiscord.events.get("afterCommandRespondersLoaded").emit([opendiscord.responders.commands,opendiscord.responders,opendiscord.actions]) @@ -761,6 +762,7 @@ const main = async () => { await (await import("./actions/clearTickets.js")).registerActions() await (await import("./actions/updateTicketTopic.js")).registerActions() await (await import("./actions/updateTicketPriority.js")).registerActions() + await (await import("./actions/transferTicket.js")).registerActions() } await opendiscord.events.get("onActionLoad").emit([opendiscord.actions]) await opendiscord.events.get("afterActionsLoaded").emit([opendiscord.actions])