├── src
├── functions
│ └── get.ts-test
├── typings
│ ├── index.d.ts
│ └── Command.d.ts
├── config.json
├── server.ts
├── index.ts
├── handlers
│ ├── commands.ts
│ ├── events.ts
│ └── slash.ts
├── events
│ ├── interactionCreate.ts
│ ├── messageCreate.ts
│ └── ready.ts
├── slashCommands
│ ├── template.ts
│ └── general
│ │ └── hello.ts
├── commands
│ └── eval.ts
└── cores
│ └── Client.ts
├── .replit
├── .gitignore
├── README.md
├── replit.nix
├── tsconfig.json
├── package.json
└── LICENSE
/src/functions/get.ts-test:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/typings/index.d.ts:
--------------------------------------------------------------------------------
1 | export * from "./Command"
--------------------------------------------------------------------------------
/.replit:
--------------------------------------------------------------------------------
1 | run = "npm start"
2 | entrypoint = "src/index.ts"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | node_modules
3 | package-lock.json
4 | .config
5 | src/decorators
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # atom-bot
2 |
3 | my first discord bot written in typescript language
4 |
--------------------------------------------------------------------------------
/src/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": false,
3 | "prefix": "at.",
4 | "port": 3000,
5 | "ownersID": ["681843628317868049", "718116697286115348"]
6 | }
--------------------------------------------------------------------------------
/replit.nix:
--------------------------------------------------------------------------------
1 | { pkgs }: {
2 | deps = with pkgs; [
3 | nodejs-16_x
4 | nodePackages.typescript-language-server
5 | libuuid
6 | cowsay
7 | ];
8 | }
--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------
1 | import * as http from "http";
2 |
3 | export default function(port: number): void {
4 | http.createServer(function(req, res) {
5 | res.writeHead(200, {
6 | "Content-Type": "text/html"
7 | })
8 | res.write("
Hello
");
9 | res.end()
10 | }).listen(port)
11 | }
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import "dotenv/config";
2 | import 'module-alias/register';
3 | import { Intents, Message } from "discord.js"
4 | import Client from "@cores/Client"
5 | import * as config from "./config.json"
6 |
7 | const client = new Client()
8 |
9 |
10 | if(client.config?.debug) {
11 | client.debug()
12 | }
13 |
14 |
15 | client.init()
--------------------------------------------------------------------------------
/src/handlers/commands.ts:
--------------------------------------------------------------------------------
1 | import Client from "@cores/Client";
2 | import * as fs from "fs";
3 | let files = fs.readdirSync("build/commands")
4 | .filter(x => x.endsWith(".js"))
5 |
6 | export default function(client: Client) {
7 | for(let file of files) {
8 | let cmd = require(`../commands/${file}`).default
9 |
10 | client.commands.set(cmd.name, cmd)
11 | }
12 | }
--------------------------------------------------------------------------------
/src/events/interactionCreate.ts:
--------------------------------------------------------------------------------
1 | import Client from "@cores/Client"
2 | import { CommandInteraction } from "discord.js"
3 |
4 | export default {
5 | name: "interactionCreate",
6 | run(client: Client, interaction: CommandInteraction) {
7 | if(!interaction.isCommand()) return
8 |
9 | let cmd = client.slashCommands.get(interaction.commandName)
10 |
11 | if(interaction.commandName === cmd?.name) {
12 | cmd?.run(client, interaction)
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/handlers/events.ts:
--------------------------------------------------------------------------------
1 | import Client from "@cores/Client"
2 | import * as fs from "fs"
3 |
4 | export default async function(client: Client) {
5 | try {
6 | let files = fs.readdirSync("build/events")
7 | .filter(x => x.endsWith(".js"))
8 | // console.log(files.filter(x => x.endsWith(".js")))
9 |
10 | for(let file of files) {
11 | //if(!file.endsWith(".js")) return
12 | let event = require(`../events/${file}`)
13 | event = event.default
14 | // console.log(event)
15 | // console.log(file)
16 |
17 | client.on(event.name, (...args) => event.run(client, ...args))
18 | }
19 | } catch (e) {
20 | console.error(e)
21 | }
22 | }
--------------------------------------------------------------------------------
/src/events/messageCreate.ts:
--------------------------------------------------------------------------------
1 | import { Message } from "discord.js";
2 | import Client from "@cores/Client"
3 |
4 | export default {
5 | name: "messageCreate",
6 | run(client: Client, message: Message): void {
7 | let args: string[] = message.content
8 | .slice(client.config.prefix.length)
9 | .trim()
10 | .split(/ +/g)
11 | // console.log(args)
12 | let command = args.shift()!.toLowerCase()
13 | // console.log(command)
14 | let cmd = client.commands.get(command)
15 | if(!message.content.startsWith(client.config.prefix)) return
16 | try {
17 | if(cmd?.dev && !client.config.ownersID.includes(message.author.id)) return
18 | cmd?.run(client, message, args)
19 | } catch (e) {
20 | message.reply(( e as string ).toString());
21 | console.error(e);
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "outDir": "./build",
4 | "baseUrl": "./src",
5 | "target": "es2017",
6 | "lib": ["esnext", "dom"],
7 | "module": "commonjs",
8 | "moduleResolution": "node",
9 | "strict": true,
10 | "declaration": false,
11 | "allowJs": true,
12 | "sourceMap": true,
13 | "typeRoots": [ "./node_modules/@types" ],
14 | "inlineSources": true,
15 | "types": ["node"],
16 | "allowSyntheticDefaultImports": true,
17 | "experimentalDecorators": true,
18 | "skipLibCheck": true,
19 | "removeComments": true,
20 | "resolveJsonModule": true,
21 | "paths": {
22 | "@src/*": ["./*"],
23 | "@typings": ["typings"],
24 | "@cores/*": ["cores/*"],
25 | "@handlers/*": ["handlers/*"]
26 | }
27 | },
28 | "includes": [
29 | "./src/**/*"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/src/slashCommands/template.ts:
--------------------------------------------------------------------------------
1 | import { SlashCommandConfig } from "@typings"
2 |
3 | const command: SlashCommandConfig = {
4 | name: "command",
5 | description: "a command",
6 | run(client, message) {
7 | let world = message.options!.get("world")
8 | if(world?.value) {
9 | message.reply("hello world")
10 | } message.reply("hello")
11 | },
12 | // can add more than 2 options
13 | // optional
14 | options: [{
15 | run(opt) {
16 | return opt
17 | .setName("world")
18 | .setDescription("with world")
19 | // optional
20 | // .setRequired(false)
21 | },
22 | /* available types:
23 | * - string
24 | * - boolean
25 | * - number
26 | * - integer
27 | * - user
28 | * - channel
29 | * - mentionable
30 | * - role
31 | */
32 | type: "string"
33 | }]
34 | }
35 |
--------------------------------------------------------------------------------
/src/slashCommands/general/hello.ts:
--------------------------------------------------------------------------------
1 | import { SlashCommandConfig } from "@typings"
2 | import Client from "@cores/Client"
3 | import { CommandInteraction } from "discord.js"
4 | // import { slashCommandBuilder }
5 |
6 | const helloCommand: SlashCommandConfig = {
7 | name: "hello",
8 | description: "say hello 👋",
9 | run(client, message) {
10 | // message.deferReply()
11 | let opt = message.options!.get("opt")
12 | if(opt && opt!.value) {
13 | message.reply("with options")
14 | } else {
15 | message.reply("Hello 👋");
16 | message.followUp({ content: "alpin ~~jelek~~ ganteng ||jangan bilang bilang!||", ephemeral: true })
17 | }
18 | },
19 | options: [{
20 | run(opt) {
21 | return opt
22 | .setName("opt")
23 | .setDescription("with options")
24 | },
25 | type: "boolean"
26 | }]
27 | }
28 |
29 | export default helloCommand;
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "atom-bot",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "build/index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "npm i && tsc && node .",
9 | "dev": "npm i && tsc --watch && pm2 start build/index.js --watch",
10 | "build": "npx tsc"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "devDependencies": {
16 | "@types/winston": "^2.4.4",
17 | "pm2": "latest",
18 | "typescript": "^4.5.2"
19 | },
20 | "dependencies": {
21 | "@discordjs/builders": "latest",
22 | "@discordjs/rest": "latest",
23 | "axios": "latest",
24 | "discord-api-types": "latest",
25 | "discord.js": "^13.5.0-dev.1640390738.2bfc638",
26 | "dotenv": "latest",
27 | "got": "latest",
28 | "module-alias": "latest",
29 | "winston": "latest",
30 | "superagent": "latest",
31 | "canvas": "^2.8.0"
32 | },
33 | "_moduleAliases": {
34 | "@src": "build",
35 | "@typings": "build/typings",
36 | "@cores": "build/cores",
37 | "@handlers": "build/handlers"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Fabian Maulana
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/events/ready.ts:
--------------------------------------------------------------------------------
1 | import Client from "../cores/Client"
2 | import { REST } from "@discordjs/rest"
3 | import { Routes } from "discord-api-types/v9"
4 | import { SlashCommandConfig, CommandConfig } from "@typings"
5 |
6 | export default {
7 | name: "ready",
8 | run(client: Client): void {
9 | const rest = new REST({
10 | version: "9"
11 | }).setToken(process.env.TOKEN!);
12 | client.logger.log("info", client.user!.tag+" Ready!")
13 | // console.log(client.commands)
14 | client.logger.log("info", 'Started refreshing application (/) commands.');
15 | try {
16 | client.slashCommands.forEach(async (c: SlashCommandConfig): Promise => {
17 | // console.log(c)
18 | if(c.global) {
19 | await rest.put(
20 | Routes.applicationCommands("757215502245298228"),
21 | { body: c?.data },
22 | );
23 | } else {
24 | await rest.put(
25 | Routes.applicationGuildCommands("757215502245298228", "380289224043266048"),
26 | { body: c?.data },
27 | );
28 | }
29 |
30 | client.logger.log("info", `/${c.name} registered.`)
31 | })
32 | client.logger.log("info", `Loaded ${client.slashCommands.size}/${client.commands.size} command(s).`)
33 | client.commands.forEach((c: CommandConfig): void => {
34 | client.logger.log("info", `${c.name} registered.`)
35 | })
36 | } catch (e) {
37 | client.logger.error(e)
38 | }
39 | }
40 | }
--------------------------------------------------------------------------------
/src/commands/eval.ts:
--------------------------------------------------------------------------------
1 | import { CommandConfig } from "@typings";
2 | import Client from "@cores/Client";
3 | import { inspect } from "util";
4 |
5 | const evalCommand: CommandConfig = {
6 | name: 'eval',
7 | dev: true,
8 | description: "Eval Command",
9 | async run(client, message, args) {
10 | const depthFlagRegex = /^--depth=?(.*)$/;
11 | let async = args?.includes("--async");
12 | let silent = args?.includes("--silent");
13 | let depth = args?.some(e => depthFlagRegex.test(e));
14 | let depths = depth ? parseInt(
15 | depthFlagRegex
16 | .exec(
17 | args?.find(e => depthFlagRegex
18 | .exec(e))!)![1],10) || 0 : 0;
19 | const code = args?.filter(e => !/^--(async|silent|depth=?(.*))$/.test(e)).join(" ");
20 | // let code = args?.filter(e => !/!^--(async|depth=?(.*))$/.test(e)).join(" ")
21 | // message.reply("Hello")
22 | let promise = false
23 |
24 | try {
25 | let evaled = eval(async ? `(async () => { ${code!} })()` : code!)
26 | let inspected = typeof evaled === "string" ? evaled : inspect(evaled, { depth: depths });
27 | if(evaled instanceof Promise) {
28 | evaled = await evaled;
29 | promise = true;
30 | }
31 |
32 | /* if(promise) {
33 | inspected = `Promise<${typeof code === "string" ? inspect(inspected) : inspected}>`;
34 | } */
35 | if(silent) return
36 |
37 | await message.reply(`\`\`\`js\n${inspected}\n\`\`\``)
38 | } catch (e) {
39 | message.reply(`\`\`\`js\n${e}\n\`\`\``)
40 | }
41 | }
42 | }
43 |
44 | export default evalCommand;
--------------------------------------------------------------------------------
/src/typings/Command.d.ts:
--------------------------------------------------------------------------------
1 | import
2 | {
3 | SlashCommandBooleanOption,
4 | SlashCommandStringOption,
5 | SlashCommandUserOption,
6 | SlashCommandChannelOption,
7 | SlashCommandIntegerOption,
8 | SlashCommandMentionableOption,
9 | SlashCommandNumberOption,
10 | SlashCommandRoleOption
11 | } from "@discordjs/builders"
12 | import { CommandInteraction, Message } from "discord.js"
13 | import Client from "@cores/Client"
14 | type SlashCommandOptions = SlashCommandBooleanOption|SlashCommandStringOption|SlashCommandUserOption|SlashCommandChannelOption|SlashCommandIntegerOption|SlashCommandMentionableOption|SlashCommandNumberOption|SlashCommandRoleOption;
15 |
16 | interface SlashCommandOption {
17 | /*
18 | string?(opt: SlashCommandStringOption): SlashCommandStringOption,
19 | boolean?(opt: SlashCommandBooleanOption): SlashCommandBooleanOption,
20 | user?(opt: SlashCommandUserOption): SlashCommandUserOption,
21 | mentionable?(opt: SlashCommandMentionableOption): SlashCommandMentionableOption,
22 | channel?(opt: SlashCommandChannelOption): SlashCommandChannelOption,
23 | integer?(opt: SlashCommandIntegerOption): SlashCommandIntegerOption,
24 | role?(opt: SlashCommandRoleOption): SlashCommandRoleOption,
25 | number?(opt: SlashCommandNumberOption): SlashCommandNumberOption,
26 | */
27 | run(opt: SlashCommandOptions): SlashCommandOptions,
28 | type: string
29 | }
30 |
31 | export interface SlashCommandConfig {
32 | name: string,
33 | description: string,
34 | data?: object[],
35 | run(client: Client, message: CommandInteraction): void,
36 | options?: SlashCommandOption[],
37 | global?: boolean
38 | }
39 |
40 | export interface CommandConfig {
41 | name: string
42 | description: string,
43 | aliases?: string[],
44 | dev?: boolean,
45 | run(client: Client, message: Message, args?: string[]): void
46 | }
--------------------------------------------------------------------------------
/src/cores/Client.ts:
--------------------------------------------------------------------------------
1 | import "dotenv/config"
2 | import { Client, Collection, Intents } from "discord.js"
3 | import { CommandConfig, SlashCommandConfig } from "@typings"
4 | import * as winston from "winston"
5 | import * as config from "@src/config.json"
6 | import * as fs from "fs";
7 | import events from "@handlers/events";
8 | import slash from "@handlers/slash";
9 | import commands from "@handlers/commands";
10 | import server from "@src/server";
11 |
12 | // let winston = require("winston")
13 |
14 | class AtomClient extends Client {
15 | public readonly slashCommands: Collection = new Collection();
16 | public readonly commands: Collection = new Collection();
17 | public readonly logger: winston.Logger = winston.createLogger({
18 | transports: [new winston.transports.Console()],
19 | // format: winston.format.combine(
20 | format: winston.format.combine(
21 | winston.format.colorize({ all: true }),
22 | //winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
23 | winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
24 | // winston.format.printf((log) => `[${log.timestamp}] [${log.level.toUpperCase()}]: ${log.message}`)
25 | winston.format.printf((log) => `[${log.timestamp}] [${log.level.toUpperCase()}]: ${log.message}`)
26 | )
27 | });
28 | public config = config;
29 |
30 | public constructor() {
31 | super({
32 | intents: [
33 | Intents.FLAGS.GUILDS,
34 | Intents.FLAGS.GUILD_MEMBERS,
35 | Intents.FLAGS.GUILD_MESSAGES,
36 | Intents.FLAGS.GUILD_MESSAGE_REACTIONS,
37 | Intents.FLAGS.GUILD_VOICE_STATES,
38 | ]
39 | })
40 | }
41 |
42 | public debug(): void {
43 | this.on("debug", (info: string): void => {
44 | this.logger.log("info", info)
45 | })
46 | }
47 |
48 | public init(): void {
49 | let port: number = this.config.port
50 | commands(this)
51 | slash(this)
52 | events(this)
53 | server(port);
54 | void this.login(process.env.TOKEN) }
55 | }
56 | export default AtomClient
--------------------------------------------------------------------------------
/src/handlers/slash.ts:
--------------------------------------------------------------------------------
1 | import "dotenv/config"
2 | import { Interaction } from "discord.js"
3 | import { SlashCommandBuilder } from "@discordjs/builders"
4 | import Client from "@cores/Client"
5 | import { SlashCommandConfig } from "@typings"
6 | import * as fs from "fs"
7 | // type optFn = (opt)
8 | var command = new SlashCommandBuilder()
9 |
10 | declare var commands: SlashCommandConfig;
11 | let dirs = fs.readdirSync("build/slashCommands", { encoding: 'utf8' })
12 | // console.log(dirs)
13 | // var command
14 |
15 | async function run(client: Client) {
16 | try {
17 | (async () => {
18 | for (let dir of dirs) {
19 | let check = fs.lstatSync(`build/slashCommands/${dir}`)
20 | if(!check.isDirectory()) return
21 |
22 | let files = fs.readdirSync(`build/slashCommands/${dir}`)
23 | // console.log(files)
24 |
25 | for(let file of files) {
26 | if(!file.endsWith(".js")) return
27 | let cmd = require(`../../build/slashCommands/${dir}/${file}`)
28 | cmd = cmd.default
29 |
30 | cmd.data = []
31 | let builder = command
32 |
33 |
34 |
35 | if(cmd?.options) {
36 | for(let opt of cmd?.options) {
37 | // builder.addStringOption(opt.run)
38 | /*
39 | if(opt.type === "string") builder.addStringOption(opt.string)
40 | else if(opt.type === "number") builder.addIntegerOption(opt.integer)
41 | else if(opt.type === 'integer') builder.addNumberOption(opt.number)
42 | // else if(opt.type === "subcommand") builder.addSubcommandOption(opt.run)
43 | else if(opt.type === 'boolean') builder.addBooleanOption(opt.boolean)
44 | else if(opt.type === "channel") builder.addChannelOption(opt.channel)
45 | else if(opt.type === 'mentionable') builder.addMentionableOption(opt.mentionable)
46 | else if(opt.type === 'user') builder.addUserOption(opt.user)
47 | else if(opt.type === "role") builder.addRoleOption(opt.role)
48 | */
49 |
50 |
51 | if(opt.type === "string") builder.addStringOption(opt.run)
52 | else if(opt.type === "number") builder.addIntegerOption(opt.run)
53 | else if(opt.type === 'integer') builder.addNumberOption(opt.run)
54 | // else if(opt.type === "subcommand") builder.addSubcommandOption(opt.run)
55 | else if(opt.type === 'boolean') builder.addBooleanOption(opt.run)
56 | else if(opt.type === "channel") builder.addChannelOption(opt.run)
57 | else if(opt.type === 'mentionable') builder.addMentionableOption(opt.run)
58 | else if(opt.type === 'user') builder.addUserOption(opt.run)
59 | else if(opt.type === "role") builder.addRoleOption(opt.run)
60 | }
61 | }
62 | cmd.data.push(
63 | command
64 | .setName(cmd.name)
65 | .setDescription(cmd.description)
66 | )
67 |
68 | // console.log(cmd)
69 | client.slashCommands.set(cmd.name, cmd)
70 | }
71 | }
72 |
73 | try {
74 |
75 | // console.log(rest)
76 |
77 |
78 |
79 | // await rest.put()
80 | } catch (error) {
81 | console.error(error)
82 | }
83 | })()
84 | } catch (e) {
85 | console.error(e)
86 | }
87 | }
88 |
89 | export default run;
--------------------------------------------------------------------------------