diff --git a/src/core/api/defaults/config.ts b/src/core/api/defaults/config.ts index af308a6..008ab00 100644 --- a/src/core/api/defaults/config.ts +++ b/src/core/api/defaults/config.ts @@ -33,9 +33,9 @@ import { ODRoleUpdateMode } from "../openticket/role" */ export interface ODConfigManagerIds_Default { "opendiscord:general":ODJsonConfig_DefaultGeneral, + "opendiscord:questions":ODJsonConfig_DefaultQuestions, "opendiscord:options":ODJsonConfig_DefaultOptions, "opendiscord:panels":ODJsonConfig_DefaultPanels, - "opendiscord:questions":ODJsonConfig_DefaultQuestions, "opendiscord:transcripts":ODJsonConfig_DefaultTranscripts } @@ -239,7 +239,7 @@ export interface ODJsonConfig_DefaultGeneralData { tokenFromENV:boolean, /**The main (hex) color used in almost every embed in the bot. */ - mainColor:discord.ColorResolvable, + mainColor:discord.ColorResolvable|string, /**The language to use. Can be the id of the language or the id without the prefix when using `opendiscord:...`. */ language:string, /**The prefix used in all text-commands. */ @@ -315,7 +315,7 @@ export interface ODJsonConfig_DefaultOptionEmbedSettingsType { /**The description of this embed. */ description:string, /**A custom color for this embed. (The default bot color is used when empty) */ - customColor:discord.ColorResolvable, + customColor:discord.ColorResolvable|string, /**A URL to an image displayed in the embed. */ image:string, @@ -471,6 +471,11 @@ export interface ODJsonConfig_DefaultOptionRoleType extends ODJsonConfig_Default addOnMemberJoin:boolean } +/**## ODJsonConfig_DefaultOptionsData `type` + * All contents of the `options.json` config file. + */ +export type ODJsonConfig_DefaultOptionsData = (ODJsonConfig_DefaultOptionTicketType|ODJsonConfig_DefaultOptionWebsiteType|ODJsonConfig_DefaultOptionRoleType)[] + /**## ODJsonConfig_DefaultOptions `default_class` * This is a special class that adds type definitions & typescript to the ODJsonConfig class. * It doesn't add any extra features! @@ -478,11 +483,7 @@ export interface ODJsonConfig_DefaultOptionRoleType extends ODJsonConfig_Default * This default class is made for the `options.json` config! */ export class ODJsonConfig_DefaultOptions extends ODJsonConfig { - declare data: ( - ODJsonConfig_DefaultOptionTicketType| - ODJsonConfig_DefaultOptionWebsiteType| - ODJsonConfig_DefaultOptionRoleType - )[] + declare data: ODJsonConfig_DefaultOptionsData } /**## ODJsonConfig_DefaultPanelEmbedSettingsType `interface` @@ -497,7 +498,7 @@ export interface ODJsonConfig_DefaultPanelEmbedSettingsType { description:string, /**A custom color for this embed. (The default bot color is used when empty) */ - customColor:discord.ColorResolvable, + customColor:discord.ColorResolvable|string, /**An optional URL used in the title of the embed. */ url:string, @@ -565,6 +566,11 @@ export interface ODJsonConfig_DefaultPanelType { settings:ODJsonConfig_DefaultPanelSettingsType } +/**## ODJsonConfig_DefaultPanelsData `type` + * All contents of the `panels.json` config file. + */ +export type ODJsonConfig_DefaultPanelsData = ODJsonConfig_DefaultPanelType[] + /**## ODJsonConfig_DefaultPanels `default_class` * This is a special class that adds type definitions & typescript to the ODJsonConfig class. * It doesn't add any extra features! @@ -572,7 +578,7 @@ export interface ODJsonConfig_DefaultPanelType { * This default class is made for the `panels.json` config! */ export class ODJsonConfig_DefaultPanels extends ODJsonConfig { - declare data: ODJsonConfig_DefaultPanelType[] + declare data: ODJsonConfig_DefaultPanelsData } /**## ODJSonConfig_DefaultQuestionLengthSettings `interface` @@ -625,6 +631,11 @@ export interface ODJsonConfig_DefaultParagraphQuestionType { length:ODJSonConfig_DefaultQuestionLengthSettings } +/**## ODJsonConfig_DefaultQuestionsData `type` + * All contents of the `questions.json` config file. + */ +export type ODJsonConfig_DefaultQuestionsData = (ODJsonConfig_DefaultShortQuestionType|ODJsonConfig_DefaultParagraphQuestionType)[] + /**## ODJsonConfig_DefaultQuestions `default_class` * This is a special class that adds type definitions & typescript to the ODJsonConfig class. * It doesn't add any extra features! @@ -632,10 +643,7 @@ export interface ODJsonConfig_DefaultParagraphQuestionType { * This default class is made for the `questions.json` config! */ export class ODJsonConfig_DefaultQuestions extends ODJsonConfig { - declare data: ( - ODJsonConfig_DefaultShortQuestionType| - ODJsonConfig_DefaultParagraphQuestionType - )[] + declare data: ODJsonConfig_DefaultQuestionsData } /**## ODJsonConfig_DefaultTranscriptsTextLayout `interface` @@ -709,6 +717,47 @@ export interface ODJsonConfig_DefaultTranscriptsHtmlLayout { } } +/**## ODJsonConfig_DefaultTranscriptsData `interface` + * All contents of the `transcripts.json` config file. + */ +export interface ODJsonConfig_DefaultTranscriptsData { + /**All general settings related to transcripts. */ + general:{ + /**Are transcripts enabled? */ + enabled:boolean, + + /**Enable sending the generated transcript in a channel. */ + enableChannel:boolean, + /**Enable sending the generated transcript to the DM of the ticket creator. */ + enableCreatorDM:boolean, + /**Enable sending the generated transcript to the DM of the participants. */ + enableParticipantDM:boolean, + /**Enable sending the generated transcript to the DM of all admins which were active in the ticket. */ + enableActiveAdminDM:boolean, + /**Enable sending the generated transcript to the DM of all admins which were assigned to the ticket. */ + enableEveryAdminDM:boolean, + + /**A discord channel id for the `"enableChannel"` setting. */ + channel:string, + /**Want to use text or HTML transcripts? */ + mode:"html"|"text" + }, + /**All settings related to the embed from the transcripts. (UNIMPLEMENTED!!) */ + embedSettings:{ + /**Unimplemented feature */ + customColor:discord.ColorResolvable|string, + /**Unimplemented feature */ + listAllParticipants:boolean, + /**Unimplemented feature */ + includeTicketStats:boolean + }, + /**The layout of the text transcripts. */ + textTranscriptStyle:ODJsonConfig_DefaultTranscriptsTextLayout, + /**The layout of the HTML transcripts. */ + htmlTranscriptStyle:ODJsonConfig_DefaultTranscriptsHtmlLayout +} + + /**## ODJsonConfig_DefaultTranscripts `default_class` * This is a special class that adds type definitions & typescript to the ODJsonConfig class. * It doesn't add any extra features! @@ -716,40 +765,5 @@ export interface ODJsonConfig_DefaultTranscriptsHtmlLayout { * This default class is made for the `transcripts.json` config! */ export class ODJsonConfig_DefaultTranscripts extends ODJsonConfig { - declare data: { - /**All general settings related to transcripts. */ - general:{ - /**Are transcripts enabled? */ - enabled:boolean, - - /**Enable sending the generated transcript in a channel. */ - enableChannel:boolean, - /**Enable sending the generated transcript to the DM of the ticket creator. */ - enableCreatorDM:boolean, - /**Enable sending the generated transcript to the DM of the participants. */ - enableParticipantDM:boolean, - /**Enable sending the generated transcript to the DM of all admins which were active in the ticket. */ - enableActiveAdminDM:boolean, - /**Enable sending the generated transcript to the DM of all admins which were assigned to the ticket. */ - enableEveryAdminDM:boolean, - - /**A discord channel id for the `"enableChannel"` setting. */ - channel:string, - /**Want to use text or HTML transcripts? */ - mode:"html"|"text" - }, - /**All settings related to the embed from the transcripts. (UNIMPLEMENTED!!) */ - embedSettings:{ - /**Unimplemented feature */ - customColor:discord.ColorResolvable, - /**Unimplemented feature */ - listAllParticipants:boolean, - /**Unimplemented feature */ - includeTicketStats:boolean - }, - /**The layout of the text transcripts. */ - textTranscriptStyle:ODJsonConfig_DefaultTranscriptsTextLayout, - /**The layout of the HTML transcripts. */ - htmlTranscriptStyle:ODJsonConfig_DefaultTranscriptsHtmlLayout - } + declare data: ODJsonConfig_DefaultTranscriptsData } \ No newline at end of file diff --git a/src/core/api/modules/builder.ts b/src/core/api/modules/builder.ts index dfb05ea..f864f2a 100644 --- a/src/core/api/modules/builder.ts +++ b/src/core/api/modules/builder.ts @@ -836,7 +836,7 @@ export interface ODEmbedData { /**The title of the embed */ title:string|null, /**The color of the embed */ - color:discord.ColorResolvable|null, + color:discord.ColorResolvable|string|null, /**The url of the embed */ url:string|null, /**The description of the embed */ @@ -994,7 +994,7 @@ export class ODEmbed extends ODBuilderImplementati //create the discord.js embed const embed = new discord.EmbedBuilder() if (instance.data.title) embed.setTitle(instance.data.title) - if (instance.data.color) embed.setColor(instance.data.color) + if (instance.data.color) embed.setColor(instance.data.color as discord.ColorResolvable) if (instance.data.url) embed.setURL(instance.data.url) if (instance.data.description) embed.setDescription(instance.data.description) if (instance.data.authorText) embed.setAuthor({ @@ -1047,7 +1047,7 @@ export class ODQuickEmbed { //create the discord.js embed const embed = new discord.EmbedBuilder() if (this.data.title) embed.setTitle(this.data.title) - if (this.data.color) embed.setColor(this.data.color) + if (this.data.color) embed.setColor(this.data.color as discord.ColorResolvable) if (this.data.url) embed.setURL(this.data.url) if (this.data.description) embed.setDescription(this.data.description) if (this.data.authorText) embed.setAuthor({ diff --git a/src/core/cli/quickSetup.ts b/src/core/cli/quickSetup.ts index 3b21d14..d791b67 100644 --- a/src/core/cli/quickSetup.ts +++ b/src/core/cli/quickSetup.ts @@ -2,7 +2,15 @@ import {opendiscord, api, utilities} from "../../index" import {Terminal, terminal} from "terminal-kit" import ansis from "ansis" import * as discord from "discord.js" -import {renderHeader} from "./cli" +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.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, @@ -24,6 +32,7 @@ interface ODQuickSetupVariables { channelPrefix:string, channelSuffix:api.ODJsonConfig_DefaultOptionTicketChannelType["suffix"] }|null)[], + optionIdStorage:string[], autocloseHours?:number|null, cooldownMinutes?:number|null, globalUserLimit?:number|null, @@ -39,7 +48,7 @@ interface ODQuickSetupVariables { } const stepCount = (count:number) => "(Step "+count+"/24) " -const quickSetupStorage: ODQuickSetupVariables = {ticketOptions:[]} +const quickSetupStorage: ODQuickSetupVariables = {ticketOptions:[],optionIdStorage:[]} const autoCompleteMenuOpts: Terminal.SingleLineMenuOptions = { style:terminal.white, selectedStyle:terminal.bgBlue.white @@ -1150,11 +1159,372 @@ async function renderQuickSetupReady(backFn:() => api.ODPromiseVoid){ //save configuration if (answer.canceled) return await backFn() - else if (answer.selectedIndex == 0) return await saveQuickSetupConfig() + 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(){ - console.log(quickSetupStorage) + //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