import {opendiscord, api, utilities} from "../../index" import {Terminal, terminal} from "terminal-kit" import ansis from "ansis" import * as discord from "discord.js" import crypto from "crypto" import {renderHeader, terminate} from "./cli" function generateUniqueIdFromName(name:string){ //id only allows a-z, 0-9 & dash characters (& replace spaces with dashes) const filteredChars = name.toLowerCase().replaceAll(" ","-").split("").filter((ch) => /^[a-zA-Z0-9-]{1}$/.test(ch)) const randomSuffix = "-"+crypto.randomBytes(4).toString("hex") return filteredChars.join("")+randomSuffix } interface ODQuickSetupVariables { client?:api.ODClientManager, guild?:discord.Guild, globalAdmins?:string[], mainColor?:discord.ColorResolvable, language?:string, slashCommands?:boolean, textCommands?:boolean, status?:api.ODJsonConfig_DefaultStatusType, logChannel?:string|null, ticketCategory?:string|null, ticketOptions:({ name:string, description:string, buttonType:"label-emoji"|"emoji"|"label", buttonColor:api.ODValidButtonColor, buttonEmoji:string|null, channelPrefix:string, channelSuffix:api.ODJsonConfig_DefaultOptionTicketChannelType["suffix"] }|null)[], optionIdStorage:string[], autocloseHours?:number|null, cooldownMinutes?:number|null, globalUserLimit?:number|null, removeParticipantsOnClose?:boolean, ticketMessageLayout?:"embed"|"text"|null, emojiStyle?:api.ODJsonConfig_DefaultSystem["emojiStyle"], panelName?:string, panelDescription?:string, panelDropdown?:boolean, panelLayout?:"embed"|"text", panelDescribeOptions?:"simple"|"normal"|"detailed"|null, panelMaxTicketsWarning?:boolean, } const stepCount = (count:number) => "(Step "+count+"/24) " const quickSetupStorage: ODQuickSetupVariables = {ticketOptions:[],optionIdStorage:[]} const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = { style:terminal.white, selectedStyle:terminal.bgBlue.white } const presetColors = new Map([ ["dark red","#992d22"], ["red","#ff0000"], ["light red","#f06c6c"], ["dark orange","#ed510e"], ["orange","#ed6f0e"], ["light orange","#f0b06c"], ["openticket","#f8ba00"], ["dark yellow","#deb100"], ["yellow","#ffff00"], ["light yellow","#ffff8c"], ["banana","#ffe896"], ["lime","#a8e312"], ["dark green","#009600"], ["green","#00ff00"], ["light green","#76f266"], ["dark cyan","#00abab"], ["cyan","#00ffff"], ["light cyan","#63ffff"], ["aquamarine","#7fffd4"], ["dark skyblue","#006bc9"], ["skyblue","#0095ff"], ["light skyblue","#40bfff"], ["dark blue","#00006e"], ["blue","#0000ff"], ["light blue","#5353fc"], ["blurple","#5865F2"], ["dark purple","#3f009e"], ["purple","#8000ff"], ["light purple","#9257eb"], ["dark pink","#b82ab0"], ["pink","#ff6bf8"], ["light pink","#ff9cfa"], ["magenta","#ff00ff"], ["black","#000000"], ["brown","#806050"], ["dark gray","#4f4f4f"], ["gray","#808080"], ["light gray","#b3b3b3"], ["white","#ffffff"], ["invisible","#393A41"] ]) export async function renderQuickSetup(backFn:() => api.ODPromiseVoid){ if (quickSetupRequiresReset()) await renderQuickSetupWarning(backFn) else await renderQuickSetupWelcome(backFn) } function quickSetupRequiresReset(): boolean { const generalConfig = opendiscord.configs.get("opendiscord:general") if (generalConfig.data.token != "your bot token here! (or leave empty when using 'tokenFromENV')") return true if (generalConfig.data.mainColor != "#f8ba00") return true if (generalConfig.data.language != "english") return true if (generalConfig.data.prefix != "!ticket ") return true if (generalConfig.data.serverId != "discord server id") return true return false } async function renderQuickSetupWarning(backFn:() => api.ODPromiseVoid) { renderHeader("ā±ļø Open Ticket Quick Setup: Warning") terminal.bold(ansis.yellow("WARNING! ")+ansis.red("By using the 'Quick Setup' feature, your current config will be completely resetted!")) terminal.gray("\nAre you sure you want to continue?\n") const answer = await terminal.singleColumnMenu([ ansis.green("āœ… No, take me back."), ansis.red("🚨 Yes, continue and reset the config.") ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled || answer.selectedIndex == 0) return await backFn() if (answer.selectedIndex == 1) await renderQuickSetupWelcome(async () => {await renderQuickSetupWarning(backFn)}) } async function renderQuickSetupWelcome(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Introduction") terminal.bold.underline.blue("Open Ticket: Quick Setup\n") terminal.gray([ "Hi there! Thank you for downloading and installing Open Ticket.", "You have chosen to configure the bot using the 'Quick Setup CLI'.", "", "This tool will help you with configuring Open Ticket using a step-by-step method.", ansis.gray("You can "+ansis.red.bold("navigate using the arrow-keys")+" and "+ansis.red.bold("go back using ESC")+"."), "", ansis.magenta("The configuration should normally only take around 6 minutes."), ansis.magenta("Once you've completed the form, the bot is ready for usage!") ].join("\n")+"\n\n") const answer = await terminal.singleColumnMenu([ ansis.green("Press 'Enter' to start!") ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else if (answer.selectedIndex == 0) await renderQuickSetupDevPortal(async () => {await renderQuickSetupWelcome(backFn)}) } async function renderQuickSetupDevPortal(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Discord Bot & Developer Portal") terminal.bold.blue(stepCount(1)+"Have you already created a Discord bot you can use for Open Ticket?\n") const answer = await terminal.singleColumnMenu([ "āœ… Yes I have, and it has been invited to the server.", "ā“ No not yet, I don't know how to create one.", "šŸ‘¶ I've never seen a Discord bot before.", ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else if (answer.selectedIndex == 0) await renderQuickSetupBotToken(async () => {await renderQuickSetupDevPortal(backFn)}) else if (answer.selectedIndex == 1) await renderQuickSetupDevPortalGuide(0,async () => {await renderQuickSetupDevPortal(backFn)}) else if (answer.selectedIndex == 2) await renderQuickSetupDevPortalGuide(1,async () => {await renderQuickSetupDevPortal(backFn)}) } async function renderQuickSetupDevPortalGuide(variation:0|1,backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Discord Bot & Developer Portal") if (variation == 0){ terminal.bold.blue(stepCount(1.1)+"You've mentioned that you don't know how to create a Discord bot.\n\n") terminal.gray("Please visit the following URL for a step-by-step guide on how to create a Discord bot.\nIf it still doesn't work, join our Discord server and we will help you further!\n"+ansis.magenta("=> https://otdocs.dj-dj.be/docs/guides/get-started#bot\n\n")) }else{ terminal.bold.blue(stepCount(1.2)+"You've mentioned that you've never seen Discord bot before.\n\n") terminal.gray("How did you even download Open Ticket 🤪? But all jokes aside, we have a step-by-step guide on how to create a Discord bot.\nIf it still doesn't work, join our Discord server and we will help you further!\n"+ansis.magenta("=> https://otdocs.dj-dj.be/docs/guides/get-started#bot\n\n")) } const answer = await terminal.singleColumnMenu([ ansis.green("āœ… Alright, I've made the bot. Take me back please!") ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise return await backFn() } async function quickSetupLogin(token:string): Promise { const client = new api.ODClientManager(opendiscord.debug) client.token = token client.intents.push("Guilds","GuildMessages","DirectMessages","GuildEmojisAndStickers","GuildMembers","MessageContent","GuildWebhooks","GuildInvites") client.privileges.push("MessageContent","GuildMembers") client.partials.push("Channel","Message") client.permissions.push("AddReactions","AttachFiles","CreatePrivateThreads","CreatePublicThreads","EmbedLinks","ManageChannels","ManageGuild","ManageMessages","ChangeNickname","ManageRoles","ManageThreads","ManageWebhooks","MentionEveryone","ReadMessageHistory","SendMessages","SendMessagesInThreads","UseApplicationCommands","UseExternalEmojis","ViewAuditLog","ViewChannel") client.initClient() //client login return new Promise(async (resolve) => { try{ client.readyListener = async () => { client.activity.setStatus("custom","Configuring Open Ticket...","idle",true) resolve(client) } const success = await client.login(true) if (!success) resolve(null) }catch(err){ resolve(null) } }) } async function renderQuickSetupBotToken(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Bot Token") terminal.bold.blue(stepCount(2)+"Please insert the token of your discord bot.\n") terminal.gray("This is used to configure the bot and is then stored securely in the './config/general.json' file.\n\n> ") const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true }).promise if (typeof answer != "string") return await backFn() else{ const result = await quickSetupLogin(answer) if (!result){ //login failure terminal.red.bold("\n\nāŒ Something went wrong with logging into the bot.\n") terminal.gray("Please try again and check your token for any mistakes.\nAlso make sure the permissions and priviliged gateaway intents are configured correctly in the developer panel.") await utilities.timer(3000) //retry await renderQuickSetupBotToken(backFn) }else{ //login success terminal.green.bold("\n\nāœ… Succesfully logged into the bot.\n") terminal.gray("Your bot should be online with the status 'Configuring Open Ticket...'.") await utilities.timer(3000) //continue quickSetupStorage.client = result await renderQuickSetupServer(async () => {await renderQuickSetupBotToken(backFn)}) } } } async function renderQuickSetupServer(backFn:() => api.ODPromiseVoid){ const {client} = quickSetupStorage if (!client) return renderHeader("ā±ļø Open Ticket Quick Setup: Discord Server") terminal.bold.blue(stepCount(3)+"Please select a Discord Server to use.\n") terminal.gray("The bot will only work in this server.\n\n") const guilds = await client.getGuilds() const nameList = guilds.map((g) => g.name) const longestName = utilities.getLongestLength(nameList) const guildList = guilds.map((g) => g.name.padEnd(longestName+5," ")+ansis.gray(" ("+g.id+")")) const answer = await terminal.singleColumnMenu([ansis.green("šŸ”„ "),...guildList],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return backFn() if (answer.selectedIndex == 0) return await renderQuickSetupServer(backFn) const server = guilds[answer.selectedIndex-1] quickSetupStorage.guild = server await renderQuickSetupAdminRoles([],async () => {await renderQuickSetupServer(backFn)}) } async function renderQuickSetupAdminRoles(selectedAdmins:string[],backFn:() => api.ODPromiseVoid,cachedRoles?:discord.Role[]){ const {client,guild} = quickSetupStorage if (!client || !guild) return renderHeader("ā±ļø Open Ticket Quick Setup: Admin Roles") terminal.bold.blue(stepCount(4)+"Please select all 'Global Admins' roles to use.\n") terminal.gray("Users with one of these roles will be able to access & interact with all tickets.\n\n") const roles = cachedRoles ?? (await guild.roles.fetch()).toJSON().sort((a,b) => b.position-a.position) const nameList = roles.map((r) => r.name) const longestName = utilities.getLongestLength(nameList) const roleList = roles.map((r) => selectedAdmins.includes(r.id) ? ansis.green("(āœ…) "+r.name.padEnd(longestName+5," ")+ansis.gray(" ("+r.id+")")) : r.name.padEnd(longestName+5," ")+ansis.gray(" ("+r.id+")")) const answer = await terminal.singleColumnMenu([ansis.green("šŸ”„ "),ansis.green("šŸ†— "),...roleList],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return backFn() if (answer.selectedIndex == 0) return await renderQuickSetupAdminRoles(selectedAdmins,backFn) if (answer.selectedIndex == 1){ quickSetupStorage.globalAdmins = selectedAdmins return await renderQuickSetupColorPicker(async () => {await renderQuickSetupAdminRoles(selectedAdmins,backFn,cachedRoles)}) } const adminRole = roles[answer.selectedIndex-2] const index = selectedAdmins.findIndex((r) => r == adminRole.id) if (index > -1) selectedAdmins.splice(index,1) else selectedAdmins.push(adminRole.id) return await renderQuickSetupAdminRoles(selectedAdmins,backFn,roles) } async function renderQuickSetupColorPicker(backFn:() => api.ODPromiseVoid){ const {client,guild,globalAdmins} = quickSetupStorage if (!client || !guild || !globalAdmins) return renderHeader("ā±ļø Open Ticket Quick Setup: Main Color") terminal.bold.blue(stepCount(5)+"Please insert a valid hex-color to use in all embeds.\n") terminal.gray("You can also choose from existing presets. (e.g. red, green, blue, ...)\n\n> ") const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true, autoComplete:Array.from(presetColors.keys()), autoCompleteHint:true, autoCompleteMenu:autoCompleteMenuOpts as Terminal.Autocompletion }).promise if (typeof answer != "string") return await backFn() else{ if (!Array.from(presetColors.keys()).includes(answer) && !/^#[0-9a-f]{6}$/.test(answer)){ terminal.red.bold("\n\nāŒ Please insert a valid hex-color or a color from the list. (TIP: use tab for autocomplete)\n") await utilities.timer(2000) return await renderQuickSetupColorPicker(backFn) } let color: discord.ColorResolvable if (Array.from(presetColors.keys()).includes(answer)){ color = presetColors.get(answer) as `#${string}` }else{ color = answer as `#${string}` } quickSetupStorage.mainColor = color return await renderQuickSetupLanguage(async () => {await renderQuickSetupColorPicker(backFn)}) } } async function renderQuickSetupLanguage(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Language") terminal.bold.blue(stepCount(6)+"What language would you like to use in the bot?\n") terminal.gray("View a list of available languages here: https://otgithub.dj-dj.be#-translators\n\n> ") const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true, autoComplete:opendiscord.defaults.getDefault("languageList"), autoCompleteHint:true, autoCompleteMenu:autoCompleteMenuOpts as Terminal.Autocompletion }).promise if (typeof answer != "string") return await backFn() else{ if (!opendiscord.defaults.getDefault("languageList").includes(answer.toLowerCase())){ terminal.red.bold("\n\nāŒ Please insert an available language from the list. (TIP: use tab for autocomplete)\n") await utilities.timer(2000) return await renderQuickSetupLanguage(backFn) } quickSetupStorage.language = answer.toLowerCase() return await renderQuickSetupCommandTypes(async () => {await renderQuickSetupLanguage(backFn)}) } } async function renderQuickSetupCommandTypes(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Command Types") terminal.bold.blue(stepCount(7)+"Would you like to use slash commands, text commands or both?\n") terminal.gray("Slash commands are recommended.\n\n") const answer = await terminal.singleColumnMenu([ "Use Slash Commands", "Use Text Commands", "Use Both Slash & Text Commands", ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else if (answer.selectedIndex == 0){ quickSetupStorage.slashCommands = true quickSetupStorage.textCommands = false }else if (answer.selectedIndex == 1){ quickSetupStorage.slashCommands = false quickSetupStorage.textCommands = true }else if (answer.selectedIndex == 2){ quickSetupStorage.slashCommands = true quickSetupStorage.textCommands = true } return await renderQuickSetupStatusType(async () => {await renderQuickSetupCommandTypes(backFn)}) } async function renderQuickSetupStatusType(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Status Type") terminal.bold.blue(stepCount(8)+"Please select the type of status you want to use.\n") terminal.gray("The status will be shown below the bot name in the userlist.\n\n") const answer = await terminal.singleColumnMenu([ "Disabled", "Custom", "Listening To ...", "Watching ...", "Playing ..." ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else if (answer.selectedIndex == 0) quickSetupStorage.status = {enabled:false,status:"online",type:"custom",text:""} else if (answer.selectedIndex == 1) quickSetupStorage.status = {enabled:true,status:"online",type:"custom",text:""} else if (answer.selectedIndex == 2) quickSetupStorage.status = {enabled:true,status:"online",type:"listening",text:""} else if (answer.selectedIndex == 3) quickSetupStorage.status = {enabled:true,status:"online",type:"watching",text:""} else if (answer.selectedIndex == 4) quickSetupStorage.status = {enabled:true,status:"online",type:"playing",text:""} if (answer.selectedIndex == 0) return await renderQuickSetupLogs(async () => {await renderQuickSetupStatusType(backFn)}) else return await renderQuickSetupStatusText(async () => {await renderQuickSetupStatusType(backFn)}) } async function renderQuickSetupStatusText(backFn:() => api.ODPromiseVoid){ const {status} = quickSetupStorage if (!status) return renderHeader("ā±ļø Open Ticket Quick Setup: Status Text") terminal.bold.blue(stepCount(8.1)+"What text would you like to display in the status?\n") terminal.gray("This will be appended after the type you have chosen in the previous question.\n\n> ") terminal.gray(status.type == "listening" ? "Listening To " : (status.type == "playing" ? "Playing " : (status.type == "watching" ? "Watching " : ""))) const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true }).promise if (typeof answer != "string") return await backFn() else{ status.text = answer return await renderQuickSetupLogs(async () => {await renderQuickSetupStatusText(backFn)}) } } async function renderQuickSetupLogs(backFn:() => api.ODPromiseVoid){ const {client,guild} = quickSetupStorage if (!client || !guild) return renderHeader("ā±ļø Open Ticket Quick Setup: Channel Logs") terminal.bold.blue(stepCount(9)+"Please select the 'Text Channel' to use for logs.\n") terminal.gray("All logs of the bot will be sent here. Make sure only admins can access this channel.\n\n") const rawChannels = (await guild.channels.fetch()).toJSON().filter((c) => c !== null && c.isTextBased()) const channels = rawChannels.sort((a,b) => (a.position + 50*((a.parent?.position ?? -1)+1)) - (b.position + 50*((b.parent?.position ?? -1)+1))) const nameList = channels.map((r) => (r.parent ? r.parent.name+" > " : "")+r.name) const longestName = utilities.getLongestLength(nameList) const channelList = channels.map((r) => ((r.parent ? r.parent.name+" > " : "")+r.name).padEnd(longestName+5," ")+ansis.gray(" ("+r.id+")")) const answer = await terminal.singleColumnMenu([ansis.green("šŸ”„ "),ansis.red("āŒ "),...channelList],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return backFn() else if (answer.selectedIndex == 0) return await renderQuickSetupLogs(backFn) else if (answer.selectedIndex == 1){ quickSetupStorage.logChannel = null return await renderQuickSetupTicketCategory(async () => {await renderQuickSetupLogs(backFn)}) }else{ const logChannel = channels[answer.selectedIndex-2] quickSetupStorage.logChannel = logChannel.id return await renderQuickSetupTicketCategory(async () => {await renderQuickSetupLogs(backFn)}) } } async function renderQuickSetupTicketCategory(backFn:() => api.ODPromiseVoid){ const {client,guild} = quickSetupStorage if (!client || !guild) return renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Category") terminal.bold.blue(stepCount(10)+"Please select which 'Category' you would like tickets to be created in.\n") terminal.gray("When no category is selected, tickets will appear at the top of the channel list.\n\n") const rawCategories = (await guild.channels.fetch()).toJSON().filter((c) => c !== null && c.type == discord.ChannelType.GuildCategory) const categories = rawCategories.sort((a,b) => a.position-b.position) const nameList = categories.map((r) => r.name) const longestName = utilities.getLongestLength(nameList) const categoryList = categories.map((r) => r.name.padEnd(longestName+5," ")+ansis.gray(" ("+r.id+")")) const answer = await terminal.singleColumnMenu([ansis.green("šŸ”„ "),ansis.red("āŒ "),...categoryList],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return backFn() else if (answer.selectedIndex == 0) return await renderQuickSetupTicketCategory(backFn) else if (answer.selectedIndex == 1){ quickSetupStorage.ticketCategory = null return await renderQuickSetupTicketCount(async () => {await renderQuickSetupTicketCategory(backFn)}) }else{ const ticketCategory = categories[answer.selectedIndex-2] quickSetupStorage.ticketCategory = ticketCategory.id return await renderQuickSetupTicketCount(async () => {await renderQuickSetupTicketCategory(backFn)}) } } async function renderQuickSetupTicketCount(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Configuration") terminal.bold.blue(stepCount(11)+"How many ticket options/types would you like to create?\n") terminal.gray("You can always add more ticket options/types in the config afterwards.\n\n") const answer = await terminal.singleColumnMenu([ "1ļøāƒ£ 1 Ticket Option", "2ļøāƒ£ 2 Ticket Options", "3ļøāƒ£ 3 Ticket Options", "4ļøāƒ£ 4 Ticket Options", "5ļøāƒ£ 5 Ticket Options", ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ const ticketCount = answer.selectedIndex+1 quickSetupStorage.ticketOptions = [] for (let i = 0; i < ticketCount; i++){ quickSetupStorage.ticketOptions.push(null) } return await renderQuickSetupCreateTicketName(0,ticketCount,async () => {await renderQuickSetupTicketCount(backFn)}) } } async function renderQuickSetupCreateTicketName(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the name of this ticket option.\n") terminal.gray("Recommendation: Clean, short, obvious name, not more than ±30 characters.\n\n> ") const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true, }).promise if (typeof answer != "string"){ //delete option ticket from list when going back to menu or previous ticket settings quickSetupStorage.ticketOptions[ticketIndex] = null return await backFn() }else if (answer.length == 0){ terminal.red.bold("\n\nāŒ Please insert a valid ticket option name.\n") await utilities.timer(2000) return await renderQuickSetupCreateTicketName(ticketIndex,requiredTickets,backFn) }else{ quickSetupStorage.ticketOptions[ticketIndex] = { name:answer, description:"", buttonType:"label", buttonEmoji:null, buttonColor:"gray", channelPrefix:"ticket-", channelSuffix:"user-name" } return await renderQuickSetupCreateTicketDescription(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketName(ticketIndex,requiredTickets,backFn)}) } } async function renderQuickSetupCreateTicketDescription(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the description of this ticket option.\n") terminal.gray("Recommendation: Use '\\n' (backslash-n) for a newline.\n\n> ") const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true, }).promise if (typeof answer != "string") return await backFn() else{ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex] if (ticketOption) ticketOption.description = answer.replaceAll("\\n","\n") return await renderQuickSetupCreateTicketButtonType(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketDescription(ticketIndex,requiredTickets,backFn)}) } } async function renderQuickSetupCreateTicketButtonType(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) How would you like to display the ticket name in the button/dropdown?\n") terminal.gray("You will be able to choose between dropdown/buttons when configuring panels.\n\n") const answer = await terminal.singleColumnMenu([ "Emoji + Label/Name", "Label/Name Only", "Emoji Only" ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex] if (ticketOption) ticketOption.buttonType = (answer.selectedIndex == 0) ? "label-emoji" : ((answer.selectedIndex == 1) ? "label" : "emoji") if (answer.selectedIndex == 0 || answer.selectedIndex == 2){ return await renderQuickSetupCreateTicketButtonEmoji(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketButtonType(ticketIndex,requiredTickets,backFn)}) }else{ return await renderQuickSetupCreateTicketButtonColor(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketButtonType(ticketIndex,requiredTickets,backFn)}) } } } async function renderQuickSetupCreateTicketButtonEmoji(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the button emoji of this ticket option.\n") terminal.gray("Only 1 emoji allowed. Tip: Insert custom emoji's via the following syntax: <:12345678910:emoji_name>\n\n> ") const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true, }).promise if (typeof answer != "string") return await backFn() else{ //check emoji using a local config checker instance const isEmojiValid = (new api.ODCheckerCustomStructure_EmojiString("opendiscord:emoji-checker",1,1,true)).check(opendiscord.checkers.createTemporaryCheckerEnvironment(),answer,["emoji"]) if (!isEmojiValid){ terminal.red.bold("\n\nāŒ Please insert a valid emoji.\n") await utilities.timer(2000) return await renderQuickSetupCreateTicketButtonEmoji(ticketIndex,requiredTickets,backFn) } const ticketOption = quickSetupStorage.ticketOptions[ticketIndex] if (ticketOption) ticketOption.buttonEmoji = answer return await renderQuickSetupCreateTicketButtonColor(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketButtonEmoji(ticketIndex,requiredTickets,backFn)}) } } async function renderQuickSetupCreateTicketButtonColor(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) What color would you like the button to be?\n") terminal.gray("This will not apply when choosing 'dropdown' mode in the panel configuration.\n\n") const answer = await terminal.singleColumnMenu([ "Gray (Default)", "Blue", "Red", "Green" ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex] if (ticketOption) ticketOption.buttonColor = (answer.selectedIndex == 0) ? "gray" : ((answer.selectedIndex == 1) ? "blue" : ((answer.selectedIndex == 2) ? "red" : "green")) return await renderQuickSetupCreateTicketChannelPrefix(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketButtonType(ticketIndex,requiredTickets,backFn)}) } } async function renderQuickSetupCreateTicketChannelPrefix(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please insert the channel prefix of this ticket option.\n") terminal.gray("Examples: 'ticket-', 'question-', 'test-channel-', ...\n\n> ") const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true, }).promise if (typeof answer != "string") return await backFn() else if (answer.length == 0){ terminal.red.bold("\n\nāŒ Please insert a valid ticket option channel prefix.\n") await utilities.timer(2000) return await renderQuickSetupCreateTicketChannelPrefix(ticketIndex,requiredTickets,backFn) }else{ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex] if (ticketOption) ticketOption.channelPrefix = answer return await renderQuickSetupCreateTicketChannelSuffix(ticketIndex,requiredTickets,async () => {await renderQuickSetupCreateTicketChannelPrefix(ticketIndex,requiredTickets,backFn)}) } } async function renderQuickSetupCreateTicketChannelSuffix(ticketIndex:number,requiredTickets:number,backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Configuration (Ticket "+(ticketIndex+1)+"/"+requiredTickets+")") terminal.bold.blue("("+utilities.ordinalNumber(ticketIndex+1)+" Ticket) Please select the channel suffix mode of this ticket option.\n") terminal.gray("The suffix is appended after the prefix and will be generated on ticket creation.\n\n") const answer = await terminal.singleColumnMenu([ "Username "+ansis.gray("(e.g. #ticket-DJj123dj, #question-wumpus)"), "User Id "+ansis.gray("(e.g. #ticket-123456789, #question-01020304)"), "Random Number "+ansis.gray("(e.g. #ticket-1234, #question-1411)"), "Random Hex "+ansis.gray("(e.g. #ticket-f8ba, #question-01f3)"), "Dynamic Counter "+ansis.gray("(e.g. #ticket-1, #question-23)"), "Fixed Counter "+ansis.gray("(e.g. #ticket-0001, #question-0023)") ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ const ticketOption = quickSetupStorage.ticketOptions[ticketIndex] if (ticketOption){ if (answer.selectedIndex == 0) ticketOption.channelSuffix = "user-name" else if (answer.selectedIndex == 1) ticketOption.channelSuffix = "user-id" else if (answer.selectedIndex == 2) ticketOption.channelSuffix = "random-number" else if (answer.selectedIndex == 3) ticketOption.channelSuffix = "random-hex" else if (answer.selectedIndex == 4) ticketOption.channelSuffix = "counter-dynamic" else if (answer.selectedIndex == 5) ticketOption.channelSuffix = "counter-fixed" } //create next ticket if (ticketIndex+1 < requiredTickets) return await renderQuickSetupCreateTicketName(ticketIndex+1,requiredTickets,async () => {await renderQuickSetupCreateTicketChannelSuffix(ticketIndex,requiredTickets,backFn)}) else return await renderQuickSetupAutoclose(async () => {await renderQuickSetupCreateTicketChannelSuffix(ticketIndex,requiredTickets,backFn)}) } } async function renderQuickSetupAutoclose(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Autoclose") terminal.bold.blue(stepCount(12)+"Would you like to enable autoclosing tickets?\n") terminal.gray("Applies to all created tickets. You can always change/disable autoclose per ticket-option in the config afterwards.\n\n") const answer = await terminal.singleColumnMenu([ ansis.red("āŒ "), "1 Hour Inactivity", "2 Hours Inactivity", "4 Hours Inactivity", "8 Hours Inactivity", "12 Hours Inactivity", "1 Day Inactivity", "2 Days Inactivity", "3 Days Inactivity", ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ if (answer.selectedIndex == 0) quickSetupStorage.autocloseHours = null else quickSetupStorage.autocloseHours = [1,2,4,8,12,24,48,72][answer.selectedIndex-1] return await renderQuickSetupCooldown(async () => {await renderQuickSetupAutoclose(backFn)}) } } async function renderQuickSetupCooldown(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Cooldown") terminal.bold.blue(stepCount(13)+"Would you like to enable ticket creation cooldown?\n") terminal.gray("Applies to all created tickets. You can always change/disable cooldown per ticket-option in the config afterwards.\n\n") const answer = await terminal.singleColumnMenu([ ansis.red("āŒ "), "1 Minute Cooldown", "2 Minutes Cooldown", "5 Minutes Cooldown", "10 Minutes Cooldown", "15 Minutes Cooldown", "30 Minutes Cooldown", "1 Hour Cooldown", "2 Hours Cooldown", "3 Hours Cooldown", ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ if (answer.selectedIndex == 0) quickSetupStorage.cooldownMinutes = null else quickSetupStorage.cooldownMinutes = [1,2,5,10,15,30,60,120,180][answer.selectedIndex-1] return await renderQuickSetupLimits(async () => {await renderQuickSetupCooldown(backFn)}) } } async function renderQuickSetupLimits(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Limits") terminal.bold.blue(stepCount(14)+"Would you like to enable user ticket creation limits?\n") terminal.gray("Applies to all created tickets. You can always change/disable limits globally or per ticket-option in the config afterwards.\n\n") const answer = await terminal.singleColumnMenu([ ansis.red("āŒ "), "Max 1 Ticket/Person", "Max 2 Tickets/Person", "Max 3 Tickets/Person", "Max 5 Tickets/Person", "Max 10 Tickets/Person", "Max 20 Tickets/Person" ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ if (answer.selectedIndex == 0) quickSetupStorage.globalUserLimit = null else quickSetupStorage.globalUserLimit = [1,2,3,5,10,20][answer.selectedIndex-1] return await renderQuickSetupCloseParticipants(async () => {await renderQuickSetupLimits(backFn)}) } } async function renderQuickSetupCloseParticipants(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Close Configuration") terminal.bold.blue(stepCount(15)+"Would you like to remove all ticket participants when closing the ticket?\n") terminal.gray("When a ticket is closed, only admins can read/write in the ticket. Reopen ticket to restore read/write perms.\n\n") const answer = await terminal.singleColumnMenu([ "āŒ No, don't remove ticket participants on close", "āœ… Yes, remove ticket participants on close", ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ quickSetupStorage.removeParticipantsOnClose = (answer.selectedIndex == 1) return await renderQuickSetupTicketMessageLayout(async () => {await renderQuickSetupCloseParticipants(backFn)}) } } async function renderQuickSetupTicketMessageLayout(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Message Configuration") terminal.bold.blue(stepCount(16)+"How would you like the (initial) ticket message to be displayed?\n") terminal.gray("This message is sent by the bot when creating a ticket and contains buttons like closing, claiming & deleting.\n\n") const answer = await terminal.singleColumnMenu([ "šŸ“‹ Embed Message "+ansis.gray("(Default)"), "šŸ’¬ Raw Text Message", ansis.red("āŒ No Message (Not Recommended)") ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ quickSetupStorage.ticketMessageLayout = (answer.selectedIndex == 0) ? "embed" : (answer.selectedIndex == 1) ? "text" : null return await renderQuickSetupEmojiStyle(async () => {await renderQuickSetupTicketMessageLayout(backFn)}) } } async function renderQuickSetupEmojiStyle(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Emoji Style") terminal.bold.blue(stepCount(17)+"How would you like emojis to be displayed in messages?\n") terminal.gray("This will affect emojis in all messages of the bot, but does not apply to buttons & dropdowns.\n\n") const answer = await terminal.singleColumnMenu([ "āœ… Before āŒ (Default) "+ansis.gray("(e.g. šŸŽ« Ticket Created)"), "āŒ After āœ… "+ansis.gray("(e.g. Ticket Created šŸŽ«)"), "āœ… Double āœ… "+ansis.gray("(e.g. šŸŽ« Ticket Created šŸŽ«)"), "āŒ Disabled āŒ "+ansis.gray("(e.g. Ticket Created)"), ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ quickSetupStorage.emojiStyle = (answer.selectedIndex == 0) ? "before" : (answer.selectedIndex == 1) ? "after" : (answer.selectedIndex == 2) ? "double" : "disabled" return await renderQuickSetupPanelName(async () => {await renderQuickSetupEmojiStyle(backFn)}) } } async function renderQuickSetupPanelName(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Panel Name") terminal.bold.blue(stepCount(18)+"Please insert the name of the ticket panel.\n") terminal.gray("This will be shown as the title of the panel message where all tickets are located.\n\n> ") const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true }).promise if (typeof answer != "string") return await backFn() else if (answer.length == 0){ terminal.red.bold("\n\nāŒ Please insert a valid panel name.\n") await utilities.timer(2000) return await renderQuickSetupPanelName(backFn) }else{ quickSetupStorage.panelName = answer return await renderQuickSetupPanelDescription(async () => {await renderQuickSetupPanelName(backFn)}) } } async function renderQuickSetupPanelDescription(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Panel Description") terminal.bold.blue(stepCount(19)+"Please insert the description of the ticket panel.\n") terminal.gray("Shown below the title. Can be used to explain some info/rules about the ticket system.\n\n> ") const answer = await terminal.inputField({ style:terminal.white, hintStyle:terminal.gray, cancelable:true }).promise if (typeof answer != "string") return await backFn() else{ quickSetupStorage.panelDescription = answer return await renderQuickSetupPanelDropdown(async () => {await renderQuickSetupPanelDescription(backFn)}) } } async function renderQuickSetupPanelDropdown(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Panel Mode") terminal.bold.blue(stepCount(20)+"Do you want to show the tickets as buttons or a dropdown?\n") terminal.gray("Dropdown doesn't support colors and cannot contain option types other than 'tickets' (e.g. website/url or reaction roles).\n\n") const answer = await terminal.singleColumnMenu([ "Use Buttons "+ansis.gray("(Recommended with 2 or less ticket options)"), "Use Dropdown "+ansis.gray("(Recommended with 3 or more ticket options)"), ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ quickSetupStorage.panelDropdown = (answer.selectedIndex == 1) return await renderQuickSetupPanelLayout(async () => {await renderQuickSetupPanelDropdown(backFn)}) } } async function renderQuickSetupPanelLayout(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Panel Layout") terminal.bold.blue(stepCount(21)+"How would you like the panel message to be displayed?\n") terminal.gray("Most of the time embeds are used. But for a simpler solution, you can choose the text layout.\n\n") const answer = await terminal.singleColumnMenu([ "šŸ“‹ Embed Message "+ansis.gray("(Default)"), "šŸ’¬ Raw Text Message", ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ quickSetupStorage.panelLayout = (answer.selectedIndex == 0) ? "embed" : "text" return await renderQuickSetupPanelDescribeOptions(async () => {await renderQuickSetupPanelLayout(backFn)}) } } async function renderQuickSetupPanelDescribeOptions(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Panel Option Descriptions") terminal.bold.blue(stepCount(22)+"Would you like the panel to have auto-generated (ticket-)option descriptions?\n") terminal.gray("It will use the 'name' & 'description' of each ticket option and displays it below the panel description.\n\n") const answer = await terminal.singleColumnMenu([ ansis.red("āŒ "), "🟢 Use Simple Option Descriptions", "🟠 Use Normal Option Descriptions "+ansis.gray("(Default)"), "šŸ”“ Use Detailed Option Descriptions", ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ quickSetupStorage.panelDescribeOptions = (answer.selectedIndex == 0) ? null : (answer.selectedIndex == 1) ? "simple" : (answer.selectedIndex == 2) ? "normal" : "detailed" return await renderQuickSetupPanelMaxTicketsWarning(async () => {await renderQuickSetupPanelDescribeOptions(backFn)}) } } async function renderQuickSetupPanelMaxTicketsWarning(backFn:() => api.ODPromiseVoid){ renderHeader("ā±ļø Open Ticket Quick Setup: Ticket Close Configuration") terminal.bold.blue(stepCount(23)+"Would you like to show the maximum amount of tickets a user can create in the panel?\n") terminal.gray("This will show the amount of tickets a user can create at the same time when limits are enabled.\n\n") const answer = await terminal.singleColumnMenu([ "āŒ No, don't show the max tickets warning in the panel", "āœ… Yes, show the max tickets warning in the panel", ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise if (answer.canceled) return await backFn() else{ quickSetupStorage.panelMaxTicketsWarning = (answer.selectedIndex == 1) return await renderQuickSetupReady(async () => {await renderQuickSetupPanelMaxTicketsWarning(backFn)}) } } async function renderQuickSetupReady(backFn:() => api.ODPromiseVoid){ renderHeader("šŸ˜Ž Open Ticket Quick Setup: Overview") terminal.bold.blue(stepCount(24)+"This is the overview of your ticket bot configuration!\n") terminal.gray("Press 'Enter' to save the result to the config.\n\n") const commands = ((quickSetupStorage.slashCommands && quickSetupStorage.textCommands) ? "Slash & Text" : (quickSetupStorage.slashCommands) ? "Slash Only" : "Text Only") const statusText = (quickSetupStorage.status?.type == "listening" ? "Listening To " : (quickSetupStorage.status?.type == "playing" ? "Playing " : (quickSetupStorage.status?.type == "watching" ? "Watching " : ""))) + quickSetupStorage.status?.text const status = (quickSetupStorage.status?.enabled) ? statusText : "Disabled" terminal([ ansis.bold.hex("#f8ba00")("Client: ")+(quickSetupStorage.client?.client.user.displayName ?? "?"), ansis.bold.hex("#f8ba00")("Status: ")+status, ansis.bold.hex("#f8ba00")("Server: ")+(quickSetupStorage.guild?.name ?? "?"), ansis.bold.hex("#f8ba00")("Admins: ")+(quickSetupStorage.globalAdmins?.length ?? "?")+" Admins", ansis.bold.hex("#f8ba00")("Color: ")+(quickSetupStorage.mainColor ?? "?"), ansis.bold.hex("#f8ba00")("Language: ")+(quickSetupStorage.language ?? "?"), ansis.bold.hex("#f8ba00")("Commands: ")+commands, ansis.bold.hex("#f8ba00")("Options: ")+quickSetupStorage.ticketOptions.length+" Tickets", ansis.bold.hex("#f8ba00")("Autoclose: ")+(quickSetupStorage.autocloseHours ? quickSetupStorage.autocloseHours+" Hours" : "Disabled"), ansis.bold.hex("#f8ba00")("Cooldown: ")+(quickSetupStorage.cooldownMinutes ? quickSetupStorage.cooldownMinutes+" Minutes" : "Disabled"), ansis.bold.hex("#f8ba00")("Limits: ")+(quickSetupStorage.globalUserLimit ? quickSetupStorage.globalUserLimit+" Tickets/Person" : "Disabled"), ansis.bold.gray("+ 13 More Settings ..."), ].join("\n")+"\n\n") const answer = await terminal.singleColumnMenu([ ansis.green("Press 'Enter' to save configuration!") ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise //save configuration if (answer.canceled) return await backFn() else if (answer.selectedIndex == 0){ await saveQuickSetupConfig() return await renderQuickSetupFinished() } } async function renderQuickSetupFinished(){ renderHeader("āœ… Open Ticket Quick Setup: Ready") terminal.bold.green("The config has been saved succesfully and the bot is now ready for usage!\n") terminal.gray("Press 'Enter' to exit the Quick Setup CLI.\n\n") terminal(ansis.gray([ "Start the bot using the following command:", ansis.blue("------------------------------"), ansis.magenta.bold("> npm start"), ansis.blue("------------------------------"), "", "Edit the existing config in the CLI or directly in the (./config/) directory:", ansis.blue("------------------------------"), ansis.magenta.bold("> npm run setup"), ansis.blue("------------------------------"), "", ansis.yellow.bold("⭐ Don't forget to star our Github repository when you enjoy using the bot! ⭐"), ansis.gray("Join our discord server "+ansis.hex("#5865F2").bold.underline("https://discord.dj-dj.be")+" when you need help with troubleshooting."), ].join("\n"))+"\n\n") await terminal.singleColumnMenu([ ansis.green("Press 'Enter' to exit Quick Setup CLI!") ],{ leftPadding:"> ", style:terminal.cyan, selectedStyle:terminal.bgDefaultColor.bold, submittedStyle:terminal.bgBlue, extraLines:2, cancelable:true }).promise //stop CLI return await terminate() } async function saveQuickSetupConfig(){ //GENERAL CONFIG const generalConfig = opendiscord.configs.get("opendiscord:general") const generalConfigData: api.ODJsonConfig_DefaultGeneralData = { _INFO:{ support:"https://otdocs.dj-dj.be", discord:"https://discord.dj-dj.be", version:"open-ticket-v4.0.6" }, token:quickSetupStorage.client?.token ?? "", tokenFromENV:false, mainColor:quickSetupStorage.mainColor ?? "#f8ba00", language:quickSetupStorage.language ?? "english", prefix:"!ticket ", serverId:quickSetupStorage.guild?.id ?? "", globalAdmins:quickSetupStorage.globalAdmins ?? [], slashCommands:quickSetupStorage.slashCommands ?? false, textCommands:quickSetupStorage.textCommands ?? false, status:quickSetupStorage.status ?? {enabled:false,status:"online",text:"",type:"custom"}, system:{ removeParticipantsOnClose:quickSetupStorage.removeParticipantsOnClose ?? false, replyOnTicketCreation:false, replyOnReactionRole:true, useTranslatedConfigChecker:true, preferSlashOverText:quickSetupStorage.slashCommands ?? false, sendErrorOnUnknownCommand:true, questionFieldsInCodeBlock:true, disableVerifyBars:false, useRedErrorEmbeds:true, emojiStyle:quickSetupStorage.emojiStyle ?? "before", enableTicketClaimButtons:true, enableTicketCloseButtons:true, enableTicketPinButtons:true, enableTicketDeleteButtons:true, enableTicketActionWithReason:true, enableDeleteWithoutTranscript:true, logs:{ enabled:(typeof quickSetupStorage.logChannel == "string"), channel:quickSetupStorage.logChannel ?? "" }, limits:{ enabled:(typeof quickSetupStorage.globalUserLimit == "number"), globalMaximum:100, userMaximum:quickSetupStorage.globalUserLimit ?? 3 }, permissions:{ help:"everyone", panel:"admin", ticket:"everyone", close:"admin", delete:"admin", reopen:"admin", claim:"admin", unclaim:"admin", pin:"admin", unpin:"admin", move:"admin", rename:"admin", add:"admin", remove:"admin", blacklist:"admin", stats:"everyone", clear:"admin", autoclose:"admin", autodelete:"admin" }, messages:{ creation:{dm:true,logs:true}, closing:{dm:true,logs:true}, deleting:{dm:true,logs:true}, reopening:{dm:false,logs:true}, claiming:{dm:false,logs:true}, pinning:{dm:false,logs:true}, adding:{dm:false,logs:true}, removing:{dm:false,logs:true}, renaming:{dm:false,logs:true}, moving:{dm:true,logs:true}, blacklisting:{dm:true,logs:true}, roleAdding:{dm:false,logs:true}, roleRemoving:{dm:false,logs:true} } } } generalConfig.data = generalConfigData await generalConfig.save() //QUESTIONS CONFIG => no configuration needed (coming soonTM) const questionsConfig = opendiscord.configs.get("opendiscord:questions") const questionsConfigData: api.ODJsonConfig_DefaultQuestionsData = [ { id:"example-question-1", name:"Example Question 1", type:"short", required:true, placeholder:"Insert your short answer here!", length:{ enabled:false, min:0, max:1000 } }, { id:"example-question-2", name:"Example Question 2", type:"paragraph", required:false, placeholder:"Insert your long answer here!", length:{ enabled:false, min:0, max:1000 } } ] questionsConfig.data = questionsConfigData await questionsConfig.save() //OPTIONS CONFIG const optionsConfig = opendiscord.configs.get("opendiscord:options") const optionsConfigData: api.ODJsonConfig_DefaultOptionsData = quickSetupStorage.ticketOptions.filter((ticket) => ticket !== null).map((ticket) => { const id = generateUniqueIdFromName(ticket.name) quickSetupStorage.optionIdStorage.push(id) return { id:id, name:ticket.name, description:ticket.description, type:"ticket", button:{ emoji:(ticket.buttonType == "label") ? "" : (ticket.buttonEmoji ?? ""), label:(ticket.buttonType == "emoji") ? "" : ticket.name, color:ticket.buttonColor ?? "gray" }, ticketAdmins:[], readonlyAdmins:[], allowCreationByBlacklistedUsers:false, questions:[], channel:{ prefix:ticket.channelPrefix, suffix:ticket.channelSuffix, category:quickSetupStorage.ticketCategory ?? "", closedCategory:"", backupCategory:"", claimedCategory:[], description:ticket.description }, dmMessage:{ enabled:false, text:"", embed:{ enabled:false, title:"", description:"", customColor:"", image:"", thumbnail:"", fields:[], timestamp:false } }, ticketMessage:{ enabled:true, text:"", embed:{ enabled:true, title:ticket.name, description:ticket.description, customColor:"", image:"", thumbnail:"", fields:[], timestamp:true }, ping:{ "@here":true, "@everyone":false, custom:[] } }, autoclose:{ enableInactiveHours:(typeof quickSetupStorage.autocloseHours == "number"), inactiveHours:quickSetupStorage.autocloseHours ?? 24, enableUserLeave:true, disableOnClaim:false }, autodelete:{ enableInactiveDays:false, inactiveDays:7, enableUserLeave:false, disableOnClaim:false }, cooldown:{ enabled:(typeof quickSetupStorage.cooldownMinutes == "number"), cooldownMinutes:quickSetupStorage.cooldownMinutes ?? 10 }, limits:{ enabled:false, globalMaximum:20, userMaximum:3 } } }) optionsConfig.data = optionsConfigData await optionsConfig.save() //PANELS CONFIG const panelsConfig = opendiscord.configs.get("opendiscord:panels") const panelsConfigData: api.ODJsonConfig_DefaultPanelsData = [ { id:generateUniqueIdFromName(quickSetupStorage.panelName ?? "ticket-panel"), name:quickSetupStorage.panelName ?? "Ticket Panel", dropdown:quickSetupStorage.panelDropdown ?? false, options:quickSetupStorage.optionIdStorage, text:(quickSetupStorage.panelLayout == "text") ? (quickSetupStorage.panelDescription ?? "") : "", embed:{ enabled:true, title:quickSetupStorage.panelName ?? "Ticket Panel", description:(quickSetupStorage.panelLayout == "embed") ? (quickSetupStorage.panelDescription ?? "") : "", customColor:"", url:"", image:"", thumbnail:"", footer:quickSetupStorage.guild?.name ?? "", fields:[], timestamp:false }, settings:{ dropdownPlaceholder:"Open a ticket", enableMaxTicketsWarningInText:(quickSetupStorage.panelLayout == "text" && (quickSetupStorage.panelMaxTicketsWarning ?? false)), enableMaxTicketsWarningInEmbed:(quickSetupStorage.panelLayout == "embed" && (quickSetupStorage.panelMaxTicketsWarning ?? false)), describeOptionsLayout:quickSetupStorage.panelDescribeOptions ?? "normal", describeOptionsCustomTitle:"", describeOptionsInText:(quickSetupStorage.panelLayout == "text" && typeof quickSetupStorage.panelDescribeOptions == "string"), describeOptionsInEmbedFields:(quickSetupStorage.panelLayout == "embed" && typeof quickSetupStorage.panelDescribeOptions == "string"), describeOptionsInEmbedDescription:false } } ] panelsConfig.data = panelsConfigData await panelsConfig.save() //TRANSCRIPTS CONFIG => no configuration needed (coming soonTM) const transcriptsConfig = opendiscord.configs.get("opendiscord:transcripts") const transcriptsConfigData: api.ODJsonConfig_DefaultTranscriptsData = { general:{ enabled:(typeof quickSetupStorage.logChannel == "string"), enableChannel:true, enableCreatorDM:true, enableParticipantDM:false, enableActiveAdminDM:false, enableEveryAdminDM:false, channel:quickSetupStorage.logChannel ?? "", mode:"html" }, embedSettings:{ customColor:"", listAllParticipants:false, includeTicketStats:false }, textTranscriptStyle:{ layout:"normal", includeStats:true, includeIds:false, includeEmbeds:true, includeFiles:true, includeBotMessages:true, fileMode:"channel-name", customFileName:"transcript" }, htmlTranscriptStyle:{ background:{ enableCustomBackground:false, backgroundColor:"", backgroundImage:"" }, header:{ enableCustomHeader:false, backgroundColor:"#202225", decoColor:"#f8ba00", textColor:"#ffffff" }, stats:{ enableCustomStats:false, backgroundColor:"#202225", keyTextColor:"#737373", valueTextColor:"#ffffff", hideBackgroundColor:"#40444a", hideTextColor:"#ffffff" }, favicon:{ enableCustomFavicon:false, imageUrl:"https://t.dj-dj.be/favicon.png" } } } transcriptsConfig.data = transcriptsConfigData await transcriptsConfig.save() } /** Steps Todo * ALREADY FINISHED: * - Ticket configuration => per-ticket configuration * - (option ID autogenerated from name BE AWARE OF TICKETS WITH SAME NAME!! + remove unicode, spaces & special chars from id) * - (embed autofilled with name+desc+color, thumbnail will automatically be set to the server icon) * - (ping will be @here) */