diff --git a/config.json.example b/config.json.example index ecb04f0..2a3b961 100644 --- a/config.json.example +++ b/config.json.example @@ -11,5 +11,5 @@ "password": "" }, "myriad": "", - "debug": false + "dev": false } \ No newline at end of file diff --git a/package.json b/package.json index c3577ee..012fef7 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "lint:fix": "eslint --ignore-path .gitignore \"**/*.+(ts|js)\" --fix", "start": "node dist", "start:dev": "node dist/yumeko.js dev", + "create:cmd": "node dist/util/CreateCommand.js", + "config:sync": "node dist/util/ConfigTypeSync.js", "test": "yarn lint && yarn build" }, "repository": "https://github.com/youKnowOwO/yumeko-ts.git", diff --git a/src/classes/Client.ts b/src/classes/Client.ts index 8b11d2e..f42a42e 100644 --- a/src/classes/Client.ts +++ b/src/classes/Client.ts @@ -7,6 +7,7 @@ import Logger from "@yumeko/libs/Logger"; import eventLoader from "@yumeko/libs/EventLoader"; import nowPlayMoe from "@yumeko/libs/NowplayMoeWS"; import { langCollector } from "@yumeko/libs/Localization"; +import { Config } from "@yumeko/interfaces"; import { Client } from "discord.js"; import { Node as Lavalink } from "lavalink"; @@ -14,7 +15,7 @@ import "../extension"; import { hide } from "@yumeko/decorators"; // i don't want compiler compile these one -const config = require("../../config.json"); +const config: Config = require("../../config.json"); export default class YumekoClient extends Client { @hide diff --git a/src/commands/Util/Brainly.ts b/src/commands/Utility/Brainly.ts similarity index 100% rename from src/commands/Util/Brainly.ts rename to src/commands/Utility/Brainly.ts diff --git a/src/commands/Util/Code.ts b/src/commands/Utility/Code.ts similarity index 100% rename from src/commands/Util/Code.ts rename to src/commands/Utility/Code.ts diff --git a/src/commands/Util/Docs.ts b/src/commands/Utility/Docs.ts similarity index 100% rename from src/commands/Util/Docs.ts rename to src/commands/Utility/Docs.ts diff --git a/src/commands/Util/EslintRule.ts b/src/commands/Utility/EslintRule.ts similarity index 100% rename from src/commands/Util/EslintRule.ts rename to src/commands/Utility/EslintRule.ts diff --git a/src/commands/Util/Google.ts b/src/commands/Utility/Google.ts similarity index 100% rename from src/commands/Util/Google.ts rename to src/commands/Utility/Google.ts diff --git a/src/commands/Util/Instagram.ts b/src/commands/Utility/Instagram.ts similarity index 100% rename from src/commands/Util/Instagram.ts rename to src/commands/Utility/Instagram.ts diff --git a/src/commands/Util/Jisho.ts b/src/commands/Utility/Jisho.ts similarity index 100% rename from src/commands/Util/Jisho.ts rename to src/commands/Utility/Jisho.ts diff --git a/src/commands/Util/Lint.ts b/src/commands/Utility/Lint.ts similarity index 100% rename from src/commands/Util/Lint.ts rename to src/commands/Utility/Lint.ts diff --git a/src/commands/Util/MDN.ts b/src/commands/Utility/MDN.ts similarity index 100% rename from src/commands/Util/MDN.ts rename to src/commands/Utility/MDN.ts diff --git a/src/commands/Util/Npm.ts b/src/commands/Utility/Npm.ts similarity index 100% rename from src/commands/Util/Npm.ts rename to src/commands/Utility/Npm.ts diff --git a/src/commands/Util/Stackoverflow/Show.ts b/src/commands/Utility/Stackoverflow/Show.ts similarity index 100% rename from src/commands/Util/Stackoverflow/Show.ts rename to src/commands/Utility/Stackoverflow/Show.ts diff --git a/src/commands/Util/Stackoverflow/Stackoverflow.ts b/src/commands/Utility/Stackoverflow/Stackoverflow.ts similarity index 100% rename from src/commands/Util/Stackoverflow/Stackoverflow.ts rename to src/commands/Utility/Stackoverflow/Stackoverflow.ts diff --git a/src/commands/Util/Weather.ts b/src/commands/Utility/Weather.ts similarity index 100% rename from src/commands/Util/Weather.ts rename to src/commands/Utility/Weather.ts diff --git a/src/commands/Util/Webshot.ts b/src/commands/Utility/Webshot.ts similarity index 100% rename from src/commands/Util/Webshot.ts rename to src/commands/Utility/Webshot.ts diff --git a/src/events/Debug.ts b/src/events/Debug.ts index 33a47fa..a07babf 100644 --- a/src/events/Debug.ts +++ b/src/events/Debug.ts @@ -3,10 +3,12 @@ import { Event } from "@yumeko/interfaces"; export default class DebugEvent implements Event { public readonly listener = "debug"; + public readonly devOnly = true; public constructor(public readonly client: YumekoClient) {} public exec(msg: string): void { - if (this.client.config.debug) { + if (this.client.config.dev) { this.client.log.info(msg); } } -} \ No newline at end of file +} + diff --git a/src/events/Ready.ts b/src/events/Ready.ts index 755485d..5c5e369 100644 --- a/src/events/Ready.ts +++ b/src/events/Ready.ts @@ -1,6 +1,5 @@ import type YumekoClient from "@yumeko/classes/Client"; import { Event } from "@yumeko/interfaces"; -import { stripIndents } from "common-tags"; import { constantly } from "@yumeko/decorators"; const presences = require("../../assets/json/presence.json"); @@ -12,9 +11,6 @@ export default class ReadyEvent implements Event { @constantly public exec (): void { assignDB(this.client); - this.client.log.info(stripIndents` - ${this.client.log.color(this.client.user!.tag, "FFFFFF")} is Ready to play. ${this.client.shard ? this.client.shard.ids.map(x => this.client.log.color(`#${x + 1}`, "00FFFF")).join(", ") : ""} - `); this.client.lavalink.userID = this.client.user!.id; presence.call(null, this.client); setInterval(presence.bind(null, this.client), 60000); diff --git a/src/interfaces/Config.ts b/src/interfaces/Config.ts new file mode 100644 index 0000000..d6924c2 --- /dev/null +++ b/src/interfaces/Config.ts @@ -0,0 +1,16 @@ +// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. + +export interface Config { + owners: string[]; + prefix: string; + color: string; + lavalink: { + hosts: { + rest: string; + ws: string; + }; + password: string; + }; + myriad: string; + dev: boolean; +} \ No newline at end of file diff --git a/src/interfaces/Event.ts b/src/interfaces/Event.ts index 5ffcd49..f7f718e 100644 --- a/src/interfaces/Event.ts +++ b/src/interfaces/Event.ts @@ -1,10 +1,17 @@ +import type Command from "@yumeko/classes/Command"; import type { ClientEvents } from "discord.js"; -export interface YumekoClientEvents extends ClientEvents { - raw: [any]; -} +type EventKeys = keyof ClientEvents; export interface Event { - readonly listener: keyof YumekoClientEvents; - exec(...args: YumekoClientEvents[Event["listener"]]): any; + readonly listener: EventKeys; + readonly devOnly?: boolean; + exec(...args: ClientEvents[EventKeys]): any; +} + +declare module "discord.js" { + interface ClientEvents { + raw: [any]; + commandStored: [Command?]; + } } \ No newline at end of file diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index 17f8272..ac8b39e 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1,4 +1,5 @@ export * from "@yumeko/interfaces/Command"; export * from "@yumeko/interfaces/Other"; export * from "@yumeko/interfaces/Event"; -export * from "@yumeko/interfaces/HTTPResponse"; \ No newline at end of file +export * from "@yumeko/interfaces/HTTPResponse"; +export * from "@yumeko/interfaces/Config"; \ No newline at end of file diff --git a/src/libs/CommandCollector.ts b/src/libs/CommandCollector.ts index 84d54eb..0ff834e 100644 --- a/src/libs/CommandCollector.ts +++ b/src/libs/CommandCollector.ts @@ -18,25 +18,23 @@ export default class CommandCollector { public constructor(public client: YumekoClient) {} - public loadAll(log = true): void { + public loadAll(): void { const path = join(__dirname, "../commands"); const files = readdirRecursive(path); - const { print, color, equal, date } = this.client.log; - if (log) print(equal(color("▶️ Collecting Command", "00C2FF"))); for (const file of files) { const load = require(file).default; if (!load || !(load.prototype instanceof Command)) continue; const command = this.getCommand(file); this.registry(command); - if (log) print(`+ ${color(command.identifier, "FE9DFF")} (${color(file, "A20092")})`); } - if (log) print(equal(color(date(), "505050"))); + this.client.emit("commandStored"); } public registry(command: string | Command): void { if (typeof command === "string") command = this.getCommand(command); this.addToCategory(command); this.commands.set(command.identifier, command); + this.client.emit("commandStored", command); } public getCommand(path: string): Command { diff --git a/src/libs/EventLoader.ts b/src/libs/EventLoader.ts index 379e58e..b1cd9bf 100644 --- a/src/libs/EventLoader.ts +++ b/src/libs/EventLoader.ts @@ -8,6 +8,7 @@ export default function EventLoader (client: YumekoClient): void { const files = readdirRecursive(path); for (const file of files) { const event: Event = new (require(file).default)(client); + if (event.devOnly && !client.config.dev) continue; client.addListener(event.listener, event.exec.bind(event) as any); } } \ No newline at end of file diff --git a/src/util/CodeLinter.ts b/src/util/CodeLinter.ts index eba246e..e7668c5 100644 --- a/src/util/CodeLinter.ts +++ b/src/util/CodeLinter.ts @@ -1,5 +1,5 @@ import type YumekoClient from "@yumeko/classes/Client"; -import LintCommand from "@yumeko/commands/Util/Lint"; +import LintCommand from "@yumeko/commands/Utility/Lint"; import type { Message } from "discord.js"; import { TypeCodeReturn } from "@yumeko/interfaces"; import { Linter, Rule } from "eslint"; diff --git a/src/util/ConfigTypeSync.ts b/src/util/ConfigTypeSync.ts new file mode 100644 index 0000000..13a1a1e --- /dev/null +++ b/src/util/ConfigTypeSync.ts @@ -0,0 +1,32 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import chalk from "chalk"; +import { promises } from "fs"; + +const config: object = require("../../config.json"); + +function parse(data: any, indent = 1): string { + const type = typeof data; + if (data instanceof Array) return parseArray(data, indent); + if (type === "object") return parseObject(data, indent); + return type; +} + +function parseObject(data: Record | null, indent: number): string { + if (data === null) return "null"; + const space = " ".repeat(indent * 4); + const types = []; + for (const x of Object.keys(data)) types.push(`${space}${x}: ${parse(data[x], indent + 1)};`); + return types.length ? `{\n${types.join("\n")}\n${space.slice(4)}}` : "{}"; +} + +function parseArray(data: T[], indent: number): string { + const parsed = parse(data[0], indent); + return `${parsed}[]`; +} + +void async function exec(): Promise { + const toWrite = `// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.\n\nexport interface Config ${parse(config)}`; + await promises.writeFile("./src/interfaces/Config.ts", toWrite); + process.stdout.write(chalk`{bgGreen {black \ DONE }} Config sucessfully sync`); + process.stdout.write("\n"); +}(); \ No newline at end of file diff --git a/src/util/CreateCommand.ts b/src/util/CreateCommand.ts new file mode 100644 index 0000000..cb99530 --- /dev/null +++ b/src/util/CreateCommand.ts @@ -0,0 +1,82 @@ +import { createInterface, ReadLineOptions } from "readline"; +import { existsSync, promises } from "fs"; +import { join } from "path"; +import { firstUpperCase } from "./Util"; +import chalk from "chalk"; + +const categories = Object.keys(require("../../assets/json/help.json")); + +const rl = createInterface({ + input: process.stdin, + output: process.stdout +} as ReadLineOptions); + +function awaitQuestion(query: string): Promise { + return new Promise(resolve => rl.question(query, resolve)); +} + +function end(prop?: string, error = false): void { + if (error) rl.write(chalk`{bgRed {black \ ERROR }} {red ${prop}}`); + else if (prop) rl.write(chalk`{bgRed {black \ ERROR }} Command {blue ${prop}} is required`); + rl.write("\n"); + process.exit(0); +} + +export async function exec(): Promise { + rl.write(chalk`\n{bgBlue {black \ INFO }} Please describe your {blue command} !\n\n`); + let aliases: string[] = []; + const id = await awaitQuestion(chalk` {blue •} What {blue identifier} for this command ? `); + if (!id.length) return end("identifier"); + aliases.push(id); + await awaitQuestion(chalk` {blue •} Please insert {blue aliases} you want splited by comma (,) ! `).then(x => aliases.push(...x.split(","))); + aliases = aliases.filter(x => x.length); + const descriptionContent = await awaitQuestion(chalk` {blue •} What {blue description} of the command ? {gray (if came from localization please insert the id between <>)} `) + .then(x => { + if (!x.length) return ""; + if (x.startsWith("<") && x.endsWith(">")) + return `(msg): string => msg.ctx.lang("${x.replace(/<|>/g, "")}")`; + return `"${x}"`; + }); + if (!descriptionContent.length) return end("description"); + const usageDescription = await awaitQuestion(chalk` {blue •} How you {blue usage} this command ? `); + if (!usageDescription.length) return end("usage"); + const exampleDescription = await awaitQuestion(chalk` {blue •} Please write at least 1 {blue example} ! `); + if (!exampleDescription.length) return end("example"); + const category = await awaitQuestion(chalk` {blue •} Which {blue category} that ship this command ? {gray (${categories.join(", ")})} `); + if (!category.length) return end("categroy"); + const filename = await awaitQuestion(chalk` {blue •} What the {blue filename} do you want ? {gray (${firstUpperCase(id)})} `).then(x => x.length ? x : firstUpperCase(id)); + const dirname = await awaitQuestion(chalk` {blue •} What the {blue dirname} do you want ? {gray (${firstUpperCase(category)})} `).then(x => x.length ? x : firstUpperCase(category)); + const toWrite = `import Command from "@yumeko/classes/Command"; +import type { Message } from "discord.js"; +import { DeclareCommand, constantly } from "@yumeko/decorators"; + +@DeclareCommand("${id}", { + aliases: [${aliases.map(x => `"${x}"`).join(", ")}], + description: { + content: ${descriptionContent}, + usage: "${usageDescription}", + examples: ["${exampleDescription}"] + }, + category: "${category}" +}) +export default class extends Command { + @constantly + public async exec(msg: Message): Promise { + return msg; + } +}`; + const path = join("./src/commands", dirname); + rl.write(chalk`\n{bgBlue {black \ INFO }} Checking directory...\n`); + if (!existsSync(path)) { + rl.write(chalk`{bgBlue {black \ INFO }} Directory isn't exist try to create...\n`); + await promises.mkdir(path, { recursive: true }); + rl.write(chalk`{bgGreen {black \ DONE }} Directory Created !\n`); + } + rl.write(chalk`{bgBlue {black \ INFO }} Writting command...\n`); + const file = join(path, `${filename}.ts`); + await promises.writeFile(file, toWrite, { encoding: "utf-8" }); + rl.write(chalk`{bgGreen {black \ DONE }} Succes write command to {green ${file}}\n`); + return end(); +} + +exec().catch((e: Error) => end(String(e), true)); \ No newline at end of file diff --git a/src/util/Myriad.ts b/src/util/Myriad.ts index 64abc5b..afb2986 100644 --- a/src/util/Myriad.ts +++ b/src/util/Myriad.ts @@ -3,7 +3,7 @@ import request from "node-superfetch"; import { Message, MessageReaction, User } from "discord.js"; import { TypeCodeReturn } from "@yumeko/interfaces"; import { join } from "path"; -import type CodeCommand from "@yumeko/commands/Util/Code"; +import type CodeCommand from "@yumeko/commands/Utility/Code"; export async function exec(client: YumekoClient, code: string, language: string): Promise<[boolean, string]> { const endpoint = join(client.config.myriad, "eval"); diff --git a/src/yumeko.ts b/src/yumeko.ts index 59c82cf..b1426be 100644 --- a/src/yumeko.ts +++ b/src/yumeko.ts @@ -2,7 +2,7 @@ import YumekoClient from "./classes/Client"; if (process.argv[2] === "dev") { require("./util/EnvLoader"); - require("../config.json").debug = true; + require("../config.json").dev = true; } const client = new YumekoClient();