Create initial bot framework (#7)

#1

Reviewed-on: https://gitea.vylpes.xyz/External/card-drop/pulls/7
Co-authored-by: Ethan Lane <ethan@vylpes.com>
Co-committed-by: Ethan Lane <ethan@vylpes.com>
This commit is contained in:
Ethan Lane 2023-08-19 16:56:22 +01:00 committed by Vylpes
parent cb548898ce
commit c706737369
35 changed files with 5876 additions and 0 deletions

24
.dev.env Normal file
View file

@ -0,0 +1,24 @@
# Security Warning! Do not commit this file to any VCS!
# This is a local file to speed up development process,
# so you don't have to change your environment variables.
#
# This is not applied to `.env.template`!
# Template files must be committed to the VCS, but must not contain
# any secret values.
BOT_TOKEN=
BOT_VER=0.1.0
BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816
BOT_CLIENTID=682942374040961060
ABOUT_FUNDING=
ABOUT_REPO=
DB_HOST=127.0.0.1
DB_PORT=3301
DB_NAME=carddrop
DB_AUTH_USER=dev
DB_AUTH_PASS=dev
DB_SYNC=true
DB_LOGGING=true

18
.gitea/ISSUE_TEMPLATE.md Normal file
View file

@ -0,0 +1,18 @@
Epic: \
Story Points:
---
*No description*
## Acceptance Criteria
*No acceptance criteria*
## Subtasks
*No subtasks*
## Notes
*No notes*

View file

@ -0,0 +1,29 @@
# Description
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change.
Fixes # (issue)
## Type of change
Please delete options that are not relevant.
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update
# How Has This Been Tested?
Please describe the tests that you ran to verify the changes. Provide instructions so we can reproduce. Please also list any relevant details to your test configuration.
# Checklist
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that provde my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream modules

108
.gitignore vendored Normal file
View file

@ -0,0 +1,108 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
config.json
.DS_Store
ormconfig.json

24
.prod.env Normal file
View file

@ -0,0 +1,24 @@
# Security Warning! Do not commit this file to any VCS!
# This is a local file to speed up development process,
# so you don't have to change your environment variables.
#
# This is not applied to `.env.template`!
# Template files must be committed to the VCS, but must not contain
# any secret values.
BOT_TOKEN=
BOT_VER=0.1.0
BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816
BOT_CLIENTID=680083120896081954
ABOUT_FUNDING=
ABOUT_REPO=
DB_HOST=127.0.0.1
DB_PORT=3321
DB_NAME=carddrop
DB_AUTH_USER=prod
DB_AUTH_PASS=prod
DB_SYNC=false
DB_LOGGING=false

24
.stage.env Normal file
View file

@ -0,0 +1,24 @@
# Security Warning! Do not commit this file to any VCS!
# This is a local file to speed up development process,
# so you don't have to change your environment variables.
#
# This is not applied to `.env.template`!
# Template files must be committed to the VCS, but must not contain
# any secret values.
BOT_TOKEN=
BOT_VER=0.1.0
BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816
BOT_CLIENTID=1016767908740857949
ABOUT_FUNDING=
ABOUT_REPO=
DB_HOST=127.0.0.1
DB_PORT=3311
DB_NAME=carddrop
DB_AUTH_USER=stage
DB_AUTH_PASS=stage
DB_SYNC=false
DB_LOGGING=false

31
docker-compose.prod.yml Normal file
View file

@ -0,0 +1,31 @@
version: "3.9"
volumes:
prod_database_data:
services:
# discord:
# build: .
database:
image: mysql/mysql-server
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
- MYSQL_DATABASE=carddrop
- MYSQL_USER=prod
- MYSQL_PASSWORD=prod
- MYSQL_ROOT_PASSWORD=root
- MYSQL_ROOT_HOST=0.0.0.0
ports:
- "3321:3306"
volumes:
- prod_database_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- "3322:80"
environment:
- PMA_ARBITRARY=1

31
docker-compose.stage.yml Normal file
View file

@ -0,0 +1,31 @@
version: "3.9"
volumes:
stage_database_data:
services:
# discord:
# build: .
database:
image: mysql/mysql-server
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
- MYSQL_DATABASE=carddrop
- MYSQL_USER=stage
- MYSQL_PASSWORD=stage
- MYSQL_ROOT_PASSWORD=root
- MYSQL_ROOT_HOST=0.0.0.0
ports:
- "3311:3306"
volumes:
- stage_database_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- "3312:80"
environment:
- PMA_ARBITRARY=1

31
docker-compose.yml Normal file
View file

@ -0,0 +1,31 @@
version: "3.9"
volumes:
dev_database_data:
services:
# discord:
# build: .
database:
image: mysql/mysql-server
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
- MYSQL_DATABASE=carddrop
- MYSQL_USER=dev
- MYSQL_PASSWORD=dev
- MYSQL_ROOT_PASSWORD=root
- MYSQL_ROOT_HOST=0.0.0.0
ports:
- "3301:3306"
volumes:
- dev_database_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin
restart: always
ports:
- "3302:80"
environment:
- PMA_ARBITRARY=1

5
jest.config.json Normal file
View file

@ -0,0 +1,5 @@
{
"preset": "ts-jest",
"testEnvironment": "node",
"setupFiles": ["./jest.setup.js"]
}

3
jest.setup.js Normal file
View file

@ -0,0 +1,3 @@
jest.setTimeout(1 * 1000); // 1 second
jest.resetModules();
jest.resetAllMocks();

45
package.json Normal file
View file

@ -0,0 +1,45 @@
{
"name": "card-drop",
"version": "0.1.0",
"main": "./dist/bot.js",
"typings": "./dist",
"scripts": {
"clean": "rm -rf node_modules/ dist/",
"build": "tsc",
"start": "node ./dist/bot.js",
"test": "jest",
"db:up": "typeorm migration:run -d dist/database/dataSources/appDataSource.js",
"db:down": "typeorm migration:revert -d dist/database/dataSources/appDataSource.js",
"release": "np --no-publish"
},
"repository": "https://gitea.vylpes.xyz/External/card-drop.git",
"author": "Ethan Lane <ethan@vylpes.com>",
"license": "MIT",
"bugs": {
"url": "https//gitea.vylpes.xyz/External/card-drop/issues",
"email": "helpdesk@vylpes.com"
},
"homepage": "https://gitea.vylpes.xyz/External/card-drop",
"funding": "https://ko-fi.com/vylpes",
"dependencies": {
"@discordjs/rest": "^1.1.0",
"@types/jest": "^29.0.0",
"@types/uuid": "^9.0.0",
"discord.js": "^14.3.0",
"dotenv": "^16.0.0",
"jest": "^29.0.0",
"jest-mock-extended": "^3.0.0",
"minimatch": "9.0.2",
"mysql": "^2.18.1",
"ts-jest": "^29.0.0",
"typeorm": "0.3.17"
},
"resolutions": {
"**/semver": "^7.5.2"
},
"devDependencies": {
"@types/node": "^20.0.0",
"np": "^8.0.4",
"typescript": "^5.0.0"
}
}

4
renovate.json Normal file
View file

@ -0,0 +1,4 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"baseBranches": ["develop"]
}

36
src/bot.ts Normal file
View file

@ -0,0 +1,36 @@
import * as dotenv from "dotenv";
import { CoreClient } from "./client/client";
import { IntentsBitField } from "discord.js";
import Registry from "./registry";
dotenv.config();
const requiredConfigs: string[] = [
"BOT_TOKEN",
"BOT_VER",
"BOT_AUTHOR",
"BOT_OWNERID",
"BOT_CLIENTID",
"DB_HOST",
"DB_PORT",
"DB_AUTH_USER",
"DB_AUTH_PASS",
"DB_SYNC",
"DB_LOGGING",
]
requiredConfigs.forEach(config => {
if (!process.env[config]) {
throw `${config} is required in .env`;
}
});
const client = new CoreClient([
IntentsBitField.Flags.Guilds,
IntentsBitField.Flags.GuildMembers,
]);
Registry.RegisterCommands();
Registry.RegisterEvents();
client.start();

75
src/client/client.ts Normal file
View file

@ -0,0 +1,75 @@
import { Client } from "discord.js";
import * as dotenv from "dotenv";
import { EventType } from "../constants/EventType";
import ICommandItem from "../contracts/ICommandItem";
import IEventItem from "../contracts/IEventItem";
import { Command } from "../type/command";
import { Events } from "./events";
import { Util } from "./util";
import AppDataSource from "../database/dataSources/appDataSource";
export class CoreClient extends Client {
private static _commandItems: ICommandItem[];
private static _eventItems: IEventItem[];
private _events: Events;
private _util: Util;
public static get commandItems(): ICommandItem[] {
return this._commandItems;
}
public static get eventItems(): IEventItem[] {
return this._eventItems;
}
constructor(intents: number[]) {
super({ intents: intents });
dotenv.config();
CoreClient._commandItems = [];
CoreClient._eventItems = [];
this._events = new Events();
this._util = new Util();
}
public async start() {
if (!process.env.BOT_TOKEN) {
console.error("BOT_TOKEN is not defined in .env");
return;
}
await AppDataSource.initialize()
.then(() => console.log("Data Source Initialized"))
.catch((err) => console.error("Error Initialising Data Source", err));
super.on("interactionCreate", this._events.onInteractionCreate);
super.on("ready", this._events.onReady);
await super.login(process.env.BOT_TOKEN);
this._util.loadEvents(this, CoreClient._eventItems);
this._util.loadSlashCommands(this);
}
public static RegisterCommand(name: string, command: Command, serverId?: string) {
const item: ICommandItem = {
Name: name,
Command: command,
ServerId: serverId,
};
CoreClient._commandItems.push(item);
}
public static RegisterEvent(eventType: EventType, func: Function) {
const item: IEventItem = {
EventType: eventType,
ExecutionFunction: func,
};
CoreClient._eventItems.push(item);
}
}

33
src/client/events.ts Normal file
View file

@ -0,0 +1,33 @@
import { Interaction } from "discord.js";
import ICommandItem from "../contracts/ICommandItem";
import { CoreClient } from "./client";
export class Events {
public async onInteractionCreate(interaction: Interaction) {
if (!interaction.isChatInputCommand()) return;
if (!interaction.guildId) return;
const item = CoreClient.commandItems.find(x => x.Name == interaction.commandName && !x.ServerId);
const itemForServer = CoreClient.commandItems.find(x => x.Name == interaction.commandName && x.ServerId == interaction.guildId);
let itemToUse: ICommandItem;
if (!itemForServer) {
if (!item) {
await interaction.reply('Command not found');
return;
}
itemToUse = item;
} else {
itemToUse = itemForServer;
}
itemToUse.Command.execute(interaction);
}
// Emit when bot is logged in and ready to use
public onReady() {
console.log("Ready");
}
}

95
src/client/util.ts Normal file
View file

@ -0,0 +1,95 @@
import { Client, REST, Routes, SlashCommandBuilder } from "discord.js";
import { EventType } from "../constants/EventType";
import IEventItem from "../contracts/IEventItem";
import { CoreClient } from "./client";
export class Util {
public loadSlashCommands(client: Client) {
const registeredCommands = CoreClient.commandItems;
const globalCommands = registeredCommands.filter(x => !x.ServerId);
const guildCommands = registeredCommands.filter(x => x.ServerId);
const globalCommandData: SlashCommandBuilder[] = globalCommands
.filter(x => x.Command.CommandBuilder)
.flatMap(x => x.Command.CommandBuilder);
const guildIds: string[] = [];
for (let command of guildCommands) {
if (!guildIds.find(x => x == command.ServerId)) {
guildIds.push(command.ServerId!);
}
}
const rest = new REST({ version: '10' }).setToken(process.env.BOT_TOKEN!);
rest.put(
Routes.applicationCommands(process.env.BOT_CLIENTID!),
{
body: globalCommandData
}
);
for (let guild of guildIds) {
const guildCommandData = guildCommands.filter(x => x.ServerId == guild)
.filter(x => x.Command.CommandBuilder)
.flatMap(x => x.Command.CommandBuilder);
if (!client.guilds.cache.has(guild)) continue;
rest.put(
Routes.applicationGuildCommands(process.env.BOT_CLIENTID!, guild),
{
body: guildCommandData
}
)
}
}
// Load the events
loadEvents(client: Client, events: IEventItem[]) {
events.forEach((e) => {
switch(e.EventType) {
case EventType.ChannelCreate:
client.on('channelCreate', (channel) => e.ExecutionFunction(channel));
break;
case EventType.ChannelDelete:
client.on('channelDelete', (channel) => e.ExecutionFunction(channel));
break;
case EventType.ChannelUpdate:
client.on('channelUpdate', (channel) => e.ExecutionFunction(channel));
break;
case EventType.GuildBanAdd:
client.on('guildBanAdd', (ban) => e.ExecutionFunction(ban));
break;
case EventType.GuildBanRemove:
client.on('guildBanRemove', (ban) => e.ExecutionFunction(ban));
break;
case EventType.GuildCreate:
client.on('guildCreate', (guild) => e.ExecutionFunction(guild));
break;
case EventType.GuildMemberAdd:
client.on('guildMemberAdd', (member) => e.ExecutionFunction(member));
break;
case EventType.GuildMemberRemove:
client.on('guildMemberRemove', (member) => e.ExecutionFunction(member));
break;
case EventType.GuildMemberUpdate:
client.on('guildMemberUpdate', (oldMember, newMember) => e.ExecutionFunction(oldMember, newMember));
break;
case EventType.MessageCreate:
client.on('messageCreate', (message) => e.ExecutionFunction(message));
break;
case EventType.MessageDelete:
client.on('messageDelete', (message) => e.ExecutionFunction(message));
break;
case EventType.MessageUpdate:
client.on('messageUpdate', (oldMessage, newMessage) => e.ExecutionFunction(oldMessage, newMessage));
break;
default:
console.error('Event not implemented.');
}
});
}
}

56
src/commands/about.ts Normal file
View file

@ -0,0 +1,56 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, EmbedBuilder, SlashCommandBuilder } from "discord.js";
import EmbedColours from "../constants/EmbedColours";
import { Command } from "../type/command";
export default class About extends Command {
constructor() {
super();
super.CommandBuilder = new SlashCommandBuilder()
.setName('about')
.setDescription('About Bot');
}
public override async execute(interaction: CommandInteraction) {
const fundingLink = process.env.ABOUT_FUNDING;
const repoLink = process.env.ABOUT_REPO;
const embed = new EmbedBuilder()
.setColor(EmbedColours.Ok)
.setTitle("About")
.setDescription("Discord Bot made by Vylpes");
embed.addFields([
{
name: "Version",
value: process.env.BOT_VER!,
inline: true,
},
{
name: "Author",
value: process.env.BOT_AUTHOR!,
inline: true,
},
]);
const row = new ActionRowBuilder<ButtonBuilder>();
if (repoLink) {
row.addComponents(
new ButtonBuilder()
.setURL(repoLink)
.setLabel("Repo")
.setStyle(ButtonStyle.Link));
}
if (fundingLink) {
row.addComponents(
new ButtonBuilder()
.setURL(fundingLink)
.setLabel("Funding")
.setStyle(ButtonStyle.Link));
}
await interaction.reply({ embeds: [ embed ], components: row.components.length > 0 ? [ row ] : [] });
}
}

View file

@ -0,0 +1,3 @@
export default class EmbedColours {
public static readonly Ok = 0x3050ba;
}

View file

@ -0,0 +1,15 @@
export enum EventType {
ChannelCreate,
ChannelDelete,
ChannelUpdate,
GuildBanAdd,
GuildBanRemove,
GuildCreate,
GuildMemberAdd,
GuildMemberRemove,
GuildMemberUpdate,
MessageCreate,
MessageDelete,
MessageUpdate,
Ready,
}

View file

@ -0,0 +1,59 @@
import { Column, DeepPartial, EntityTarget, PrimaryColumn, ObjectLiteral, FindOptionsWhere } from "typeorm";
import { v4 } from "uuid";
import AppDataSource from "../database/dataSources/appDataSource";
export default class BaseEntity {
constructor() {
this.Id = v4();
this.WhenCreated = new Date();
this.WhenUpdated = new Date();
}
@PrimaryColumn()
Id: string;
@Column()
WhenCreated: Date;
@Column()
WhenUpdated: Date;
public async Save<T extends BaseEntity>(target: EntityTarget<T>, entity: DeepPartial<T>): Promise<void> {
this.WhenUpdated = new Date();
const repository = AppDataSource.getRepository<T>(target);
await repository.save(entity);
}
public static async Remove<T extends BaseEntity>(target: EntityTarget<T>, entity: T): Promise<void> {
const repository = AppDataSource.getRepository<T>(target);
await repository.remove(entity);
}
public static async FetchAll<T extends BaseEntity>(target: EntityTarget<T>, relations?: string[]): Promise<T[]> {
const repository = AppDataSource.getRepository<T>(target);
const all = await repository.find({ relations: relations || [] });
return all;
}
public static async FetchOneById<T extends BaseEntity>(target: EntityTarget<T>, id: string, relations?: string[]): Promise<T | null> {
const repository = AppDataSource.getRepository<T>(target);
const single = await repository.findOne({ where: ({ Id: id } as FindOptionsWhere<T>), relations: relations || {} });
return single;
}
public static async Any<T extends ObjectLiteral>(target: EntityTarget<T>): Promise<boolean> {
const repository = AppDataSource.getRepository<T>(target);
const any = await repository.find();
return any.length > 0;
}
}

View file

@ -0,0 +1,4 @@
export interface IBaseResponse {
valid: boolean;
message?: string;
}

View file

@ -0,0 +1,7 @@
import { Command } from "../type/command";
export default interface ICommandItem {
Name: string,
Command: Command,
ServerId?: string,
}

View file

@ -0,0 +1,7 @@
import { EventType } from "../constants/EventType";
export default interface IEventItem {
EventType: EventType,
ExecutionFunction: Function,
}

View file

@ -0,0 +1,26 @@
import { DataSource } from "typeorm";
import * as dotenv from "dotenv";
dotenv.config();
const AppDataSource = new DataSource({
type: "mysql",
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
username: process.env.DB_AUTH_USER,
password: process.env.DB_AUTH_PASS,
database: process.env.DB_NAME,
synchronize: process.env.DB_SYNC == "true",
logging: process.env.DB_LOGGING == "true",
entities: [
"dist/database/entities/**/*.js",
],
migrations: [
"dist/database/migrations/**/*.js",
],
subscribers: [
"dist/database/subscribers/**/*.js",
],
});
export default AppDataSource;

View file

View file

View file

@ -0,0 +1,20 @@
import { readFileSync } from "fs";
import { QueryRunner } from "typeorm";
export default class MigrationHelper {
public static Up(migrationName: string, version: string, queryFiles: string[], queryRunner: QueryRunner) {
for (let path of queryFiles) {
const query = readFileSync(`${process.cwd()}/database/${version}/${migrationName}/Up/${path}.sql`).toString();
queryRunner.query(query);
}
}
public static Down(migrationName: string, version: string, queryFiles: string[], queryRunner: QueryRunner) {
for (let path of queryFiles) {
const query = readFileSync(`${process.cwd()}/database/${version}/${migrationName}/Down/${path}.sql`).toString();
queryRunner.query(query);
}
}
}

View file

@ -0,0 +1,42 @@
export default class StringTools {
public static Capitalise(str: string): string {
const words = str.split(" ");
let result: string[] = [];
words.forEach(word => {
const firstLetter = word.substring(0, 1).toUpperCase();
const rest = word.substring(1);
result.push(firstLetter + rest);
});
return result.join(" ");
}
public static CapitaliseArray(str: string[]): string[] {
const res: string[] = [];
str.forEach(s => {
res.push(StringTools.Capitalise(s));
});
return res;
}
public static RandomString(length: number) {
let result = "";
const characters = 'abcdefghkmnpqrstuvwxyz23456789';
const charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
public static ReplaceAll(str: string, find: string, replace: string) {
return str.replace(new RegExp(find, 'g'), replace);
}
}

View file

@ -0,0 +1,121 @@
import StringTools from "./StringTools";
export default class TimeLengthInput {
public readonly value: string;
constructor(input: string) {
this.value = StringTools.ReplaceAll(input, ',', '');
}
public GetDays(): number {
return this.GetValue('d');
}
public GetHours(): number {
return this.GetValue('h');
}
public GetMinutes(): number {
return this.GetValue('m');
}
public GetSeconds(): number {
return this.GetValue('s');
}
public GetMilliseconds(): number {
const days = this.GetDays();
const hours = this.GetHours();
const minutes = this.GetMinutes();
const seconds = this.GetSeconds();
let milliseconds = 0;
milliseconds += seconds * 1000;
milliseconds += minutes * 60 * 1000;
milliseconds += hours * 60 * 60 * 1000;
milliseconds += days * 24 * 60 * 60 * 1000;
return milliseconds;
}
public GetDateFromNow(): Date {
const now = Date.now();
const dateFromNow = now
+ (1000 * this.GetSeconds())
+ (1000 * 60 * this.GetMinutes())
+ (1000 * 60 * 60 * this.GetHours())
+ (1000 * 60 * 60 * 24 * this.GetDays());
return new Date(dateFromNow);
}
public GetLength(): string {
const days = this.GetDays();
const hours = this.GetHours();
const minutes = this.GetMinutes();
const seconds = this.GetSeconds();
const value = [];
if (days) {
value.push(`${days} days`);
}
if (hours) {
value.push(`${hours} hours`);
}
if (minutes) {
value.push(`${minutes} minutes`);
}
if (seconds) {
value.push(`${seconds} seconds`);
}
return value.join(", ");
}
public GetLengthShort(): string {
const days = this.GetDays();
const hours = this.GetHours();
const minutes = this.GetMinutes();
const seconds = this.GetSeconds();
const value = [];
if (days) {
value.push(`${days}d`);
}
if (hours) {
value.push(`${hours}h`);
}
if (minutes) {
value.push(`${minutes}m`);
}
if (seconds) {
value.push(`${seconds}s`);
}
return value.join(" ");
}
private GetValue(designation: string): number {
const valueSplit = this.value.split(' ');
const desString = valueSplit.find(x => x.charAt(x.length - 1) == designation);
if (!desString) return 0;
const desNumber = Number(desString.substring(0, desString.length - 1));
if (!desNumber) return 0;
return desNumber;
}
}

13
src/registry.ts Normal file
View file

@ -0,0 +1,13 @@
import { CoreClient } from "./client/client";
import About from "./commands/about";
export default class Registry {
public static RegisterCommands() {
CoreClient.RegisterCommand('about', new About());
}
public static RegisterEvents() {
}
}

9
src/type/command.ts Normal file
View file

@ -0,0 +1,9 @@
import { CommandInteraction } from "discord.js";
export class Command {
public CommandBuilder: any;
public execute(interaction: CommandInteraction) {
}
}

0
tests/.gitkeep Normal file
View file

78
tsconfig.json Normal file
View file

@ -0,0 +1,78 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
"declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"include": [
"./src",
],
"exclude": [
"./tests"
]
}

4800
yarn.lock Normal file

File diff suppressed because it is too large Load diff