Compare commits

..

No commits in common. "486826470aca4c7fecc3fc493f5561830545aee2" and "a4abaa6ae43c140bad4368141c4356eb4727e7c1" have entirely different histories.

50 changed files with 658 additions and 2343 deletions

View file

@ -7,18 +7,15 @@
# any secret values. # any secret values.
BOT_TOKEN= BOT_TOKEN=
BOT_VER=0.4.0 BOT_VER=0.3.1
BOT_AUTHOR=Vylpes BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
BOT_CLIENTID=682942374040961060 BOT_CLIENTID=682942374040961060
BOT_ENV=4 BOT_ENV=4
BOT_ADMINS=147392775707426816,887272961504071690
ABOUT_FUNDING= ABOUT_FUNDING=
ABOUT_REPO= ABOUT_REPO=
DATA_DIR=
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3301 DB_PORT=3301
DB_NAME=carddrop DB_NAME=carddrop
@ -31,4 +28,5 @@ DB_CARD_FILE=:memory:
EXPRESS_PORT=3303 EXPRESS_PORT=3303
GDRIVESYNC_WHITELIST=147392775707426816,887272961504071690
GDRIVESYNC_AUTO=true GDRIVESYNC_AUTO=true

View file

@ -4,36 +4,16 @@ kind: pipeline
name: deployment name: deployment
steps: steps:
- name: build - name: deploy
image: node image: appleboy/drone-ssh
commands:
- npm ci
- npm run build
- name: test
image: node
commands:
- npm test
- name: deploy to prod
image: drillster/drone-rsync
secrets: [ ssh_key, bot_token_prod ]
settings: settings:
hosts: host: 192.168.68.120
- 192.168.68.120 username: vylpes
user: vylpes password:
key: from_secret: ssh_password
from_secret: ssh_key port: 22
source: .
target: ~/apps/card-drop/card-drop_prod
recursive: true
script: script:
- export PATH="$HOME/.yarn/bin:$PATH" - sh /home/vylpes/scripts/card-drop/deploy_prod.sh
- export PATH="$HOME/.nodeuse/bin:$PATH"
- export BOT_TOKEN="$BOT_TOKEN_PROD"
- cd ~/apps/card-drop/card-drop_prod
- docker compose --file docker-compose.prod.yml up -d
- sleep 10
- cp .prod.env .env
- pm2 restart card-drop_prod || pm2 start --name card-drop_prod dist/bot.js
trigger: trigger:
event: event:
@ -45,36 +25,16 @@ kind: pipeline
name: staging name: staging
steps: steps:
- name: build - name: stage
image: node image: appleboy/drone-ssh
commands:
- npm ci
- npm run build
- name: test
image: node
commands:
- npm test
- name: deploy to stage
image: drillster/drone-rsync
secrets: [ ssh_key, bot_token_stage ]
settings: settings:
hosts: host: 192.168.68.120
- 192.168.68.120 username: vylpes
user: vylpes password:
key: from_secret: ssh_password
from_secret: ssh_key port: 22
source: .
target: ~/apps/card-drop/card-drop_stage
recursive: true
script: script:
- export PATH="$HOME/.yarn/bin:$PATH" - sh /home/vylpes/scripts/card-drop/deploy_stage.sh
- export PATH="$HOME/.nodeuse/bin:$PATH"
- export BOT_TOKEN="$BOT_TOKEN_STAGE"
- cd ~/apps/card-drop/card-drop_stage
- docker compose --file docker-compose.stage.yml up -d
- sleep 10
- cp .stage.env .env
- pm2 restart card-drop_stage || pm2 start --name card-drop_stage dist/bot.js
trigger: trigger:
branch: branch:
@ -94,18 +54,16 @@ steps:
- npm ci - npm ci
- npm run build - npm run build
- name: lint
image: node
commands:
- npm run lint
- name: test - name: test
image: node image: node
commands: commands:
- npm ci
- npm test - npm test
trigger: trigger:
branch: branch:
- main
- develop
- hotfix/* - hotfix/*
- feature/* - feature/*
- renovate/* - renovate/*

View file

@ -1,45 +0,0 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
]
},
"globals": {
"jest": true,
"require": true,
"exports": true,
"process": true
},
"ignorePatterns": [
"dist/**/*"
]
}

2
.gitignore vendored
View file

@ -107,5 +107,5 @@ config.json
.DS_Store .DS_Store
ormconfig.json ormconfig.json
gdrive-credentials.json gdrive-credentials.json
data/ cards/
*.db *.db

View file

@ -7,18 +7,15 @@
# any secret values. # any secret values.
BOT_TOKEN= BOT_TOKEN=
BOT_VER=0.4.0 BOT_VER=0.3.1
BOT_AUTHOR=Vylpes BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
BOT_CLIENTID=1093810443589529631 BOT_CLIENTID=1093810443589529631
BOT_ENV=1 BOT_ENV=1
BOT_ADMINS=147392775707426816,887272961504071690
ABOUT_FUNDING= ABOUT_FUNDING=
ABOUT_REPO= ABOUT_REPO=
DATA_DIR=/home/vylpes/appdata/card-drop/card-drop_prod
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3321 DB_PORT=3321
DB_NAME=carddrop DB_NAME=carddrop
@ -31,4 +28,5 @@ DB_CARD_FILE=:memory:
EXPRESS_PORT=3323 EXPRESS_PORT=3323
GDRIVESYNC_WHITELIST=147392775707426816,887272961504071690
GDRIVESYNC_AUTO=false GDRIVESYNC_AUTO=false

View file

@ -7,18 +7,15 @@
# any secret values. # any secret values.
BOT_TOKEN= BOT_TOKEN=
BOT_VER=0.4.0 BOT_VER=0.3.1
BOT_AUTHOR=Vylpes BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
BOT_CLIENTID=1147976642942214235 BOT_CLIENTID=1147976642942214235
BOT_ENV=2 BOT_ENV=2
BOT_ADMINS=147392775707426816,887272961504071690
ABOUT_FUNDING= ABOUT_FUNDING=
ABOUT_REPO= ABOUT_REPO=
DATA_DIR=/home/vylpes/appdata/card-drop/card-drop_stage
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3311 DB_PORT=3311
DB_NAME=carddrop DB_NAME=carddrop
@ -31,4 +28,5 @@ DB_CARD_FILE=:memory:
EXPRESS_PORT=3313 EXPRESS_PORT=3313
GDRIVESYNC_WHITELIST=147392775707426816,887272961504071690
GDRIVESYNC_AUTO=false GDRIVESYNC_AUTO=false

1839
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "card-drop", "name": "card-drop",
"version": "0.4.0", "version": "0.3.1",
"main": "./dist/bot.js", "main": "./dist/bot.js",
"typings": "./dist", "typings": "./dist",
"scripts": { "scripts": {
@ -8,8 +8,6 @@
"build": "tsc", "build": "tsc",
"start": "node ./dist/bot.js", "start": "node ./dist/bot.js",
"test": "jest --passWithNoTests", "test": "jest --passWithNoTests",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"db:up": "typeorm migration:run -d dist/database/dataSources/appDataSource.js", "db:up": "typeorm migration:run -d dist/database/dataSources/appDataSource.js",
"db:down": "typeorm migration:revert -d dist/database/dataSources/appDataSource.js", "db:down": "typeorm migration:revert -d dist/database/dataSources/appDataSource.js",
"db:create": "typeorm migration:create ./src/database/migrations/app/new", "db:create": "typeorm migration:create ./src/database/migrations/app/new",
@ -39,15 +37,14 @@
"minimatch": "9.0.3", "minimatch": "9.0.3",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"ts-jest": "^29.0.0", "ts-jest": "^29.0.0",
"typeorm": "0.3.19" "typeorm": "0.3.17"
},
"resolutions": {
"**/undici": "^5.26.2"
}, },
"resolutions": {},
"devDependencies": { "devDependencies": {
"@types/node": "^20.0.0", "@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^6.16.0", "np": "^8.0.4",
"@typescript-eslint/parser": "^6.16.0",
"eslint": "^8.56.0",
"np": "^9.0.0",
"typescript": "^5.0.0" "typescript": "^5.0.0"
} }
} }

View file

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

View file

@ -2,12 +2,12 @@ import { readFileSync } from "fs";
import path from "path"; import path from "path";
import Config from "../database/entities/app/Config"; import Config from "../database/entities/app/Config";
import { glob } from "glob"; import { glob } from "glob";
import { SeriesMetadata } from "../contracts/SeriesMetadata"; import SeriesMetadata from "../contracts/SeriesMetadata";
import { CoreClient } from "../client/client"; import { CoreClient } from "../client/client";
export default class CardMetadataFunction { export default class CardMetadataFunction {
public static async Execute(overrideSafeMode: boolean = false): Promise<boolean> { public static async Execute(overrideSafeMode: boolean = false): Promise<boolean> {
if (!overrideSafeMode && await Config.GetValue("safemode") == "true") return false; if (!overrideSafeMode && await Config.GetValue('safemode') == "true") return false;
try { try {
CoreClient.Cards = await this.FindMetadataJSONs(); CoreClient.Cards = await this.FindMetadataJSONs();
@ -16,7 +16,7 @@ export default class CardMetadataFunction {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
await Config.SetValue("safemode", "true"); await Config.SetValue('safemode', 'true');
return false; return false;
} }
@ -26,9 +26,9 @@ export default class CardMetadataFunction {
private static async FindMetadataJSONs(): Promise<SeriesMetadata[]> { private static async FindMetadataJSONs(): Promise<SeriesMetadata[]> {
const res: SeriesMetadata[] = []; const res: SeriesMetadata[] = [];
const seriesJSONs = await glob(path.join(process.env.DATA_DIR!, "cards", "/**/*.json")); const seriesJSONs = await glob(path.join(process.cwd(), 'cards', '/**/*.json'));
for (const jsonPath of seriesJSONs) { for (let jsonPath of seriesJSONs) {
console.log(`Reading file ${jsonPath}`); console.log(`Reading file ${jsonPath}`);
const jsonFile = readFileSync(jsonPath); const jsonFile = readFileSync(jsonPath);
const parsedJson: SeriesMetadata[] = JSON.parse(jsonFile.toString()); const parsedJson: SeriesMetadata[] = JSON.parse(jsonFile.toString());

View file

@ -14,8 +14,6 @@ const requiredConfigs: string[] = [
"BOT_OWNERID", "BOT_OWNERID",
"BOT_CLIENTID", "BOT_CLIENTID",
"BOT_ENV", "BOT_ENV",
"BOT_ADMINS",
"DATA_DIR",
"DB_HOST", "DB_HOST",
"DB_PORT", "DB_PORT",
"DB_AUTH_USER", "DB_AUTH_USER",
@ -23,7 +21,8 @@ const requiredConfigs: string[] = [
"DB_SYNC", "DB_SYNC",
"DB_LOGGING", "DB_LOGGING",
"EXPRESS_PORT", "EXPRESS_PORT",
]; "GDRIVESYNC_WHITELIST",
]
requiredConfigs.forEach(config => { requiredConfigs.forEach(config => {
if (!process.env[config]) { if (!process.env[config]) {
@ -40,7 +39,7 @@ Registry.RegisterCommands();
Registry.RegisterEvents(); Registry.RegisterEvents();
Registry.RegisterButtonEvents(); Registry.RegisterButtonEvents();
if (!existsSync(`${process.env.DATA_DIR}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == "true") { if (!existsSync(`${process.cwd()}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == 'true') {
console.log("Card directory not found, syncing..."); console.log("Card directory not found, syncing...");
CoreClient.AllowDrops = false; CoreClient.AllowDrops = false;
@ -50,7 +49,7 @@ if (!existsSync(`${process.env.DATA_DIR}/cards`) && process.env.GDRIVESYNC_AUTO
console.error(error.code); console.error(error.code);
throw `Error while running sync command. Code: ${error.code}`; throw `Error while running sync command. Code: ${error.code}`;
} else { } else {
console.log("Synced successfully."); console.log('Synced successfully.');
CoreClient.AllowDrops = true; CoreClient.AllowDrops = true;
} }
}); });

View file

@ -8,20 +8,20 @@ export default class Claim extends ButtonEvent {
public override async execute(interaction: ButtonInteraction) { public override async execute(interaction: ButtonInteraction) {
if (!interaction.guild || !interaction.guildId) return; if (!interaction.guild || !interaction.guildId) return;
const cardNumber = interaction.customId.split(" ")[1]; const cardNumber = interaction.customId.split(' ')[1];
const claimId = interaction.customId.split(" ")[2]; const claimId = interaction.customId.split(' ')[2];
const droppedBy = interaction.customId.split(" ")[3]; const droppedBy = interaction.customId.split(' ')[3];
const userId = interaction.user.id; const userId = interaction.user.id;
const claimed = await eClaim.FetchOneByClaimId(claimId); const claimed = await eClaim.FetchOneByClaimId(claimId);
if (claimed) { if (claimed) {
await interaction.reply("This card has already been claimed"); await interaction.reply('This card has already been claimed');
return; return;
} }
if (claimId == CoreClient.ClaimId && userId != droppedBy) { if (claimId == CoreClient.ClaimId && userId != droppedBy) {
await interaction.reply("The latest dropped card can only be claimed by the user who dropped it"); await interaction.reply('The latest dropped card can only be claimed by the user who dropped it');
return; return;
} }

View file

@ -1,21 +0,0 @@
import { ButtonInteraction } from "discord.js";
import { ButtonEvent } from "../type/buttonEvent";
import InventoryHelper from "../helpers/InventoryHelper";
export default class Inventory extends ButtonEvent {
public override async execute(interaction: ButtonInteraction) {
const userid = interaction.customId.split(" ")[1];
const page = interaction.customId.split(" ")[2];
try {
const embed = await InventoryHelper.GenerateInventoryPage(interaction.user.username, userid, Number(page));
await interaction.update({
embeds: [ embed.embed ],
components: [ embed.row ],
});
} catch {
await interaction.reply("No page for user found.");
}
}
}

View file

@ -1,4 +1,4 @@
import { AttachmentBuilder, ButtonInteraction } from "discord.js"; import { AttachmentBuilder, ButtonInteraction, DiscordAPIError } from "discord.js";
import { ButtonEvent } from "../type/buttonEvent"; import { ButtonEvent } from "../type/buttonEvent";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { v4 } from "uuid"; import { v4 } from "uuid";
@ -11,26 +11,28 @@ import path from "path";
export default class Reroll extends ButtonEvent { export default class Reroll extends ButtonEvent {
public override async execute(interaction: ButtonInteraction) { public override async execute(interaction: ButtonInteraction) {
if (!CoreClient.AllowDrops) { if (!CoreClient.AllowDrops) {
await interaction.reply("Bot is currently syncing, please wait until its done."); await interaction.reply('Bot is currently syncing, please wait until its done.');
return; return;
} }
if (await Config.GetValue("safemode") == "true") { if (await Config.GetValue('safemode') == "true") {
await interaction.reply("Safe Mode has been activated, please resync to continue."); await interaction.reply('Safe Mode has been activated, please resync to continue.');
return; return;
} }
const randomCard = CardDropHelperMetadata.GetRandomCard(); const randomCard = CardDropHelperMetadata.GetRandomCard();
if (!randomCard) { if (!randomCard) {
await interaction.reply("Unable to fetch card, please try again."); await interaction.reply('Unable to fetch card, please try again.');
return; return;
} }
try { try {
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path)); let image: Buffer;
const imageFileName = randomCard.card.path.split("/").pop()!; const imageFileName = randomCard.card.path.split("/").pop()!;
image = readFileSync(path.join(process.cwd(), 'cards', randomCard.card.path));
await interaction.deferReply(); await interaction.deferReply();
const attachment = new AttachmentBuilder(image, { name: imageFileName }); const attachment = new AttachmentBuilder(image, { name: imageFileName });

View file

@ -1,22 +1,23 @@
import { Client, DMChannel, Guild, GuildBan, GuildMember, Message, NonThreadGuildBasedChannel, PartialGuildMember, PartialMessage } from "discord.js"; import { Client } from "discord.js";
import * as dotenv from "dotenv"; import * as dotenv from "dotenv";
import { EventType } from "../constants/EventType";
import ICommandItem from "../contracts/ICommandItem"; import ICommandItem from "../contracts/ICommandItem";
import EventExecutors from "../contracts/EventExecutors"; import IEventItem from "../contracts/IEventItem";
import { Command } from "../type/command"; import { Command } from "../type/command";
import { Events } from "./events"; import { Events } from "./events";
import { Util } from "./util"; import { Util } from "./util";
import IButtonEventItem from "../contracts/ButtonEventItem"; import IButtonEventItem from "../contracts/IButtonEventItem";
import { ButtonEvent } from "../type/buttonEvent"; import { ButtonEvent } from "../type/buttonEvent";
import AppDataSource from "../database/dataSources/appDataSource"; import AppDataSource from "../database/dataSources/appDataSource";
import { Environment } from "../constants/Environment"; import { Environment } from "../constants/Environment";
import Webhooks from "../webhooks"; import Webhooks from "../webhooks";
import CardMetadataFunction from "../Functions/CardMetadataFunction"; import CardMetadataFunction from "../Functions/CardMetadataFunction";
import { SeriesMetadata } from "../contracts/SeriesMetadata"; import SeriesMetadata from "../contracts/SeriesMetadata";
export class CoreClient extends Client { export class CoreClient extends Client {
private static _commandItems: ICommandItem[]; private static _commandItems: ICommandItem[];
private static _eventExecutors: EventExecutors; private static _eventItems: IEventItem[];
private static _buttonEvents: IButtonEventItem[]; private static _buttonEvents: IButtonEventItem[];
private _events: Events; private _events: Events;
@ -32,8 +33,8 @@ export class CoreClient extends Client {
return this._commandItems; return this._commandItems;
} }
public static get eventExecutors(): EventExecutors { public static get eventItems(): IEventItem[] {
return this._eventExecutors; return this._eventItems;
} }
public static get buttonEvents(): IButtonEventItem[] { public static get buttonEvents(): IButtonEventItem[] {
@ -45,6 +46,7 @@ export class CoreClient extends Client {
dotenv.config(); dotenv.config();
CoreClient._commandItems = []; CoreClient._commandItems = [];
CoreClient._eventItems = [];
CoreClient._buttonEvents = []; CoreClient._buttonEvents = [];
this._events = new Events(); this._events = new Events();
@ -72,11 +74,15 @@ export class CoreClient extends Client {
await CardMetadataFunction.Execute(true); await CardMetadataFunction.Execute(true);
this._util.loadEvents(this, CoreClient._eventExecutors); this._util.loadEvents(this, CoreClient._eventItems);
this._util.loadSlashCommands(this); this._util.loadSlashCommands(this);
this._webhooks.start(); this._webhooks.start();
console.log(`Registered Commands: ${CoreClient._commandItems.flatMap(x => x.Name).join(", ")}`);
console.log(`Registered Events: ${CoreClient._eventItems.flatMap(x => x.EventType).join(", ")}`);
console.log(`Registered Buttons: ${CoreClient._buttonEvents.flatMap(x => x.ButtonId).join(", ")}`);
await super.login(process.env.BOT_TOKEN); await super.login(process.env.BOT_TOKEN);
} }
@ -88,260 +94,20 @@ export class CoreClient extends Client {
ServerId: serverId, ServerId: serverId,
}; };
if ((environment & CoreClient.Environment) == CoreClient.Environment) { if (environment &= CoreClient.Environment) {
CoreClient._commandItems.push(item); CoreClient._commandItems.push(item);
} }
} }
public static RegisterChannelCreateEvent(fn: (channel: NonThreadGuildBasedChannel) => void) { public static RegisterEvent(eventType: EventType, func: Function, environment: Environment = Environment.All) {
if (this._eventExecutors) { const item: IEventItem = {
this._eventExecutors.ChannelCreate.push(fn); EventType: eventType,
} else { ExecutionFunction: func,
this._eventExecutors = { Environment: environment,
ChannelCreate: [ fn ], };
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static RegisterChannelDeleteEvent(fn: (channel: DMChannel | NonThreadGuildBasedChannel) => void) { if (environment &= CoreClient.Environment) {
if (this._eventExecutors) { CoreClient._eventItems.push(item);
this._eventExecutors.ChannelDelete.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [ fn ],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static RegisterChannelUpdateEvent(fn: (channel: DMChannel | NonThreadGuildBasedChannel) => void) {
if (this._eventExecutors) {
this._eventExecutors.ChannelCreate.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [ fn ],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static RegisterGuildBanAddEvent(fn: (ban: GuildBan) => void) {
if (this._eventExecutors) {
this._eventExecutors.GuildBanAdd.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [ fn ],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static RegisterGuildBanRemoveEvent(fn: (channel: GuildBan) => void) {
if (this._eventExecutors) {
this._eventExecutors.GuildBanRemove.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [ fn ],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static RegisterGuildCreateEvent(fn: (guild: Guild) => void) {
if (this._eventExecutors) {
this._eventExecutors.GuildCreate.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [ fn ],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static RegisterGuildMemberAddEvent(fn: (member: GuildMember) => void) {
if (this._eventExecutors) {
this._eventExecutors.GuildMemberAdd.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [ fn ],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static RegisterGuildMemberRemoveEvent(fn: (member: GuildMember | PartialGuildMember) => void) {
if (this._eventExecutors) {
this._eventExecutors.GuildMemberRemove.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [ fn ],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static GuildMemebrUpdate(fn: (oldMember: GuildMember | PartialGuildMember, newMember: GuildMember) => void) {
if (this._eventExecutors) {
this._eventExecutors.GuildMemebrUpdate.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [ fn ],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static RegisterMessageCreateEvent(fn: (message: Message<boolean>) => void) {
if (this._eventExecutors) {
this._eventExecutors.MessageCreate.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [ fn ],
MessageDelete: [],
MessageUpdate: [],
};
}
}
public static RegisterMessageDeleteEvent(fn: (message: Message<boolean> | PartialMessage) => void) {
if (this._eventExecutors) {
this._eventExecutors.MessageDelete.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [ fn ],
MessageUpdate: [],
};
}
}
public static RegisterMessageUpdateEvent(fn: (oldMessage: Message<boolean> | PartialMessage, newMessage: Message<boolean> | PartialMessage) => void) {
if (this._eventExecutors) {
this._eventExecutors.MessageUpdate.push(fn);
} else {
this._eventExecutors = {
ChannelCreate: [],
ChannelDelete: [],
ChannelUpdate: [],
GuildBanAdd: [],
GuildBanRemove: [],
GuildCreate: [],
GuildMemberAdd: [],
GuildMemberRemove: [],
GuildMemebrUpdate: [],
MessageCreate: [],
MessageDelete: [],
MessageUpdate: [ fn ],
};
} }
} }
@ -352,7 +118,7 @@ export class CoreClient extends Client {
Environment: environment, Environment: environment,
}; };
if ((environment & CoreClient.Environment) == CoreClient.Environment) { if (environment &= CoreClient.Environment) {
CoreClient._buttonEvents.push(item); CoreClient._buttonEvents.push(item);
} }
} }

View file

@ -1,14 +1,14 @@
import { ButtonInteraction } from "discord.js"; import { ButtonInteraction, Interaction } from "discord.js";
import { CoreClient } from "../client"; import { CoreClient } from "../client";
export default class Button { export default class Button {
public static async onButtonClicked(interaction: ButtonInteraction) { public static async onButtonClicked(interaction: ButtonInteraction) {
if (!interaction.isButton) return; if (!interaction.isButton) return;
const item = CoreClient.buttonEvents.find(x => x.ButtonId == interaction.customId.split(" ")[0]); const item = CoreClient.buttonEvents.find(x => x.ButtonId == interaction.customId.split(' ')[0]);
if (!item) { if (!item) {
await interaction.reply("Event not found"); await interaction.reply('Event not found');
return; return;
} }

View file

@ -13,7 +13,7 @@ export default class ChatInputCommand {
if (!itemForServer) { if (!itemForServer) {
if (!item) { if (!item) {
await interaction.reply("Command not found"); await interaction.reply('Command not found');
return; return;
} }

View file

@ -1,5 +1,6 @@
import { Client, REST, Routes, SlashCommandBuilder } from "discord.js"; import { Client, REST, Routes, SlashCommandBuilder } from "discord.js";
import EventExecutors from "../contracts/EventExecutors"; import { EventType } from "../constants/EventType";
import IEventItem from "../contracts/IEventItem";
import { CoreClient } from "./client"; import { CoreClient } from "./client";
export class Util { export class Util {
@ -9,25 +10,25 @@ export class Util {
const globalCommands = registeredCommands.filter(x => !x.ServerId); const globalCommands = registeredCommands.filter(x => !x.ServerId);
const guildCommands = registeredCommands.filter(x => x.ServerId); const guildCommands = registeredCommands.filter(x => x.ServerId);
const globalCommandData: Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">[] = []; const globalCommandData: SlashCommandBuilder[] = [];
for (const command of globalCommands) { for (let command of globalCommands) {
if (!command.Command.CommandBuilder) continue; if (!command.Command.CommandBuilder) continue;
if ((command.Environment & CoreClient.Environment) == CoreClient.Environment) { if (command.Environment &= CoreClient.Environment) {
globalCommandData.push(command.Command.CommandBuilder); globalCommandData.push(command.Command.CommandBuilder);
} }
} }
const guildIds: string[] = []; const guildIds: string[] = [];
for (const command of guildCommands) { for (let command of guildCommands) {
if (!guildIds.find(x => x == command.ServerId)) { if (!guildIds.find(x => x == command.ServerId)) {
guildIds.push(command.ServerId!); guildIds.push(command.ServerId!);
} }
} }
const rest = new REST({ version: "10" }).setToken(process.env.BOT_TOKEN!); const rest = new REST({ version: '10' }).setToken(process.env.BOT_TOKEN!);
rest.put( rest.put(
Routes.applicationCommands(process.env.BOT_CLIENTID!), Routes.applicationCommands(process.env.BOT_CLIENTID!),
@ -36,13 +37,13 @@ export class Util {
} }
); );
for (const guild of guildIds) { for (let guild of guildIds) {
const guildCommandData: Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">[] = []; const guildCommandData: SlashCommandBuilder[] = [];
for (const command of guildCommands.filter(x => x.ServerId == guild)) { for (let command of guildCommands.filter(x => x.ServerId == guild)) {
if (!command.Command.CommandBuilder) continue; if (!command.Command.CommandBuilder) continue;
if ((command.Environment & CoreClient.Environment) == CoreClient.Environment) { if (command.Environment &= CoreClient.Environment) {
guildCommandData.push(command.Command.CommandBuilder); guildCommandData.push(command.Command.CommandBuilder);
} }
} }
@ -54,23 +55,53 @@ export class Util {
{ {
body: guildCommandData body: guildCommandData
} }
); )
} }
} }
// Load the events // Load the events
loadEvents(client: Client, events: EventExecutors) { loadEvents(client: Client, events: IEventItem[]) {
client.on("channelCreate", (channel) => events.ChannelCreate.forEach((fn) => fn(channel))); events.forEach((e) => {
client.on("channelDelete", (channel) => events.ChannelDelete.forEach((fn) => fn(channel))); switch(e.EventType) {
client.on("channelUpdate", (channel) => events.ChannelUpdate.forEach((fn) => fn(channel))); case EventType.ChannelCreate:
client.on("guildBanAdd", (ban) => events.GuildBanAdd.forEach((fn) => fn(ban))); client.on('channelCreate', (channel) => e.ExecutionFunction(channel));
client.on("guildBanRemove", (ban) => events.GuildBanRemove.forEach((fn) => fn(ban))); break;
client.on("guildCreate", (guild) => events.GuildCreate.forEach((fn) => fn(guild))); case EventType.ChannelDelete:
client.on("guildMemberAdd", (member) => events.GuildMemberAdd.forEach((fn) => fn(member))); client.on('channelDelete', (channel) => e.ExecutionFunction(channel));
client.on("guildMemberRemove", (member) => events.GuildMemberRemove.forEach((fn) => fn(member))); break;
client.on("guildMemberUpdate", (oldMember, newMember) => events.GuildMemebrUpdate.forEach((fn) => fn(oldMember, newMember))); case EventType.ChannelUpdate:
client.on("messageCreate", (message) => events.MessageCreate.forEach((fn) => fn(message))); client.on('channelUpdate', (channel) => e.ExecutionFunction(channel));
client.on("messageDelete", (message) => events.MessageDelete.forEach((fn) => fn(message))); break;
client.on("messageUpdate", (oldMessage, newMessage) => events.MessageUpdate.forEach((fn) => fn(oldMessage, newMessage))); 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.');
}
});
} }
} }

View file

@ -6,9 +6,9 @@ export default class About extends Command {
constructor() { constructor() {
super(); super();
this.CommandBuilder = new SlashCommandBuilder() super.CommandBuilder = new SlashCommandBuilder()
.setName("about") .setName('about')
.setDescription("About Bot"); .setDescription('About Bot');
} }
public override async execute(interaction: CommandInteraction) { public override async execute(interaction: CommandInteraction) {

View file

@ -1,4 +1,4 @@
import { AttachmentBuilder, CommandInteraction, SlashCommandBuilder } from "discord.js"; import { AttachmentBuilder, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
import { Command } from "../type/command"; import { Command } from "../type/command";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { CoreClient } from "../client/client"; import { CoreClient } from "../client/client";
@ -12,33 +12,35 @@ export default class Drop extends Command {
constructor() { constructor() {
super(); super();
this.CommandBuilder = new SlashCommandBuilder() super.CommandBuilder = new SlashCommandBuilder()
.setName("drop") .setName('drop')
.setDescription("Summon a new card drop"); .setDescription('Summon a new card drop');
} }
public override async execute(interaction: CommandInteraction) { public override async execute(interaction: CommandInteraction) {
if (!CoreClient.AllowDrops) { if (!CoreClient.AllowDrops) {
await interaction.reply("Bot is currently syncing, please wait until its done."); await interaction.reply('Bot is currently syncing, please wait until its done.');
return; return;
} }
if (await Config.GetValue("safemode") == "true") { if (await Config.GetValue('safemode') == "true") {
await interaction.reply("Safe Mode has been activated, please resync to continue."); await interaction.reply('Safe Mode has been activated, please resync to continue.');
return; return;
} }
const randomCard = CardDropHelperMetadata.GetRandomCard(); const randomCard = CardDropHelperMetadata.GetRandomCard();
if (!randomCard) { if (!randomCard) {
await interaction.reply("Unable to fetch card, please try again."); await interaction.reply('Unable to fetch card, please try again.');
return; return;
} }
try { try {
const image = readFileSync(path.join(process.env.DATA_DIR!, "cards", randomCard.card.path)); let image: Buffer;
const imageFileName = randomCard.card.path.split("/").pop()!; const imageFileName = randomCard.card.path.split("/").pop()!;
image = readFileSync(path.join(process.cwd(), 'cards', randomCard.card.path));
await interaction.deferReply(); await interaction.deferReply();
const attachment = new AttachmentBuilder(image, { name: imageFileName }); const attachment = new AttachmentBuilder(image, { name: imageFileName });

View file

@ -9,37 +9,37 @@ export default class Gdrivesync extends Command {
constructor() { constructor() {
super(); super();
this.CommandBuilder = new SlashCommandBuilder() super.CommandBuilder = new SlashCommandBuilder()
.setName("gdrivesync") .setName('gdrivesync')
.setDescription("Sync google drive to the bot") .setDescription('Sync google drive to the bot')
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator); .setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);
} }
public override async execute(interaction: CommandInteraction<CacheType>) { public override async execute(interaction: CommandInteraction<CacheType>) {
if (!interaction.isChatInputCommand()) return; if (!interaction.isChatInputCommand()) return;
const whitelistedUsers = process.env.BOT_ADMINS!.split(","); const whitelistedUsers = process.env.GDRIVESYNC_WHITELIST!.split(',');
if (!whitelistedUsers.find(x => x == interaction.user.id)) { if (!whitelistedUsers.find(x => x == interaction.user.id)) {
await interaction.reply("Only whitelisted users can use this command."); await interaction.reply("Only whitelisted users can use this command.");
return; return;
} }
await interaction.reply("Syncing, this might take a while..."); await interaction.reply('Syncing, this might take a while...');
CoreClient.AllowDrops = false; CoreClient.AllowDrops = false;
exec(`rclone sync card-drop-gdrive: ${process.env.DATA_DIR}/cards`, async (error: ExecException | null) => { exec(`rclone sync card-drop-gdrive: ${process.cwd()}/cards`, async (error: ExecException | null) => {
if (error) { if (error) {
await interaction.editReply(`Error while running sync command. Safe Mode has been activated. Code: ${error.code}`); await interaction.editReply(`Error while running sync command. Safe Mode has been activated. Code: ${error.code}`);
await Config.SetValue("safemode", "true"); await Config.SetValue('safemode', 'true');
} else { } else {
await CardMetadataFunction.Execute(); await CardMetadataFunction.Execute();
await interaction.editReply("Synced successfully."); await interaction.editReply('Synced successfully.');
CoreClient.AllowDrops = true; CoreClient.AllowDrops = true;
await Config.SetValue("safemode", "false"); await Config.SetValue('safemode', 'false');
} }
}); });
} }

View file

@ -1,68 +0,0 @@
import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
import { Command } from "../type/command";
import { CoreClient } from "../client/client";
import Config from "../database/entities/app/Config";
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
import Inventory from "../database/entities/app/Inventory";
export default class Give extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
.setName("give")
.setDescription("Give a user a card manually, in case bot breaks")
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator)
.addStringOption(x =>
x
.setName("cardnumber")
.setDescription("G")
.setRequired(true))
.addUserOption(x =>
x
.setName("user")
.setDescription("The user to give the card to")
.setRequired(true));
}
public override async execute(interaction: CommandInteraction<CacheType>) {
if (!CoreClient.AllowDrops) {
await interaction.reply("Bot is currently syncing, please wait until its done.");
return;
}
if (await Config.GetValue("safemode") == "true") {
await interaction.reply("Safe Mode has been activated, please resync to continue.");
return;
}
const whitelistedUsers = process.env.BOT_ADMINS!.split(",");
if (!whitelistedUsers.find(x => x == interaction.user.id)) {
await interaction.reply("Only whitelisted users can use this command.");
return;
}
const cardNumber = interaction.options.get("cardnumber", true);
const user = interaction.options.getUser("user", true);
const card = CardDropHelperMetadata.GetCardByCardNumber(cardNumber.value!.toString());
if (!card) {
await interaction.reply("Unable to fetch card, please try again.");
return;
}
let inventory = await Inventory.FetchOneByCardNumberAndUserId(user.id, card.id);
if (!inventory) {
inventory = new Inventory(user.id, card.id, 1);
} else {
inventory.SetQuantity(inventory.Quantity + 1);
}
await inventory.Save(Inventory, inventory);
await interaction.reply(`${card.name} given to ${interaction.user}, they now have ${inventory.Quantity}`);
}
}

View file

@ -1,38 +0,0 @@
import { CommandInteraction, SlashCommandBuilder } from "discord.js";
import { Command } from "../type/command";
import InventoryHelper from "../helpers/InventoryHelper";
export default class Inventory extends Command {
constructor() {
super();
this.CommandBuilder = new SlashCommandBuilder()
.setName("inventory")
.setDescription("View your inventory")
.addNumberOption(x =>
x
.setName("page")
.setDescription("The page to start with"));
}
public override async execute(interaction: CommandInteraction) {
const page = interaction.options.get("page");
try {
let pageNumber = 0;
if (page && page.value) {
pageNumber = Number(page.value) - 1;
}
const embed = await InventoryHelper.GenerateInventoryPage(interaction.user.username, interaction.user.id, pageNumber);
await interaction.reply({
embeds: [ embed.embed ],
components: [ embed.row ],
});
} catch {
await interaction.reply("No page for user found.");
}
}
}

View file

@ -7,27 +7,27 @@ export default class Resync extends Command {
constructor() { constructor() {
super(); super();
this.CommandBuilder = new SlashCommandBuilder() super.CommandBuilder = new SlashCommandBuilder()
.setName("resync") .setName('resync')
.setDescription("Resync the card database") .setDescription('Resync the card database')
.setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator); .setDefaultMemberPermissions(PermissionsBitField.Flags.Administrator);
} }
public override async execute(interaction: CommandInteraction<CacheType>) { public override async execute(interaction: CommandInteraction<CacheType>) {
if (!interaction.isChatInputCommand()) return; if (!interaction.isChatInputCommand()) return;
const whitelistedUsers = process.env.BOT_ADMINS!.split(","); const whitelistedUsers = process.env.GDRIVESYNC_WHITELIST!.split(',');
if (!whitelistedUsers.find(x => x == interaction.user.id)) { if (!whitelistedUsers.find(x => x == interaction.user.id)) {
await interaction.reply("Only whitelisted users can use this command."); await interaction.reply("Only whitelisted users can use this command.");
return; return;
} }
const result = await CardMetadataFunction.Execute(true); let result = await CardMetadataFunction.Execute(true);
if (result) { if (result) {
if (await Config.GetValue("safemode") == "true") { if (await Config.GetValue('safemode') == "true") {
await Config.SetValue("safemode", "false"); await Config.SetValue('safemode', 'false');
await interaction.reply("Resynced database and disabled safe mode."); await interaction.reply("Resynced database and disabled safe mode.");
return; return;

View file

@ -11,23 +11,23 @@ export default class Dropnumber extends Command {
constructor() { constructor() {
super(); super();
this.CommandBuilder = new SlashCommandBuilder() super.CommandBuilder = new SlashCommandBuilder()
.setName("dropnumber") .setName('dropnumber')
.setDescription("(TEST) Summon a specific card") .setDescription('(TEST) Summon a specific card')
.addStringOption(x => .addStringOption(x =>
x x
.setName("cardnumber") .setName('cardnumber')
.setDescription("The card number to summon") .setDescription('The card number to summon')
.setRequired(true)); .setRequired(true));
} }
public override async execute(interaction: CommandInteraction<CacheType>) { public override async execute(interaction: CommandInteraction<CacheType>) {
if (!interaction.isChatInputCommand()) return; if (!interaction.isChatInputCommand()) return;
const cardNumber = interaction.options.get("cardnumber"); const cardNumber = interaction.options.get('cardnumber');
if (!cardNumber || !cardNumber.value) { if (!cardNumber || !cardNumber.value) {
await interaction.reply("Card Number is required"); await interaction.reply('Card Number is required');
return; return;
} }
@ -36,7 +36,7 @@ export default class Dropnumber extends Command {
.find(x => x.id == cardNumber.value); .find(x => x.id == cardNumber.value);
if (!card) { if (!card) {
await interaction.reply("Card not found"); await interaction.reply('Card not found');
return; return;
} }
@ -47,7 +47,7 @@ export default class Dropnumber extends Command {
const imageFileName = card.path.split("/").pop()!; const imageFileName = card.path.split("/").pop()!;
try { try {
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.path)); image = readFileSync(path.join(process.cwd(), 'cards', card.path));
} catch { } catch {
await interaction.reply(`Unable to fetch image for card ${card.id}`); await interaction.reply(`Unable to fetch image for card ${card.id}`);
return; return;
@ -78,7 +78,7 @@ export default class Dropnumber extends Command {
if (e instanceof DiscordAPIError) { if (e instanceof DiscordAPIError) {
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: ${e.code}`); await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: ${e.code}`);
} else { } else {
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening. Code: UNKNOWN"); await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: UNKNOWN`);
} }
} }

View file

@ -12,37 +12,37 @@ export default class Droprarity extends Command {
constructor() { constructor() {
super(); super();
this.CommandBuilder = new SlashCommandBuilder() super.CommandBuilder = new SlashCommandBuilder()
.setName("droprarity") .setName('droprarity')
.setDescription("(TEST) Summon a random card of a specific rarity") .setDescription('(TEST) Summon a random card of a specific rarity')
.addStringOption(x => .addStringOption(x =>
x x
.setName("rarity") .setName('rarity')
.setDescription("The rarity you want to summon") .setDescription('The rarity you want to summon')
.setRequired(true)); .setRequired(true));
} }
public override async execute(interaction: CommandInteraction<CacheType>) { public override async execute(interaction: CommandInteraction<CacheType>) {
if (!interaction.isChatInputCommand()) return; if (!interaction.isChatInputCommand()) return;
const rarity = interaction.options.get("rarity"); const rarity = interaction.options.get('rarity');
if (!rarity || !rarity.value) { if (!rarity || !rarity.value) {
await interaction.reply("Rarity is required"); await interaction.reply('Rarity is required');
return; return;
} }
const rarityType = CardRarityParse(rarity.value.toString()); const rarityType = CardRarityParse(rarity.value.toString());
if (rarityType == CardRarity.Unknown) { if (rarityType == CardRarity.Unknown) {
await interaction.reply("Invalid rarity"); await interaction.reply('Invalid rarity');
return; return;
} }
const card = await CardDropHelperMetadata.GetRandomCardByRarity(rarityType); const card = await CardDropHelperMetadata.GetRandomCardByRarity(rarityType);
if (!card) { if (!card) {
await interaction.reply("Card not found"); await interaction.reply('Card not found');
return; return;
} }
@ -50,7 +50,7 @@ export default class Droprarity extends Command {
const imageFileName = card.card.path.split("/").pop()!; const imageFileName = card.card.path.split("/").pop()!;
try { try {
image = readFileSync(path.join(process.env.DATA_DIR!, "cards", card.card.path)); image = readFileSync(path.join(process.cwd(), 'cards', card.card.path));
} catch { } catch {
await interaction.reply(`Unable to fetch image for card ${card.card.id}`); await interaction.reply(`Unable to fetch image for card ${card.card.id}`);
return; return;
@ -81,7 +81,7 @@ export default class Droprarity extends Command {
if (e instanceof DiscordAPIError) { if (e instanceof DiscordAPIError) {
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: ${e.code}`); await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: ${e.code}`);
} else { } else {
await interaction.editReply("Unable to send next drop. Please try again, and report this if it keeps happening. Code: UNKNOWN"); await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: UNKNOWN`);
} }
} }

View file

@ -11,51 +11,51 @@ export enum CardRarity {
export function CardRarityToString(rarity: CardRarity): string { export function CardRarityToString(rarity: CardRarity): string {
switch (rarity) { switch (rarity) {
case CardRarity.Unknown: case CardRarity.Unknown:
return "Unknown"; return "Unknown";
case CardRarity.Bronze: case CardRarity.Bronze:
return "Bronze"; return "Bronze";
case CardRarity.Silver: case CardRarity.Silver:
return "Silver"; return "Silver";
case CardRarity.Gold: case CardRarity.Gold:
return "Gold"; return "Gold";
case CardRarity.Legendary: case CardRarity.Legendary:
return "Legendary"; return "Legendary";
case CardRarity.Manga: case CardRarity.Manga:
return "Manga"; return "Manga";
} }
} }
export function CardRarityToColour(rarity: CardRarity): number { export function CardRarityToColour(rarity: CardRarity): number {
switch (rarity) { switch (rarity) {
case CardRarity.Unknown: case CardRarity.Unknown:
return EmbedColours.Grey; return EmbedColours.Grey;
case CardRarity.Bronze: case CardRarity.Bronze:
return EmbedColours.BronzeCard; return EmbedColours.BronzeCard;
case CardRarity.Silver: case CardRarity.Silver:
return EmbedColours.SilverCard; return EmbedColours.SilverCard;
case CardRarity.Gold: case CardRarity.Gold:
return EmbedColours.GoldCard; return EmbedColours.GoldCard;
case CardRarity.Legendary: case CardRarity.Legendary:
return EmbedColours.LegendaryCard; return EmbedColours.LegendaryCard;
case CardRarity.Manga: case CardRarity.Manga:
return EmbedColours.MangaCard; return EmbedColours.MangaCard;
} }
} }
export function CardRarityParse(rarity: string): CardRarity { export function CardRarityParse(rarity: string): CardRarity {
switch (rarity.toLowerCase()) { switch (rarity.toLowerCase()) {
case "bronze": case "bronze":
return CardRarity.Bronze; return CardRarity.Bronze;
case "silver": case "silver":
return CardRarity.Silver; return CardRarity.Silver;
case "gold": case "gold":
return CardRarity.Gold; return CardRarity.Gold;
case "legendary": case "legendary":
return CardRarity.Legendary; return CardRarity.Legendary;
case "manga": case "manga":
return CardRarity.Manga; return CardRarity.Manga;
default: default:
return CardRarity.Unknown; return CardRarity.Unknown;
} }
} }

View file

@ -11,13 +11,13 @@ export default class AppBaseEntity {
} }
@PrimaryColumn() @PrimaryColumn()
Id: string; Id: string;
@Column() @Column()
WhenCreated: Date; WhenCreated: Date;
@Column() @Column()
WhenUpdated: Date; WhenUpdated: Date;
public async Save<T extends AppBaseEntity>(target: EntityTarget<T>, entity: DeepPartial<T>): Promise<void> { public async Save<T extends AppBaseEntity>(target: EntityTarget<T>, entity: DeepPartial<T>): Promise<void> {
this.WhenUpdated = new Date(); this.WhenUpdated = new Date();

View file

@ -1,19 +0,0 @@
import { DMChannel, Guild, GuildBan, GuildMember, Message, NonThreadGuildBasedChannel, PartialGuildMember, PartialMessage } from "discord.js";
interface EventExecutors {
ChannelCreate: ((channel: NonThreadGuildBasedChannel) => void)[],
ChannelDelete: ((channel: DMChannel | NonThreadGuildBasedChannel) => void)[],
ChannelUpdate: ((channel: DMChannel | NonThreadGuildBasedChannel) => void)[],
GuildBanAdd: ((ban: GuildBan) => void)[],
GuildBanRemove: ((ban: GuildBan) => void)[],
GuildCreate: ((guild: Guild) => void)[],
GuildMemberAdd: ((member: GuildMember) => void)[],
GuildMemberRemove: ((member: GuildMember | PartialGuildMember) => void)[],
GuildMemebrUpdate: ((oldMember: GuildMember | PartialGuildMember, newMember: GuildMember) => void)[],
MessageCreate: ((message: Message<boolean>) => void)[],
MessageDelete: ((message: Message<boolean> | PartialMessage) => void)[],
MessageUpdate: ((oldMessage: Message<boolean> | PartialMessage, newMessage: Message<boolean> | PartialMessage) => void)[],
}
export default EventExecutors;

View file

@ -1,10 +1,8 @@
import { Environment } from "../constants/Environment"; import { Environment } from "../constants/Environment";
import { ButtonEvent } from "../type/buttonEvent"; import { ButtonEvent } from "../type/buttonEvent";
interface ButtonEventItem { export default interface IButtonEventItem {
ButtonId: string, ButtonId: string,
Event: ButtonEvent, Event: ButtonEvent,
Environment: Environment, Environment: Environment,
} }
export default ButtonEventItem;

View file

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

View file

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

View file

@ -1,4 +1,4 @@
export interface IGDriveFolderListing { export default interface IGDriveFolderListing {
id: string, id: string,
name: string, name: string,
} };

View file

@ -1,6 +1,6 @@
import { CardRarity } from "../constants/CardRarity"; import { CardRarity } from "../constants/CardRarity";
export interface SeriesMetadata { export default interface SeriesMetadata {
id: number, id: number,
name: string, name: string,
cards: CardMetadata[], cards: CardMetadata[],

View file

@ -12,10 +12,10 @@ export default class Claim extends AppBaseEntity {
} }
@Column() @Column()
ClaimId: string; ClaimId: string;
@ManyToOne(() => Inventory, x => x.Claims) @ManyToOne(() => Inventory, x => x.Claims)
Inventory: Inventory; Inventory: Inventory;
public SetInventory(inventory: Inventory) { public SetInventory(inventory: Inventory) {
this.Inventory = inventory; this.Inventory = inventory;

View file

@ -12,10 +12,10 @@ export default class Config extends AppBaseEntity {
} }
@Column() @Column()
Key: string; Key: string;
@Column() @Column()
Value: string; Value: string;
public SetValue(value: string) { public SetValue(value: string) {
this.Value = value; this.Value = value;

View file

@ -14,16 +14,16 @@ export default class Inventory extends AppBaseEntity {
} }
@Column() @Column()
UserId: string; UserId: string;
@Column() @Column()
CardNumber: string; CardNumber: string;
@Column() @Column()
Quantity: number; Quantity: number;
@OneToMany(() => Claim, x => x.Inventory) @OneToMany(() => Claim, x => x.Inventory)
Claims: Claim[]; Claims: Claim[];
public SetQuantity(quantity: number) { public SetQuantity(quantity: number) {
this.Quantity = quantity; this.Quantity = quantity;
@ -40,12 +40,4 @@ export default class Inventory extends AppBaseEntity {
return single; return single;
} }
public static async FetchAllByUserId(userId: string): Promise<Inventory[]> {
const repository = AppDataSource.getRepository(Inventory);
const all = await repository.find({ where: { UserId: userId }});
return all;
}
} }

View file

@ -1,17 +1,17 @@
import { MigrationInterface, QueryRunner } from "typeorm"; import { MigrationInterface, QueryRunner } from "typeorm"
import MigrationHelper from "../../../../helpers/MigrationHelper"; import MigrationHelper from "../../../../helpers/MigrationHelper"
export class CreateClaim1694609771821 implements MigrationInterface { export class CreateClaim1694609771821 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
MigrationHelper.Up("1694609771821-CreateClaim", "0.1.5", [ MigrationHelper.Up('1694609771821-CreateClaim', '0.1.5', [
"01-CreateClaim", '01-CreateClaim',
"02-MoveToClaim", '02-MoveToClaim',
"03-AlterInventory", '03-AlterInventory',
], queryRunner); ], queryRunner);
} }
public async down(): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
} }
} }

View file

@ -1,15 +1,15 @@
import { MigrationInterface, QueryRunner } from "typeorm"; import { MigrationInterface, QueryRunner } from "typeorm"
import MigrationHelper from "../../../../helpers/MigrationHelper"; import MigrationHelper from "../../../../helpers/MigrationHelper"
export class CreateBase1693769942868 implements MigrationInterface { export class CreateBase1693769942868 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
MigrationHelper.Up("1693769942868-CreateBase", "0.1", [ MigrationHelper.Up('1693769942868-CreateBase', '0.1', [
"01-table/Inventory", "01-table/Inventory",
], queryRunner); ], queryRunner);
} }
public async down(): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
} }
} }

View file

@ -1,15 +1,15 @@
import { MigrationInterface, QueryRunner } from "typeorm"; import { MigrationInterface, QueryRunner } from "typeorm"
import MigrationHelper from "../../../../helpers/MigrationHelper"; import MigrationHelper from "../../../../helpers/MigrationHelper"
export class CreateConfig1699814500650 implements MigrationInterface { export class CreateConfig1699814500650 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> { public async up(queryRunner: QueryRunner): Promise<void> {
MigrationHelper.Up("1699814500650-createConfig", "0.2", [ MigrationHelper.Up('1699814500650-createConfig', '0.2', [
"01-table/Config", "01-table/Config",
], queryRunner); ], queryRunner);
} }
public async down(): Promise<void> { public async down(queryRunner: QueryRunner): Promise<void> {
} }
} }

View file

@ -1,7 +1,7 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
import { CardRarity, CardRarityToColour, CardRarityToString } from "../constants/CardRarity"; import { CardRarity, CardRarityToColour, CardRarityToString } from "../constants/CardRarity";
import CardRarityChances from "../constants/CardRarityChances"; import CardRarityChances from "../constants/CardRarityChances";
import { CardMetadata, DropResult } from "../contracts/SeriesMetadata"; import { DropResult } from "../contracts/SeriesMetadata";
import { CoreClient } from "../client/client"; import { CoreClient } from "../client/client";
export default class CardDropHelperMetadata { export default class CardDropHelperMetadata {
@ -47,15 +47,7 @@ export default class CardDropHelperMetadata {
}; };
} }
public static GetCardByCardNumber(cardNumber: string): CardMetadata | undefined { public static GenerateDropEmbed(drop: DropResult, quantityClaimed: Number, imageFileName: string): EmbedBuilder {
const card = CoreClient.Cards
.flatMap(x => x.cards)
.find(x => x.id == cardNumber);
return card;
}
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string): EmbedBuilder {
let description = ""; let description = "";
description += `Series: ${drop.series.name}\n`; description += `Series: ${drop.series.name}\n`;
description += `Claimed: ${quantityClaimed}\n`; description += `Claimed: ${quantityClaimed}\n`;
@ -76,7 +68,7 @@ export default class CardDropHelperMetadata {
.setLabel("Claim") .setLabel("Claim")
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
new ButtonBuilder() new ButtonBuilder()
.setCustomId("reroll") .setCustomId(`reroll`)
.setLabel("Reroll") .setLabel("Reroll")
.setStyle(ButtonStyle.Secondary)); .setStyle(ButtonStyle.Secondary));
} }

View file

@ -1,98 +0,0 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
import Inventory from "../database/entities/app/Inventory";
import { CoreClient } from "../client/client";
import EmbedColours from "../constants/EmbedColours";
import { CardRarity, CardRarityToString } from "../constants/CardRarity";
interface InventoryPage {
id: number,
name: string,
cards: InventoryPageCards[],
seriesSubpage: number,
}
interface InventoryPageCards {
id: string,
name: string,
type: CardRarity,
quantity: number,
}
export default class InventoryHelper {
public static async GenerateInventoryPage(username: string, userid: string, page: number): Promise<{ embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder> }> {
const cardsPerPage = 15;
const inventory = await Inventory.FetchAllByUserId(userid);
const allSeriesClaimed = CoreClient.Cards
.sort((a, b) => a.id - b.id)
.filter(x => {
x.cards = x.cards
.sort((a, b) => b.type - a.type)
.filter(y => inventory.find(z => z.CardNumber == y.id));
return x;
});
const pages: InventoryPage[] = [];
for (const series of allSeriesClaimed) {
const seriesCards = series.cards;
for (let i = 0; i < seriesCards.length; i+= cardsPerPage) {
const cards = series.cards.slice(i, i + cardsPerPage);
const pageCards: InventoryPageCards[] = [];
for (const card of cards) {
const item = inventory.find(x => x.CardNumber == card.id);
if (!item) {
continue;
}
pageCards.push({
id: card.id,
name: card.name,
type: card.type,
quantity: item.Quantity,
});
}
pages.push({
id: series.id,
name: series.name,
cards: pageCards,
seriesSubpage: i / cardsPerPage,
});
}
}
const currentPage = pages[page];
if (!currentPage) {
console.error("Unable to find page");
return Promise.reject("Unable to find page");
}
const embed = new EmbedBuilder()
.setTitle(username)
.setDescription(`**${currentPage.name} (${currentPage.seriesSubpage + 1})**\n${currentPage.cards.map(x => `[${x.id}] ${x.name} (${CardRarityToString(x.type)}) x${x.quantity}`).join("\n")}`)
.setFooter({ text: `Page ${page + 1} of ${pages.length}` })
.setColor(EmbedColours.Ok);
const row = new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(`inventory ${userid} ${page - 1}`)
.setLabel("Previous")
.setStyle(ButtonStyle.Primary)
.setDisabled(page == 0),
new ButtonBuilder()
.setCustomId(`inventory ${userid} ${page + 1}`)
.setLabel("Next")
.setStyle(ButtonStyle.Primary)
.setDisabled(page + 1 == pages.length));
return { embed, row };
}
}

View file

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

View file

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

View file

@ -4,23 +4,23 @@ export default class TimeLengthInput {
public readonly value: string; public readonly value: string;
constructor(input: string) { constructor(input: string) {
this.value = StringTools.ReplaceAll(input, ",", ""); this.value = StringTools.ReplaceAll(input, ',', '');
} }
public GetDays(): number { public GetDays(): number {
return this.GetValue("d"); return this.GetValue('d');
} }
public GetHours(): number { public GetHours(): number {
return this.GetValue("h"); return this.GetValue('h');
} }
public GetMinutes(): number { public GetMinutes(): number {
return this.GetValue("m"); return this.GetValue('m');
} }
public GetSeconds(): number { public GetSeconds(): number {
return this.GetValue("s"); return this.GetValue('s');
} }
public GetMilliseconds(): number { public GetMilliseconds(): number {
@ -106,7 +106,7 @@ export default class TimeLengthInput {
} }
private GetValue(designation: string): number { private GetValue(designation: string): number {
const valueSplit = this.value.split(" "); const valueSplit = this.value.split(' ');
const desString = valueSplit.find(x => x.charAt(x.length - 1) == designation); const desString = valueSplit.find(x => x.charAt(x.length - 1) == designation);

View file

@ -2,7 +2,7 @@ import { Request, Response } from "express";
import CardMetadataFunction from "../Functions/CardMetadataFunction"; import CardMetadataFunction from "../Functions/CardMetadataFunction";
export default async function ReloadDB(req: Request, res: Response) { export default async function ReloadDB(req: Request, res: Response) {
console.log("Reloading Card DB..."); console.log('Reloading Card DB...');
await CardMetadataFunction.Execute(); await CardMetadataFunction.Execute();

View file

@ -1,12 +1,9 @@
import { CoreClient } from "./client/client"; import { CoreClient } from "./client/client";
import { Environment } from "./constants/Environment";
// Global Command Imports // Global Command Imports
import About from "./commands/about"; import About from "./commands/about";
import Drop from "./commands/drop"; import Drop from "./commands/drop";
import Gdrivesync from "./commands/gdrivesync"; import Gdrivesync from "./commands/gdrivesync";
import Give from "./commands/give";
import Inventory from "./commands/inventory";
import Resync from "./commands/resync"; import Resync from "./commands/resync";
// Test Command Imports // Test Command Imports
@ -15,22 +12,20 @@ import Droprarity from "./commands/stage/droprarity";
// Button Event Imports // Button Event Imports
import Claim from "./buttonEvents/Claim"; import Claim from "./buttonEvents/Claim";
import InventoryButtonEvent from "./buttonEvents/Inventory";
import Reroll from "./buttonEvents/Reroll"; import Reroll from "./buttonEvents/Reroll";
import { Environment } from "./constants/Environment";
export default class Registry { export default class Registry {
public static RegisterCommands() { public static RegisterCommands() {
// Global Commands // Global Commands
CoreClient.RegisterCommand("about", new About()); CoreClient.RegisterCommand('about', new About());
CoreClient.RegisterCommand("drop", new Drop()); CoreClient.RegisterCommand('drop', new Drop());
CoreClient.RegisterCommand("gdrivesync", new Gdrivesync()); CoreClient.RegisterCommand('gdrivesync', new Gdrivesync());
CoreClient.RegisterCommand("give", new Give()); CoreClient.RegisterCommand('resync', new Resync());
CoreClient.RegisterCommand("inventory", new Inventory());
CoreClient.RegisterCommand("resync", new Resync());
// Test Commands // Test Commands
CoreClient.RegisterCommand("dropnumber", new Dropnumber(), Environment.Test); CoreClient.RegisterCommand('dropnumber', new Dropnumber(), Environment.Test);
CoreClient.RegisterCommand("droprarity", new Droprarity(), Environment.Test); CoreClient.RegisterCommand('droprarity', new Droprarity(), Environment.Test);
} }
public static RegisterEvents() { public static RegisterEvents() {
@ -38,8 +33,7 @@ export default class Registry {
} }
public static RegisterButtonEvents() { public static RegisterButtonEvents() {
CoreClient.RegisterButtonEvent("claim", new Claim()); CoreClient.RegisterButtonEvent('claim', new Claim());
CoreClient.RegisterButtonEvent("inventory", new InventoryButtonEvent); CoreClient.RegisterButtonEvent('reroll', new Reroll());
CoreClient.RegisterButtonEvent("reroll", new Reroll());
} }
} }

View file

@ -1,5 +1,7 @@
import { ButtonInteraction } from "discord.js"; import { ButtonInteraction } from "discord.js";
export abstract class ButtonEvent { export class ButtonEvent {
abstract execute(interaction: ButtonInteraction): Promise<void>; public execute(interaction: ButtonInteraction) {
}
} }

View file

@ -1,7 +1,9 @@
import { CommandInteraction, SlashCommandBuilder } from "discord.js"; import { CommandInteraction } from "discord.js";
export abstract class Command { export class Command {
public CommandBuilder: Omit<SlashCommandBuilder, "addSubcommand" | "addSubcommandGroup">; public CommandBuilder: any;
public execute(interaction: CommandInteraction) {
abstract execute(interaction: CommandInteraction): Promise<void>; }
} }

View file

@ -19,7 +19,7 @@ export default class Webhooks {
} }
private setupRoutes() { private setupRoutes() {
this.app.post("/api/reload-db", ReloadDB); this.app.post('/api/reload-db', ReloadDB);
} }
private setupListen() { private setupListen() {