Compare commits

..

No commits in common. "091b8e52056cfaf98c791b4cfcb44d9c3b59934f" and "0dc430b7d5090f675f1b3ac02da5ee9c34cb01e0" have entirely different histories.

30 changed files with 719 additions and 1163 deletions

View file

@ -7,15 +7,17 @@
# any secret values. # any secret values.
BOT_TOKEN= BOT_TOKEN=
BOT_VER=0.2 DEV BOT_VER=0.1.8 DEV
BOT_AUTHOR=Vylpes BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
BOT_CLIENTID=682942374040961060 BOT_CLIENTID=682942374040961060
BOT_ENV=4
ABOUT_FUNDING= ABOUT_FUNDING=
ABOUT_REPO= ABOUT_REPO=
DROP_RARITY=-1
DROP_CARD=-1
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3301 DB_PORT=3301
DB_NAME=carddrop DB_NAME=carddrop
@ -25,8 +27,3 @@ DB_SYNC=true
DB_LOGGING=true DB_LOGGING=true
DB_CARD_FILE=:memory: DB_CARD_FILE=:memory:
EXPRESS_PORT=3303
GDRIVESYNC_WHITELIST=147392775707426816,887272961504071690
GDRIVESYNC_AUTO=true

1
.gitignore vendored
View file

@ -108,4 +108,3 @@ config.json
ormconfig.json ormconfig.json
gdrive-credentials.json gdrive-credentials.json
cards/ cards/
*.db

View file

@ -7,15 +7,17 @@
# any secret values. # any secret values.
BOT_TOKEN= BOT_TOKEN=
BOT_VER=0.2 BOT_VER=0.1.8
BOT_AUTHOR=Vylpes BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
BOT_CLIENTID=1093810443589529631 BOT_CLIENTID=1093810443589529631
BOT_ENV=1
ABOUT_FUNDING= ABOUT_FUNDING=
ABOUT_REPO= ABOUT_REPO=
DROP_RARITY=-1
DROP_CARD=-1
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3321 DB_PORT=3321
DB_NAME=carddrop DB_NAME=carddrop
@ -25,8 +27,3 @@ DB_SYNC=false
DB_LOGGING=false DB_LOGGING=false
DB_CARD_FILE=:memory: DB_CARD_FILE=:memory:
EXPRESS_PORT=3323
GDRIVESYNC_WHITELIST=147392775707426816,887272961504071690
GDRIVESYNC_AUTO=false

View file

@ -7,15 +7,17 @@
# any secret values. # any secret values.
BOT_TOKEN= BOT_TOKEN=
BOT_VER=0.2 BETA BOT_VER=0.1.8 BETA
BOT_AUTHOR=Vylpes BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
BOT_CLIENTID=1147976642942214235 BOT_CLIENTID=1147976642942214235
BOT_ENV=2
ABOUT_FUNDING= ABOUT_FUNDING=
ABOUT_REPO= ABOUT_REPO=
DROP_RARITY=-1
DROP_CARD=-1
DB_HOST=127.0.0.1 DB_HOST=127.0.0.1
DB_PORT=3311 DB_PORT=3311
DB_NAME=carddrop DB_NAME=carddrop
@ -25,8 +27,3 @@ DB_SYNC=false
DB_LOGGING=false DB_LOGGING=false
DB_CARD_FILE=:memory: DB_CARD_FILE=:memory:
EXPRESS_PORT=3313
GDRIVESYNC_WHITELIST=147392775707426816,887272961504071690
GDRIVESYNC_AUTO=false

View file

@ -1,8 +0,0 @@
CREATE TABLE `config` (
`Id` VARCHAR(255) NOT NULL,
`WhenCreated` DATETIME NOT NULL,
`WhenUpdated` DATETIME NOT NULL,
`Key` VARCHAR(255) NOT NULL,
`Value` VARCHAR(255) NOT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

View file

@ -1,6 +1,6 @@
{ {
"name": "card-drop", "name": "card-drop",
"version": "0.2.0", "version": "0.1.8",
"main": "./dist/bot.js", "main": "./dist/bot.js",
"typings": "./dist", "typings": "./dist",
"scripts": { "scripts": {
@ -23,23 +23,22 @@
"homepage": "https://gitea.vylpes.xyz/External/card-drop", "homepage": "https://gitea.vylpes.xyz/External/card-drop",
"funding": "https://ko-fi.com/vylpes", "funding": "https://ko-fi.com/vylpes",
"dependencies": { "dependencies": {
"@discordjs/rest": "^2.0.0", "@discordjs/rest": "^1.1.0",
"@types/express": "^4.17.20", "@types/jest": "^29.5.7",
"@types/jest": "^29.0.0",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"body-parser": "^1.20.2",
"discord.js": "^14.3.0", "discord.js": "^14.3.0",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"express": "^4.18.2", "googleapis": "^126.0.0",
"jest": "^29.0.0", "jest": "^29.7.0",
"jest-mock-extended": "^3.0.0", "jest-mock-extended": "^3.0.0",
"minimatch": "9.0.3", "minimatch": "9.0.2",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"sqlite3": "^5.1.6", "sqlite3": "^5.1.6",
"ts-jest": "^29.0.0", "ts-jest": "^29.0.0",
"typeorm": "0.3.17" "typeorm": "0.3.17"
}, },
"resolutions": { "resolutions": {
"**/@babel/traverse": "^7.23.2",
"**/undici": "^5.26.2" "**/undici": "^5.26.2"
}, },
"devDependencies": { "devDependencies": {

View file

@ -1,5 +0,0 @@
#!/bin/bash
cd ~/apps/card-drop/card-drop_stage \
&& rclone sync card-drop-gdrive: ./cards \
&& curl -X POST http://localhost:3313/api/reload-db

View file

@ -4,22 +4,15 @@ import Card from "../database/entities/card/Card";
import Series from "../database/entities/card/Series"; import Series from "../database/entities/card/Series";
import path from "path"; import path from "path";
import { CardRarity, CardRarityToString } from "../constants/CardRarity"; import { CardRarity, CardRarityToString } from "../constants/CardRarity";
import Config from "../database/entities/app/Config";
export default class CardSetupFunction { export default class CardSetupFunction {
public static async Execute() { public async Execute() {
if (await Config.GetValue('safemode') == "true") return;
try {
await this.ClearDatabase(); await this.ClearDatabase();
await this.ReadSeries(); await this.ReadSeries();
await this.ReadCards(); await this.ReadCards();
} catch {
await Config.SetValue('safemode', 'true');
}
} }
private static async ClearDatabase() { private async ClearDatabase() {
const cardRepository = CardDataSource.getRepository(Card); const cardRepository = CardDataSource.getRepository(Card);
await cardRepository.clear(); await cardRepository.clear();
@ -27,7 +20,7 @@ export default class CardSetupFunction {
await seriesRepository.clear(); await seriesRepository.clear();
} }
private static async ReadSeries() { private async ReadSeries() {
const seriesDir = readdirSync(path.join(process.cwd(), 'cards')); const seriesDir = readdirSync(path.join(process.cwd(), 'cards'));
const seriesRepository = CardDataSource.getRepository(Series); const seriesRepository = CardDataSource.getRepository(Series);
@ -48,7 +41,7 @@ export default class CardSetupFunction {
await seriesRepository.save(seriesToSave); await seriesRepository.save(seriesToSave);
} }
private static async ReadCards() { private async ReadCards() {
const loadedSeries = await Series.FetchAll(Series, [ "Cards", "Cards.Series" ]); const loadedSeries = await Series.FetchAll(Series, [ "Cards", "Cards.Series" ]);
const cardRepository = CardDataSource.getRepository(Card); const cardRepository = CardDataSource.getRepository(Card);
@ -76,7 +69,7 @@ export default class CardSetupFunction {
console.log(`Loaded ${cardsToSave.length} cards to database`); console.log(`Loaded ${cardsToSave.length} cards to database`);
} }
private static GenerateCardData(files: string[], rarity: CardRarity, series: Series): Card[] { private GenerateCardData(files: string[], rarity: CardRarity, series: Series): Card[] {
const result: Card[] = []; const result: Card[] = [];
for (let file of files.filter(x => !x.startsWith('.') && (x.endsWith('.png') || x.endsWith('.jpg') || x.endsWith('.gif')))) { for (let file of files.filter(x => !x.startsWith('.') && (x.endsWith('.png') || x.endsWith('.jpg') || x.endsWith('.gif')))) {
@ -93,7 +86,7 @@ export default class CardSetupFunction {
return result; return result;
} }
private static GetCardFiles(rarity: CardRarity, series: Series): string[] { private GetCardFiles(rarity: CardRarity, series: Series): string[] {
const folder = path.join(process.cwd(), 'cards', series.Path, CardRarityToString(rarity).toUpperCase()); const folder = path.join(process.cwd(), 'cards', series.Path, CardRarityToString(rarity).toUpperCase());
const folderExists = existsSync(folder); const folderExists = existsSync(folder);

View file

@ -2,8 +2,6 @@ import * as dotenv from "dotenv";
import { CoreClient } from "./client/client"; import { CoreClient } from "./client/client";
import { IntentsBitField } from "discord.js"; import { IntentsBitField } from "discord.js";
import Registry from "./registry"; import Registry from "./registry";
import { existsSync } from "fs";
import { ExecException, exec } from "child_process";
dotenv.config(); dotenv.config();
@ -13,15 +11,12 @@ const requiredConfigs: string[] = [
"BOT_AUTHOR", "BOT_AUTHOR",
"BOT_OWNERID", "BOT_OWNERID",
"BOT_CLIENTID", "BOT_CLIENTID",
"BOT_ENV",
"DB_HOST", "DB_HOST",
"DB_PORT", "DB_PORT",
"DB_AUTH_USER", "DB_AUTH_USER",
"DB_AUTH_PASS", "DB_AUTH_PASS",
"DB_SYNC", "DB_SYNC",
"DB_LOGGING", "DB_LOGGING",
"EXPRESS_PORT",
"GDRIVESYNC_WHITELIST",
] ]
requiredConfigs.forEach(config => { requiredConfigs.forEach(config => {
@ -39,20 +34,4 @@ Registry.RegisterCommands();
Registry.RegisterEvents(); Registry.RegisterEvents();
Registry.RegisterButtonEvents(); Registry.RegisterButtonEvents();
if (!existsSync(`${process.cwd()}/cards`) && process.env.GDRIVESYNC_AUTO && process.env.GDRIVESYNC_AUTO == 'true') {
console.log("Card directory not found, syncing...");
CoreClient.AllowDrops = false;
exec(`rclone sync card-drop-gdrive: ${process.cwd()}/cards`, async (error: ExecException | null) => {
if (error) {
console.error(error.code);
throw `Error while running sync command. Code: ${error.code}`;
} else {
console.log('Synced successfully.');
CoreClient.AllowDrops = true;
}
});
}
client.start(); client.start();

View file

@ -40,6 +40,6 @@ export default class Claim extends ButtonEvent {
await claim.Save(eClaim, claim); await claim.Save(eClaim, claim);
await interaction.reply(`Card claimed by ${interaction.user}`); await interaction.reply('Card claimed');
} }
} }

View file

@ -1,31 +1,29 @@
import { AttachmentBuilder, ButtonInteraction, DiscordAPIError } from "discord.js"; import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonInteraction, ButtonStyle, CacheType, DiscordAPIError, EmbedBuilder } from "discord.js";
import { ButtonEvent } from "../type/buttonEvent"; import { ButtonEvent } from "../type/buttonEvent";
import CardDropHelper from "../helpers/CardDropHelper"; import CardDropHelper from "../helpers/CardDropHelper";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { CardRarityToColour, CardRarityToString } from "../constants/CardRarity";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { CoreClient } from "../client/client"; import { CoreClient } from "../client/client";
import Inventory from "../database/entities/app/Inventory"; import Card from "../database/entities/card/Card";
import Config from "../database/entities/app/Config";
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) {
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 contunue.');
return;
}
if (!interaction.guild || !interaction.guildId) return; if (!interaction.guild || !interaction.guildId) return;
let randomCard = await CardDropHelper.GetRandomCard(); let randomCard = await CardDropHelper.GetRandomCard();
if (process.env.DROP_RARITY && Number(process.env.DROP_RARITY) > 0) { if (process.env.DROP_RARITY && Number(process.env.DROP_RARITY) > 0) {
randomCard = await CardDropHelper.GetRandomCardByRarity(Number(process.env.DROP_RARITY)); randomCard = await CardDropHelper.GetRandomCardByRarity(Number(process.env.DROP_RARITY));
} else if (process.env.DROP_CARD && process.env.DROP_CARD != '-1') {
let card = await Card.FetchOneByCardNumber(process.env.DROP_CARD, [ "Series" ]);
if (!card) {
await interaction.reply("Card not found");
return;
}
randomCard = card;
} }
const image = readFileSync(randomCard.Path); const image = readFileSync(randomCard.Path);
@ -34,14 +32,26 @@ export default class Reroll extends ButtonEvent {
const attachment = new AttachmentBuilder(image, { name: randomCard.FileName }); const attachment = new AttachmentBuilder(image, { name: randomCard.FileName });
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.CardNumber); const embed = new EmbedBuilder()
const quantityClaimed = inventory ? inventory.Quantity : 0; .setTitle(randomCard.Name)
.setDescription(randomCard.Series.Name)
.setFooter({ text: CardRarityToString(randomCard.Rarity) })
.setColor(CardRarityToColour(randomCard.Rarity))
.setImage(`attachment://${randomCard.FileName}`);
const embed = CardDropHelper.GenerateDropEmbed(randomCard, quantityClaimed || 0); const row = new ActionRowBuilder<ButtonBuilder>();
const claimId = v4(); const claimId = v4();
const row = CardDropHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id); row.addComponents(
new ButtonBuilder()
.setCustomId(`claim ${randomCard.CardNumber} ${claimId} ${interaction.user.id}`)
.setLabel("Claim")
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId(`reroll`)
.setLabel("Reroll")
.setStyle(ButtonStyle.Secondary));
try { try {
await interaction.editReply({ await interaction.editReply({
@ -52,6 +62,7 @@ export default class Reroll extends ButtonEvent {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
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 {

View file

@ -12,8 +12,6 @@ import CardDataSource from "../database/dataSources/cardDataSource";
import IButtonEventItem from "../contracts/IButtonEventItem"; 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 Webhooks from "../webhooks";
export class CoreClient extends Client { export class CoreClient extends Client {
private static _commandItems: ICommandItem[]; private static _commandItems: ICommandItem[];
@ -22,11 +20,9 @@ export class CoreClient extends Client {
private _events: Events; private _events: Events;
private _util: Util; private _util: Util;
private _webhooks: Webhooks; private _cardSetupFunc: CardSetupFunction;
public static ClaimId: string; public static ClaimId: string;
public static Environment: Environment;
public static AllowDrops: boolean;
public static get commandItems(): ICommandItem[] { public static get commandItems(): ICommandItem[] {
return this._commandItems; return this._commandItems;
@ -50,12 +46,7 @@ export class CoreClient extends Client {
this._events = new Events(); this._events = new Events();
this._util = new Util(); this._util = new Util();
this._webhooks = new Webhooks(); this._cardSetupFunc = new CardSetupFunction();
CoreClient.Environment = Number(process.env.BOT_ENV);
console.log(`Bot Environment: ${CoreClient.Environment}`);
CoreClient.AllowDrops = true;
} }
public async start() { public async start() {
@ -75,54 +66,39 @@ export class CoreClient extends Client {
super.on("interactionCreate", this._events.onInteractionCreate); super.on("interactionCreate", this._events.onInteractionCreate);
super.on("ready", this._events.onReady); super.on("ready", this._events.onReady);
await CardSetupFunction.Execute(); await this._cardSetupFunc.Execute();
await super.login(process.env.BOT_TOKEN);
this._util.loadEvents(this, CoreClient._eventItems); this._util.loadEvents(this, CoreClient._eventItems);
this._util.loadSlashCommands(this); this._util.loadSlashCommands(this);
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);
} }
public static RegisterCommand(name: string, command: Command, environment: Environment = Environment.All, serverId?: string) { public static RegisterCommand(name: string, command: Command, serverId?: string) {
const item: ICommandItem = { const item: ICommandItem = {
Name: name, Name: name,
Environment: environment,
Command: command, Command: command,
ServerId: serverId, ServerId: serverId,
}; };
if (environment &= CoreClient.Environment) {
CoreClient._commandItems.push(item); CoreClient._commandItems.push(item);
} }
}
public static RegisterEvent(eventType: EventType, func: Function, environment: Environment = Environment.All) { public static RegisterEvent(eventType: EventType, func: Function) {
const item: IEventItem = { const item: IEventItem = {
EventType: eventType, EventType: eventType,
ExecutionFunction: func, ExecutionFunction: func,
Environment: environment,
}; };
if (environment &= CoreClient.Environment) {
CoreClient._eventItems.push(item); CoreClient._eventItems.push(item);
} }
}
public static RegisterButtonEvent(buttonId: string, event: ButtonEvent, environment: Environment = Environment.All) { public static RegisterButtonEvent(buttonId: string, event: ButtonEvent) {
const item: IButtonEventItem = { const item: IButtonEventItem = {
ButtonId: buttonId, ButtonId: buttonId,
Event: event, Event: event,
Environment: environment,
}; };
if (environment &= CoreClient.Environment) {
CoreClient._buttonEvents.push(item); CoreClient._buttonEvents.push(item);
} }
} }
}

View file

@ -10,15 +10,9 @@ 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: SlashCommandBuilder[] = []; const globalCommandData: SlashCommandBuilder[] = globalCommands
.filter(x => x.Command.CommandBuilder)
for (let command of globalCommands) { .flatMap(x => x.Command.CommandBuilder);
if (!command.Command.CommandBuilder) continue;
if (command.Environment &= CoreClient.Environment) {
globalCommandData.push(command.Command.CommandBuilder);
}
}
const guildIds: string[] = []; const guildIds: string[] = [];
@ -38,15 +32,9 @@ export class Util {
); );
for (let guild of guildIds) { for (let guild of guildIds) {
const guildCommandData: SlashCommandBuilder[] = []; const guildCommandData = guildCommands.filter(x => x.ServerId == guild)
.filter(x => x.Command.CommandBuilder)
for (let command of guildCommands.filter(x => x.ServerId == guild)) { .flatMap(x => x.Command.CommandBuilder);
if (!command.Command.CommandBuilder) continue;
if (command.Environment &= CoreClient.Environment) {
guildCommandData.push(command.Command.CommandBuilder);
}
}
if (!client.guilds.cache.has(guild)) continue; if (!client.guilds.cache.has(guild)) continue;

View file

@ -1,11 +1,11 @@
import { AttachmentBuilder, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js"; import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, DiscordAPIError, EmbedBuilder, SlashCommandBuilder } from "discord.js";
import { Command } from "../type/command"; import { Command } from "../type/command";
import CardDropHelper from "../helpers/CardDropHelper"; import CardDropHelper from "../helpers/CardDropHelper";
import { CardRarityToColour, CardRarityToString } from "../constants/CardRarity";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { CoreClient } from "../client/client"; import { CoreClient } from "../client/client";
import { v4 } from "uuid"; import { v4 } from "uuid";
import Inventory from "../database/entities/app/Inventory"; import Card from "../database/entities/card/Card";
import Config from "../database/entities/app/Config";
export default class Drop extends Command { export default class Drop extends Command {
constructor() { constructor() {
@ -17,33 +17,47 @@ export default class Drop extends Command {
} }
public override async execute(interaction: CommandInteraction) { public override async execute(interaction: CommandInteraction) {
if (!CoreClient.AllowDrops) { let randomCard = await CardDropHelper.GetRandomCard();
await interaction.reply('Bot is currently syncing, please wait until its done.');
if (process.env.DROP_RARITY && Number(process.env.DROP_RARITY) > 0) {
randomCard = await CardDropHelper.GetRandomCardByRarity(Number(process.env.DROP_RARITY));
} else if (process.env.DROP_CARD && process.env.DROP_CARD != '-1') {
let card = await Card.FetchOneByCardNumber(process.env.DROP_CARD, [ "Series" ]);
if (!card) {
await interaction.reply("Card not found");
return; return;
} }
if (await Config.GetValue('safemode') == "true") randomCard = card;
{
await interaction.reply('Safe Mode has been activated, please resync to contunue.');
return;
} }
const randomCard = await CardDropHelper.GetRandomCard();
const image = readFileSync(randomCard.Path); const image = readFileSync(randomCard.Path);
await interaction.deferReply(); await interaction.deferReply();
const attachment = new AttachmentBuilder(image, { name: randomCard.FileName }); const attachment = new AttachmentBuilder(image, { name: randomCard.FileName });
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.CardNumber); const embed = new EmbedBuilder()
const quantityClaimed = inventory ? inventory.Quantity : 0; .setTitle(randomCard.Name)
.setDescription(randomCard.Series.Name)
.setFooter({ text: CardRarityToString(randomCard.Rarity) })
.setColor(CardRarityToColour(randomCard.Rarity))
.setImage(`attachment://${randomCard.FileName}`);
const embed = CardDropHelper.GenerateDropEmbed(randomCard, quantityClaimed || 0); const row = new ActionRowBuilder<ButtonBuilder>();
const claimId = v4(); const claimId = v4();
const row = CardDropHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id); row.addComponents(
new ButtonBuilder()
.setCustomId(`claim ${randomCard.CardNumber} ${claimId} ${interaction.user.id}`)
.setLabel("Claim")
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId(`reroll`)
.setLabel("Reroll")
.setStyle(ButtonStyle.Secondary));
try { try {
await interaction.editReply({ await interaction.editReply({
@ -54,6 +68,7 @@ export default class Drop extends Command {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
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 {

View file

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

View file

@ -1,76 +0,0 @@
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
import { Command } from "../../type/command";
import Card from "../../database/entities/card/Card";
import { readFileSync } from "fs";
import Inventory from "../../database/entities/app/Inventory";
import CardDropHelper from "../../helpers/CardDropHelper";
import { v4 } from "uuid";
import { CoreClient } from "../../client/client";
export default class Dropnumber extends Command {
constructor() {
super();
super.CommandBuilder = new SlashCommandBuilder()
.setName('dropnumber')
.setDescription('(TEST) Summon a specific card')
.addStringOption(x =>
x
.setName('cardnumber')
.setDescription('The card number to summon')
.setRequired(true));
}
public override async execute(interaction: CommandInteraction<CacheType>) {
if (!interaction.isChatInputCommand()) return;
const cardNumber = interaction.options.get('cardnumber');
if (!cardNumber || !cardNumber.value) {
await interaction.reply('Card Number is required');
return;
}
const card = await Card.FetchOneByCardNumber(cardNumber.value.toString(), [
"Series"
]);
if (!card) {
await interaction.reply('Card not found');
return;
}
const image = readFileSync(card.Path);
await interaction.deferReply();
const attachment = new AttachmentBuilder(image, { name: card.FileName });
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.CardNumber);
const quantityClaimed = inventory ? inventory.Quantity : 0;
const embed = CardDropHelper.GenerateDropEmbed(card, quantityClaimed || 0);
const claimId = v4();
const row = CardDropHelper.GenerateDropButtons(card, claimId, interaction.user.id);
try {
await interaction.editReply({
embeds: [ embed ],
files: [ attachment ],
components: [ row ],
});
} catch (e) {
console.error(e);
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}`);
} else {
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening.Code: UNKNOWN`);
}
}
CoreClient.ClaimId = claimId;
}
}

View file

@ -1,81 +0,0 @@
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
import { Command } from "../../type/command";
import { CardRarity, CardRarityParse } from "../../constants/CardRarity";
import CardDropHelper from "../../helpers/CardDropHelper";
import { readFileSync } from "fs";
import Inventory from "../../database/entities/app/Inventory";
import { v4 } from "uuid";
import { CoreClient } from "../../client/client";
export default class Droprarity extends Command {
constructor() {
super();
super.CommandBuilder = new SlashCommandBuilder()
.setName('droprarity')
.setDescription('(TEST) Summon a random card of a specific rarity')
.addStringOption(x =>
x
.setName('rarity')
.setDescription('The rarity you want to summon')
.setRequired(true));
}
public override async execute(interaction: CommandInteraction<CacheType>) {
if (!interaction.isChatInputCommand()) return;
const rarity = interaction.options.get('rarity');
if (!rarity || !rarity.value) {
await interaction.reply('Rarity is required');
return;
}
const rarityType = CardRarityParse(rarity.value.toString());
if (rarityType == CardRarity.Unknown) {
await interaction.reply('Invalid rarity');
return;
}
const card = await CardDropHelper.GetRandomCardByRarity(rarityType);
if (!card) {
await interaction.reply('Card not found');
return;
}
const image = readFileSync(card.Path);
await interaction.deferReply();
const attachment = new AttachmentBuilder(image, { name: card.FileName });
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.CardNumber);
const quantityClaimed = inventory ? inventory.Quantity : 0;
const embed = CardDropHelper.GenerateDropEmbed(card, quantityClaimed || 0);
const claimId = v4();
const row = CardDropHelper.GenerateDropButtons(card, claimId, interaction.user.id);
try {
await interaction.editReply({
embeds: [ embed ],
files: [ attachment ],
components: [ row ],
});
} catch (e) {
console.error(e);
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}`);
} else {
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. Code: UNKNOWN`);
}
}
CoreClient.ClaimId = claimId;
}
}

View file

@ -42,20 +42,3 @@ export function CardRarityToColour(rarity: CardRarity): number {
return EmbedColours.MangaCard; return EmbedColours.MangaCard;
} }
} }
export function CardRarityParse(rarity: string): CardRarity {
switch (rarity.toLowerCase()) {
case "bronze":
return CardRarity.Bronze;
case "silver":
return CardRarity.Silver;
case "gold":
return CardRarity.Gold;
case "legendary":
return CardRarity.Legendary;
case "manga":
return CardRarity.Manga;
default:
return CardRarity.Unknown;
}
}

View file

@ -1,9 +0,0 @@
export enum Environment {
None = 0,
Production = 1 << 0,
Stage = 1 << 1,
Local = 1 << 2,
All = Production | Stage | Local,
Test = Stage | Local,
}

View file

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

View file

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

View file

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

View file

@ -1,53 +0,0 @@
import { Column, Entity } from "typeorm";
import AppBaseEntity from "../../../contracts/AppBaseEntity";
import AppDataSource from "../../dataSources/appDataSource";
@Entity()
export default class Config extends AppBaseEntity {
constructor(key: string, value: string) {
super();
this.Key = key;
this.Value = value;
}
@Column()
Key: string;
@Column()
Value: string;
public SetValue(value: string) {
this.Value = value;
}
public static async FetchOneByKey(key: string): Promise<Config | null> {
const repository = AppDataSource.getRepository(Config);
const single = await repository.findOne({ where: { Key: key }});
return single;
}
public static async GetValue(key: string): Promise<string | undefined> {
const config = await Config.FetchOneByKey(key);
if (!config) return undefined;
return config.Value;
}
public static async SetValue(key: string, value: string) {
const config = await Config.FetchOneByKey(key);
if (!config) {
const newConfig = new Config(key, value);
await newConfig.Save(Config, newConfig);
} else {
config.SetValue(value);
await config.Save(Config, config);
}
}
}

View file

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

View file

@ -1,7 +1,7 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js"; import { CardRarity } from "../constants/CardRarity";
import { CardRarity, CardRarityToColour, CardRarityToString } from "../constants/CardRarity";
import CardRarityChances from "../constants/CardRarityChances"; import CardRarityChances from "../constants/CardRarityChances";
import Card from "../database/entities/card/Card"; import Card from "../database/entities/card/Card";
import Series from "../database/entities/card/Series";
export default class CardDropHelper { export default class CardDropHelper {
public static async GetRandomCard(): Promise<Card> { public static async GetRandomCard(): Promise<Card> {
@ -20,7 +20,18 @@ export default class CardDropHelper {
else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga; else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga;
else cardRarity = CardRarity.Legendary; else cardRarity = CardRarity.Legendary;
const randomCard = await this.GetRandomCardByRarity(cardRarity); const allSeries = await Series.FetchAll(Series, [ "Cards", "Cards.Series" ]);
const allSeriesWithCards = allSeries.filter(x => x.Cards.length > 0 && x.Cards.find(x => x.Rarity == cardRarity));
const randomSeriesIndex = Math.floor(Math.random() * allSeriesWithCards.length);
const randomSeries = allSeriesWithCards[randomSeriesIndex];
const allCards = randomSeries.Cards.filter(x => x.Rarity == cardRarity && x.Path && x.FileName);
const randomCardIndex = Math.floor(Math.random() * allCards.length);
const randomCard = allCards[randomCardIndex];
return randomCard; return randomCard;
} }
@ -34,30 +45,4 @@ export default class CardDropHelper {
return card; return card;
} }
public static GenerateDropEmbed(card: Card, quantityClaimed: Number): EmbedBuilder {
let description = "";
description += `Series: ${card.Series.Name}\n`;
description += `Claimed: ${quantityClaimed}\n`;
return new EmbedBuilder()
.setTitle(card.Name)
.setDescription(description)
.setFooter({ text: CardRarityToString(card.Rarity) })
.setColor(CardRarityToColour(card.Rarity))
.setImage(`attachment://${card.FileName}`);
}
public static GenerateDropButtons(card: Card, claimId: string, userId: string): ActionRowBuilder<ButtonBuilder> {
return new ActionRowBuilder<ButtonBuilder>()
.addComponents(
new ButtonBuilder()
.setCustomId(`claim ${card.CardNumber} ${claimId} ${userId}`)
.setLabel("Claim")
.setStyle(ButtonStyle.Primary),
new ButtonBuilder()
.setCustomId(`reroll`)
.setLabel("Reroll")
.setStyle(ButtonStyle.Secondary));
}
} }

View file

@ -0,0 +1,83 @@
import { Auth, drive_v3, google } from "googleapis";
import IGDriveFolderListing from "../contracts/IGDriveFolderListing";
import path, { resolve } from "path";
import os from 'os';
import uuid, { v4 } from 'uuid';
import { createWriteStream } from "fs";
export default class GoogleDriveHelper {
private _auth: Auth.GoogleAuth;
private _drive: drive_v3.Drive;
constructor() {
this._auth = new google.auth.GoogleAuth({
keyFile: "gdrive-credentials.json",
scopes: [
"https://www.googleapis.com/auth/drive.readonly",
"https://www.googleapis.com/auth/drive.metadata.readonly",
],
});
this._drive = google.drive( { version: "v3", auth: this._auth });
}
public async listFolder(folderId: string, pageSize: number): Promise<IGDriveFolderListing[]> {
const params = {
pageSize: pageSize,
fields: "nextPageToken, files(id, name)",
q: `'${folderId}' in parents and trashed=false`
}
const res = await this._drive.files.list(params);
return res.data.files as IGDriveFolderListing[];
}
public downloadFile(fileId: string) {
const res = this._drive.files.get({
fileId: fileId,
alt: 'media',
}, {
responseType: 'stream',
})
.then(res => {
return new Promise((resolve, reject) => {
const filePath = path.join(process.cwd(), 'temp', v4());
const dest = createWriteStream(filePath);
let progress = 0;
res.data
.on('end', () => {
resolve(filePath);
})
.on('error', err => {
reject(err);
})
.on('data', d => {
progress += d.length;
})
.pipe(dest);
});
})
}
public async exportFile(fileId: string, mimeType: string) {
const destPath = path.join(process.cwd(), 'temp', v4());
const dest = createWriteStream(destPath);
const res = await this._drive.files.export({
fileId: fileId,
mimeType: mimeType
}, {
responseType: 'stream',
});
await new Promise((resolve, reject) => {
res.data
.on('error', reject)
.pipe(dest)
.on('error', reject)
.on('finish', resolve);
})
}
}

View file

@ -1,10 +0,0 @@
import { Request, Response } from "express";
import CardSetupFunction from "../Functions/CardSetupFunction";
export default async function ReloadDB(req: Request, res: Response) {
console.log('Reloading Card DB...');
await CardSetupFunction.Execute();
res.sendStatus(200);
}

View file

@ -1,29 +1,15 @@
import { CoreClient } from "./client/client"; import { CoreClient } from "./client/client";
// 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";
// Test Command Imports
import Dropnumber from "./commands/stage/dropnumber";
import Droprarity from "./commands/stage/droprarity";
// Button Event Imports
import Claim from "./buttonEvents/Claim"; import Claim from "./buttonEvents/Claim";
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
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());
// Test Commands
CoreClient.RegisterCommand('dropnumber', new Dropnumber(), Environment.Test);
CoreClient.RegisterCommand('droprarity', new Droprarity(), Environment.Test);
} }
public static RegisterEvents() { public static RegisterEvents() {

View file

@ -1,30 +0,0 @@
import bodyParser from "body-parser";
import express, { Application } from "express";
import ReloadDB from "./hooks/ReloadDB";
export default class Webhooks {
private app: Application;
private port = process.env.EXPRESS_PORT!;
public start() {
this.setupApp();
this.setupRoutes();
this.setupListen();
}
private setupApp() {
this.app = express();
this.app.use(bodyParser.json());
}
private setupRoutes() {
this.app.post('/api/reload-db', ReloadDB);
}
private setupListen() {
this.app.listen(this.port, () => {
console.log(`API listening on port ${this.port}`);
});
}
}

1120
yarn.lock

File diff suppressed because it is too large Load diff