diff --git a/src/core/cli/cli.ts b/src/core/cli/cli.ts new file mode 100644 index 0000000..6fcb2ce --- /dev/null +++ b/src/core/cli/cli.ts @@ -0,0 +1,75 @@ +import {opendiscord, api, utilities} from "../../index" +import {Terminal, terminal} from "terminal-kit" +import ansis from "ansis" + +const logo = [ + " ██████╗ ██████╗ ███████╗███╗ ██╗ ████████╗██╗ ██████╗██╗ ██╗███████╗████████╗ ", + " ██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝ ", + " ██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ ██║██║ █████╔╝ █████╗ ██║ ", + " ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ ", + " ╚██████╔╝██║ ███████╗██║ ╚████║ ██║ ██║╚██████╗██║ ██╗███████╗ ██║ ", + " ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ " +] + +/**A utility function to center text to a certain width. */ +export function centerText(text:string,width:number){ + if (width < text.length) return text + let newWidth = width-ansis.strip(text).length+1 + let final = " ".repeat(newWidth/2)+text + return final +} + +/**A utility function to terminate the interactive CLI. */ +export async function terminate(){ + terminal.grabInput(false) + terminal.clear() + terminal.green("👋 Exited the Open Ticket Interactive Setup CLI.\n") + process.exit(0) +} +terminal.on("key",(name,matches,data) => { + if (name == "CTRL_C") terminate() +}) + +/**Render the header of the interactive CLI. */ +export function renderHeader(path:(string|number)[]|string){ + terminal.grabInput(true) + terminal.clear().moveTo(1,1) + terminal(ansis.hex("#f8ba00")(logo.join("\n")+"\n")) + terminal.bold(centerText("Interactive Setup CLI - Version: "+opendiscord.versions.get("opendiscord:version").toString()+" - Support: https://discord.dj-dj.be\n",88)) + if (typeof path == "string") terminal.cyan(centerText(path+"\n\n",88)) + else if (path.length < 1) terminal.cyan(centerText("👋 Hi! Welcome to the Open Ticket Interactive Setup CLI! 👋\n\n",88)) + else terminal.cyan(centerText("🌐 Current Location: "+path.map((v,i) => { + if (i == 0) return v.toString() + else if (typeof v == "string") return ".\""+v+"\"" + else if (typeof v == "number") return "."+v + }).join("")+"\n\n",88)) +} + +async function renderCliModeSelector(backFn:(() => api.ODPromiseVoid)){ + renderHeader([]) + terminal(ansis.bold.green("Please select what CLI module you want to use.\n")+ansis.italic.gray("(use arrow keys to navigate, exit using escape)\n")) + + const answer = await terminal.singleColumnMenu([ + "✏️ Edit Config "+ansis.gray("=> Edit the current config, add/remove new tickets/questions/panels & more!"), + "⏱️ Quick Setup "+ansis.gray("=> A quick and easy way of setting up the bot in your Discord server."), + ],{ + 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 (await import("./editConfig.js")).renderEditConfig(async () => {await renderCliModeSelector(backFn)}) + else if (answer.selectedIndex == 1) await (await import("./quickSetup.js")).renderQuickSetup(async () => {await renderCliModeSelector(backFn)}) +} + +export async function execute(){ + if (terminal.width < 100 || terminal.height < 35){ + terminal(ansis.red.bold("\n\nMake sure your console or cmd window has a "+ansis.cyan("minimum width & height")+" of "+ansis.cyan("100x35")+" characters.")) + terminal(ansis.red.bold("\nOtherwise the Open Ticket Interactive Setup CLI will be rendered incorrectly.")) + terminal(ansis.red.bold("\nThe current terminal dimensions are: "+ansis.cyan(terminal.width+"x"+terminal.height)+".")) + }else await renderCliModeSelector(terminate) +} \ No newline at end of file diff --git a/src/core/startup/cli.ts b/src/core/cli/editConfig.ts similarity index 94% rename from src/core/startup/cli.ts rename to src/core/cli/editConfig.ts index 6cc6331..9eebf29 100644 --- a/src/core/startup/cli.ts +++ b/src/core/cli/editConfig.ts @@ -1,50 +1,9 @@ import {opendiscord, api, utilities} from "../../index" import {Terminal, terminal} from "terminal-kit" import ansis from "ansis" +import {renderHeader} from "./cli" -const logo = [ - " ██████╗ ██████╗ ███████╗███╗ ██╗ ████████╗██╗ ██████╗██╗ ██╗███████╗████████╗ ", - " ██╔═══██╗██╔══██╗██╔════╝████╗ ██║ ╚══██╔══╝██║██╔════╝██║ ██╔╝██╔════╝╚══██╔══╝ ", - " ██║ ██║██████╔╝█████╗ ██╔██╗ ██║ ██║ ██║██║ █████╔╝ █████╗ ██║ ", - " ██║ ██║██╔═══╝ ██╔══╝ ██║╚██╗██║ ██║ ██║██║ ██╔═██╗ ██╔══╝ ██║ ", - " ╚██████╔╝██║ ███████╗██║ ╚████║ ██║ ██║╚██████╗██║ ██╗███████╗ ██║ ", - " ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═╝ " -] - -/**A utility function to center text to a certain width. */ -function centerText(text:string,width:number){ - if (width < text.length) return text - let newWidth = width-ansis.strip(text).length+1 - let final = " ".repeat(newWidth/2)+text - return final -} - -/**A utility function to terminate the interactive CLI. */ -async function terminate(){ - terminal.grabInput(false) - terminal.clear() - terminal.green("👋 Exited the Open Ticket Interactive Setup CLI.\n") - process.exit(0) -} -terminal.on("key",(name,matches,data) => { - if (name == "CTRL_C") terminate() -}) - -/**Render the header of the interactive CLI. */ -function renderHeader(path:(string|number)[]){ - terminal.grabInput(true) - terminal.clear().moveTo(1,1) - terminal(ansis.hex("#f8ba00")(logo.join("\n")+"\n")) - terminal.bold(centerText("Interactive Setup CLI - Version: "+opendiscord.versions.get("opendiscord:version").toString()+" - Support: https://discord.dj-dj.be\n",88)) - if (path.length < 1) terminal.cyan(centerText("👋 Hi! Welcome to the Open Ticket Interactive Setup CLI! 👋\n\n",88)) - else terminal.cyan(centerText("🌐 Current Location: "+path.map((v,i) => { - if (i == 0) return v.toString() - else if (typeof v == "string") return ".\""+v+"\"" - else if (typeof v == "number") return "."+v - }).join("")+"\n\n",88)) -} - -async function renderConfigSelector(backFn:(() => api.ODPromiseVoid)){ +export async function renderEditConfig(backFn:(() => api.ODPromiseVoid)){ renderHeader([]) terminal(ansis.bold.green("Please select which config you would like to edit.\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) @@ -65,7 +24,7 @@ async function renderConfigSelector(backFn:(() => api.ODPromiseVoid)){ if (answer.canceled) return await backFn() const checker = checkerList[answer.selectedIndex] const configData = checker.config.data as api.ODValidJsonType - await chooseConfigStructure(checker,async () => {await renderConfigSelector(backFn)},checker.structure,configData,{},NaN,["("+checker.config.path+")"]) + await chooseConfigStructure(checker,async () => {await renderEditConfig(backFn)},checker.structure,configData,{},NaN,["("+checker.config.path+")"]) } async function chooseConfigStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),structure:api.ODCheckerStructure,data:api.ODValidJsonType,parent:object|any[],parentIndex:string|number,path:(string|number)[]){ @@ -938,7 +897,6 @@ async function renderAdditionConfigArrayStructureSelector(checker:api.ODChecker, else if (answer.selectedIndex == 5) await renderConfigArrayStructureDuplicateSelector(checker,backFnFunc,structure,structure.options.propertyChecker,localData,parent,parentIndex,path) } - async function renderAdditionConfigTypeSwitchStructure(checker:api.ODChecker,backFn:(() => api.ODPromiseVoid),nextFn:((data:any) => api.ODPromiseVoid),structure:api.ODCheckerTypeSwitchStructure,parent:object|any[],parentIndex:string|number,path:(string|number)[],localPath:(string|number)[]){ renderHeader(path) terminal(ansis.bold.green("You are now creating "+(typeof parentIndex == "string" ? "the property "+ansis.blue("\""+parentIndex+"\"") : "property "+ansis.blue("#"+(parentIndex+1)))+".\n")+ansis.italic.gray("(use arrow keys to navigate, go back using escape)\n")) @@ -972,12 +930,4 @@ async function renderAdditionConfigTypeSwitchStructure(checker:api.ODChecker,bac else if (answer.selectedText.startsWith("Create as object") && structure.options.object) await renderAdditionConfigObjectStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.object,parent,parentIndex,path,localPath) else if (answer.selectedText.startsWith("Create as array/list") && structure.options.array) await renderAdditionConfigArrayStructureSelector(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.array,parent,parentIndex,path,localPath) else if (answer.selectedText.startsWith("Create as null") && structure.options.null) await renderAdditionConfigNullStructure(checker,async () => {await renderAdditionConfigTypeSwitchStructure(checker,backFn,nextFn,structure,parent,parentIndex,path,localPath)},nextFn,structure.options.null,parent,parentIndex,path,localPath) -} - -export async function execute(){ - if (terminal.width < 100 || terminal.height < 35){ - terminal(ansis.red.bold("\n\nMake sure your console or cmd window has a "+ansis.cyan("minimum width & height")+" of "+ansis.cyan("100x35")+" characters.")) - terminal(ansis.red.bold("\nOtherwise the Open Ticket Interactive Setup CLI will be rendered incorrectly.")) - terminal(ansis.red.bold("\nThe current terminal dimensions are: "+ansis.cyan(terminal.width+"x"+terminal.height)+".")) - }else await renderConfigSelector(terminate) } \ No newline at end of file diff --git a/src/core/cli/quickSetup.ts b/src/core/cli/quickSetup.ts new file mode 100644 index 0000000..c27e17f --- /dev/null +++ b/src/core/cli/quickSetup.ts @@ -0,0 +1,121 @@ +import {opendiscord, api, utilities} from "../../index" +import {Terminal, terminal} from "terminal-kit" +import ansis from "ansis" +import {renderHeader} from "./cli" + +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("\n\nAre you sure you want to continue?") + + 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 program will help you with configuring Open Ticket using a step-by-step method.", + "If you've ever used Google Forms, then this will probably be very easy for you 😉.", + "", + ansis.magenta("The configuration should normally only take around 5 minutes."), + ansis.magenta("Once you've completed the form, the bot is technically 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 renderQuickSetupDevPortalInfo(async () => {await renderQuickSetupWelcome(backFn)}) +} + +async function renderQuickSetupDevPortalInfo(backFn:() => api.ODPromiseVoid){ + renderHeader("⏱️ Open Ticket Quick Setup: Dev Portal & Experience") + + terminal.bold.blue("(Step 1) Have you already created a Discord bot to 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.gray, + selectedStyle:terminal.bgDefaultColor.bold, + submittedStyle:terminal.bgBlue, + extraLines:2, + cancelable:true + }).promise + + if (answer.canceled) return await backFn() + else if (answer.selectedIndex == 0) await backFn() //TODO NEXT QUESTION + else if (answer.selectedIndex == 1) await renderQuickSetupDevPortalGuide(0,async () => {await renderQuickSetupDevPortalInfo(backFn)}) + else if (answer.selectedIndex == 2) await renderQuickSetupDevPortalGuide(1,async () => {await renderQuickSetupDevPortalInfo(backFn)}) +} + +async function renderQuickSetupDevPortalGuide(variation:0|1,backFn:() => api.ODPromiseVoid){ + renderHeader("⏱️ Open Ticket Quick Setup: Dev Portal & Experience") + + if (variation == 0){ + terminal.bold.blue("(Step 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("(Step 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 still, 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() +} diff --git a/src/index.ts b/src/index.ts index 5bbb50c..c83ae71 100644 --- a/src/index.ts +++ b/src/index.ts @@ -301,7 +301,7 @@ const main = async () => { //switch to CLI context instead of running the bot if (useCliFlag && useCliFlag.value){ - await (await (import("./core/startup/cli.js"))).execute() + await (await (import("./core/cli/cli.js"))).execute() await utilities.timer(1000) console.log("\n\n"+ansis.red("❌ Something went wrong in the Interactive Setup CLI. Please try again or report a bug in our discord server.")) process.exit(0)