Compare commits

..

No commits in common. "2ddb170b48f0b827341321dd8d34256f4901db8e" and "b9c0151285d3e1a97e8e5835f61112513a6319ed" have entirely different histories.

26 changed files with 6134 additions and 10530 deletions

View file

@ -7,7 +7,7 @@
# any secret values. # any secret values.
BOT_TOKEN= BOT_TOKEN=
BOT_VER=0.3.0 DEV BOT_VER=0.2.1 DEV
BOT_AUTHOR=Vylpes BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
BOT_CLIENTID=682942374040961060 BOT_CLIENTID=682942374040961060

View file

@ -51,14 +51,14 @@ steps:
- name: build - name: build
image: node image: node
commands: commands:
- npm ci - yarn install --frozen-lockfile
- npm run build - yarn build
- name: test - name: test
image: node image: node
commands: commands:
- npm ci - yarn install --frozen-lockfile
- npm test - yarn test
trigger: trigger:
branch: branch:

View file

@ -7,7 +7,7 @@
# any secret values. # any secret values.
BOT_TOKEN= BOT_TOKEN=
BOT_VER=0.3.0 BOT_VER=0.2.1
BOT_AUTHOR=Vylpes BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
BOT_CLIENTID=1093810443589529631 BOT_CLIENTID=1093810443589529631

View file

@ -7,7 +7,7 @@
# any secret values. # any secret values.
BOT_TOKEN= BOT_TOKEN=
BOT_VER=0.3.0 BETA BOT_VER=0.2.1 BETA
BOT_AUTHOR=Vylpes BOT_AUTHOR=Vylpes
BOT_OWNERID=147392775707426816 BOT_OWNERID=147392775707426816
BOT_CLIENTID=1147976642942214235 BOT_CLIENTID=1147976642942214235

10331
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.3.0", "version": "0.2.1",
"main": "./dist/bot.js", "main": "./dist/bot.js",
"typings": "./dist", "typings": "./dist",
"scripts": { "scripts": {
@ -31,11 +31,11 @@
"discord.js": "^14.3.0", "discord.js": "^14.3.0",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"express": "^4.18.2", "express": "^4.18.2",
"glob": "^10.3.10",
"jest": "^29.0.0", "jest": "^29.0.0",
"jest-mock-extended": "^3.0.0", "jest-mock-extended": "^3.0.0",
"minimatch": "9.0.3", "minimatch": "9.0.3",
"mysql": "^2.18.1", "mysql": "^2.18.1",
"sqlite3": "^5.1.6",
"ts-jest": "^29.0.0", "ts-jest": "^29.0.0",
"typeorm": "0.3.17" "typeorm": "0.3.17"
}, },

View file

@ -13,11 +13,11 @@ cd ~/apps/card-drop/card-drop_prod \
&& (pm2 stop card-drop_prod || true) \ && (pm2 stop card-drop_prod || true) \
&& (pm2 delete card-drop_prod || true) \ && (pm2 delete card-drop_prod || true) \
&& cp .prod.env .env \ && cp .prod.env .env \
&& npm run clean \ && yarn clean \
&& npm ci \ && yarn install --frozen-lockfile \
&& npm run build \ && yarn build \
&& docker compose --file docker-compose.prod.yml up -d \ && docker compose --file docker-compose.prod.yml up -d \
&& echo "Sleeping for 10 seconds to let database load..." \ && echo "Sleeping for 10 seconds to let database load..." \
&& sleep 10 \ && sleep 10 \
&& npm run db:up \ && yarn run db:up \
&& NODE_ENV=production pm2 start --name card-drop_prod dist/bot.js && NODE_ENV=production pm2 start --name card-drop_prod dist/bot.js

View file

@ -13,11 +13,11 @@ cd ~/apps/card-drop/card-drop_stage \
&& (pm2 stop card-drop_stage || true) \ && (pm2 stop card-drop_stage || true) \
&& (pm2 delete card-drop_stage || true) \ && (pm2 delete card-drop_stage || true) \
&& cp .stage.env .env \ && cp .stage.env .env \
&& npm run clean \ && yarn clean \
&& npm ci \ && yarn install --frozen-lockfile \
&& npm run build \ && yarn build \
&& docker compose --file docker-compose.stage.yml up -d \ && docker compose --file docker-compose.stage.yml up -d \
&& echo "Sleeping for 10 seconds to let database load..." \ && echo "Sleeping for 10 seconds to let database load..." \
&& sleep 10 \ && sleep 10 \
&& npm run db:up \ && yarn run db:up \
&& NODE_ENV=production pm2 start --name card-drop_stage dist/bot.js && NODE_ENV=production pm2 start --name card-drop_stage dist/bot.js

View file

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

View file

@ -0,0 +1,105 @@
import { existsSync, readdirSync } from "fs";
import CardDataSource from "../database/dataSources/cardDataSource";
import Card from "../database/entities/card/Card";
import Series from "../database/entities/card/Series";
import path from "path";
import { CardRarity, CardRarityToString } from "../constants/CardRarity";
import Config from "../database/entities/app/Config";
export default class CardSetupFunction {
public static async Execute(): Promise<boolean> {
if (await Config.GetValue('safemode') == "true") return false;
try {
await this.ClearDatabase();
await this.ReadSeries();
await this.ReadCards();
} catch {
await Config.SetValue('safemode', 'true');
return false;
}
return true;
}
private static async ClearDatabase() {
const cardRepository = CardDataSource.getRepository(Card);
await cardRepository.clear();
const seriesRepository = CardDataSource.getRepository(Series);
await seriesRepository.clear();
}
private static async ReadSeries() {
const seriesDir = readdirSync(path.join(process.cwd(), 'cards'));
const seriesRepository = CardDataSource.getRepository(Series);
const seriesToSave: Series[] = [];
for (let dir of seriesDir) {
const dirPart = dir.split(' ');
const seriesId = dirPart.shift();
const seriesName = dirPart.join(' ');
const series = new Series(seriesId!, seriesName, dir);
seriesToSave.push(series);
}
await seriesRepository.save(seriesToSave);
}
private static async ReadCards() {
const loadedSeries = await Series.FetchAll(Series, [ "Cards", "Cards.Series" ]);
const cardRepository = CardDataSource.getRepository(Card);
const cardsToSave: Card[] = [];
for (let series of loadedSeries) {
const cardDirBronze = this.GetCardFiles(CardRarity.Bronze, series);
const cardDirGold = this.GetCardFiles(CardRarity.Gold, series);
const cardDirLegendary = this.GetCardFiles(CardRarity.Legendary, series);
const cardDirSilver = this.GetCardFiles(CardRarity.Silver, series);
const cardDirManga = this.GetCardFiles(CardRarity.Manga, series);
cardsToSave.push(
...this.GenerateCardData(cardDirBronze, CardRarity.Bronze, series),
...this.GenerateCardData(cardDirGold, CardRarity.Gold, series),
...this.GenerateCardData(cardDirLegendary, CardRarity.Legendary, series),
...this.GenerateCardData(cardDirSilver, CardRarity.Silver, series),
...this.GenerateCardData(cardDirManga, CardRarity.Manga, series)
);
}
await cardRepository.save(cardsToSave);
console.log(`Loaded ${cardsToSave.length} cards to database`);
}
private static GenerateCardData(files: string[], rarity: CardRarity, series: Series): Card[] {
const result: Card[] = [];
for (let file of files.filter(x => !x.startsWith('.') && (x.endsWith('.png') || x.endsWith('.jpg') || x.endsWith('.gif')))) {
const filePart = file.split('.');
const cardId = filePart[0];
const cardName = filePart[0];
const card = new Card(cardId, cardName, rarity, path.join(process.cwd(), 'cards', series.Path, CardRarityToString(rarity).toUpperCase(), file), file, series);
result.push(card);
}
return result;
}
private static GetCardFiles(rarity: CardRarity, series: Series): string[] {
const folder = path.join(process.cwd(), 'cards', series.Path, CardRarityToString(rarity).toUpperCase());
const folderExists = existsSync(folder);
return folderExists ? readdirSync(folder) : [];
}
}

View file

@ -1,12 +1,11 @@
import { AttachmentBuilder, ButtonInteraction, DiscordAPIError } from "discord.js"; import { AttachmentBuilder, ButtonInteraction, DiscordAPIError } from "discord.js";
import { ButtonEvent } from "../type/buttonEvent"; import { ButtonEvent } from "../type/buttonEvent";
import CardDropHelper from "../helpers/CardDropHelper";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
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 Inventory from "../database/entities/app/Inventory";
import Config from "../database/entities/app/Config"; import Config from "../database/entities/app/Config";
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
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) {
@ -15,40 +14,34 @@ export default class Reroll extends ButtonEvent {
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 contunue.');
return; return;
} }
const randomCard = CardDropHelperMetadata.GetRandomCard(); if (!interaction.guild || !interaction.guildId) return;
if (!randomCard) { let randomCard = await CardDropHelper.GetRandomCard();
await interaction.reply('Unable to fetch card, please try again.');
return; if (process.env.DROP_RARITY && Number(process.env.DROP_RARITY) > 0) {
randomCard = await CardDropHelper.GetRandomCardByRarity(Number(process.env.DROP_RARITY));
} }
let image: Buffer; const image = readFileSync(randomCard.Path);
const imageFileName = randomCard.card.path.split("/").pop()!;
try {
image = readFileSync(path.join(process.cwd(), 'cards', randomCard.card.path));
} catch {
await interaction.reply(`Unable to fetch image for card ${randomCard.card.id}`);
return;
}
await interaction.deferReply(); await interaction.deferReply();
const attachment = new AttachmentBuilder(image, { name: imageFileName }); const attachment = new AttachmentBuilder(image, { name: randomCard.FileName });
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.CardNumber);
const quantityClaimed = inventory ? inventory.Quantity : 0; const quantityClaimed = inventory ? inventory.Quantity : 0;
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName); const embed = CardDropHelper.GenerateDropEmbed(randomCard, quantityClaimed || 0);
const claimId = v4(); const claimId = v4();
const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id); const row = CardDropHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id);
try { try {
await interaction.editReply({ await interaction.editReply({

View file

@ -7,13 +7,13 @@ import { Command } from "../type/command";
import { Events } from "./events"; import { Events } from "./events";
import { Util } from "./util"; import { Util } from "./util";
import CardSetupFunction from "../Functions/CardSetupFunction";
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 { Environment } from "../constants/Environment";
import Webhooks from "../webhooks"; import Webhooks from "../webhooks";
import CardMetadataFunction from "../Functions/CardMetadataFunction";
import SeriesMetadata from "../contracts/SeriesMetadata";
export class CoreClient extends Client { export class CoreClient extends Client {
private static _commandItems: ICommandItem[]; private static _commandItems: ICommandItem[];
@ -27,7 +27,6 @@ export class CoreClient extends Client {
public static ClaimId: string; public static ClaimId: string;
public static Environment: Environment; public static Environment: Environment;
public static AllowDrops: boolean; public static AllowDrops: boolean;
public static Cards: SeriesMetadata[];
public static get commandItems(): ICommandItem[] { public static get commandItems(): ICommandItem[] {
return this._commandItems; return this._commandItems;
@ -69,10 +68,14 @@ export class CoreClient extends Client {
.then(() => console.log("App Data Source Initialised")) .then(() => console.log("App Data Source Initialised"))
.catch(err => console.error("Error initialising App Data Source", err)); .catch(err => console.error("Error initialising App Data Source", err));
await CardDataSource.initialize()
.then(() => console.log("Card Data Source Initialised"))
.catch(err => console.error("Error initialising Card Data Source", err));
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 CardMetadataFunction.Execute(true); await CardSetupFunction.Execute();
this._util.loadEvents(this, CoreClient._eventItems); this._util.loadEvents(this, CoreClient._eventItems);
this._util.loadSlashCommands(this); this._util.loadSlashCommands(this);

View file

@ -1,12 +1,11 @@
import { AttachmentBuilder, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js"; import { AttachmentBuilder, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
import { Command } from "../type/command"; import { Command } from "../type/command";
import CardDropHelper from "../helpers/CardDropHelper";
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 Inventory from "../database/entities/app/Inventory";
import Config from "../database/entities/app/Config"; import Config from "../database/entities/app/Config";
import CardDropHelperMetadata from "../helpers/CardDropHelperMetadata";
import path from "path";
export default class Drop extends Command { export default class Drop extends Command {
constructor() { constructor() {
@ -23,40 +22,28 @@ export default class Drop extends Command {
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 contunue.');
return; return;
} }
const randomCard = CardDropHelperMetadata.GetRandomCard(); const randomCard = await CardDropHelper.GetRandomCard();
if (!randomCard) { const image = readFileSync(randomCard.Path);
await interaction.reply('Unable to fetch card, please try again.');
return;
}
let image: Buffer;
const imageFileName = randomCard.card.path.split("/").pop()!;
try {
image = readFileSync(path.join(process.cwd(), 'cards', randomCard.card.path));
} catch {
await interaction.reply(`Unable to fetch image for card ${randomCard.card.id}`);
return;
}
await interaction.deferReply(); await interaction.deferReply();
const attachment = new AttachmentBuilder(image, { name: imageFileName }); const attachment = new AttachmentBuilder(image, { name: randomCard.FileName });
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id); const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.CardNumber);
const quantityClaimed = inventory ? inventory.Quantity : 0; const quantityClaimed = inventory ? inventory.Quantity : 0;
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName); const embed = CardDropHelper.GenerateDropEmbed(randomCard, quantityClaimed || 0);
const claimId = v4(); const claimId = v4();
const row = CardDropHelperMetadata.GenerateDropButtons(randomCard, claimId, interaction.user.id); const row = CardDropHelper.GenerateDropButtons(randomCard, claimId, interaction.user.id);
try { try {
await interaction.editReply({ await interaction.editReply({

View file

@ -1,9 +1,9 @@
import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js"; import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
import { Command } from "../type/command"; import { Command } from "../type/command";
import { ExecException, exec } from "child_process"; import { ExecException, exec } from "child_process";
import CardSetupFunction from "../Functions/CardSetupFunction";
import { CoreClient } from "../client/client"; import { CoreClient } from "../client/client";
import Config from "../database/entities/app/Config"; import Config from "../database/entities/app/Config";
import CardMetadataFunction from "../Functions/CardMetadataFunction";
export default class Gdrivesync extends Command { export default class Gdrivesync extends Command {
constructor() { constructor() {
@ -34,8 +34,7 @@ export default class Gdrivesync extends Command {
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 CardSetupFunction.Execute();
await interaction.editReply('Synced successfully.'); await interaction.editReply('Synced successfully.');
CoreClient.AllowDrops = true; CoreClient.AllowDrops = true;

View file

@ -1,7 +1,7 @@
import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js"; import { CacheType, CommandInteraction, PermissionsBitField, SlashCommandBuilder } from "discord.js";
import { Command } from "../type/command"; import { Command } from "../type/command";
import CardSetupFunction from "../Functions/CardSetupFunction";
import Config from "../database/entities/app/Config"; import Config from "../database/entities/app/Config";
import CardMetadataFunction from "../Functions/CardMetadataFunction";
export default class Resync extends Command { export default class Resync extends Command {
constructor() { constructor() {
@ -23,9 +23,7 @@ export default class Resync extends Command {
return; return;
} }
let result = await CardMetadataFunction.Execute(true); if (await CardSetupFunction.Execute()) {
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.");

View file

@ -1,11 +1,11 @@
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js"; import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
import { Command } from "../../type/command"; import { Command } from "../../type/command";
import Card from "../../database/entities/card/Card";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import Inventory from "../../database/entities/app/Inventory"; import Inventory from "../../database/entities/app/Inventory";
import CardDropHelper from "../../helpers/CardDropHelper";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { CoreClient } from "../../client/client"; import { CoreClient } from "../../client/client";
import path from "path";
import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata";
export default class Dropnumber extends Command { export default class Dropnumber extends Command {
constructor() { constructor() {
@ -31,40 +31,29 @@ export default class Dropnumber extends Command {
return; return;
} }
const card = CoreClient.Cards const card = await Card.FetchOneByCardNumber(cardNumber.value.toString(), [
.flatMap(x => x.cards) "Series"
.find(x => x.id == cardNumber.value); ]);
if (!card) { if (!card) {
await interaction.reply('Card not found'); await interaction.reply('Card not found');
return; return;
} }
const series = CoreClient.Cards const image = readFileSync(card.Path);
.find(x => x.cards.includes(card))!;
let image: Buffer;
const imageFileName = card.path.split("/").pop()!;
try {
image = readFileSync(path.join(process.cwd(), 'cards', card.path));
} catch {
await interaction.reply(`Unable to fetch image for card ${card.id}`);
return;
}
await interaction.deferReply(); await interaction.deferReply();
const attachment = new AttachmentBuilder(image, { name: imageFileName }); const attachment = new AttachmentBuilder(image, { name: card.FileName });
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.id); const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.CardNumber);
const quantityClaimed = inventory ? inventory.Quantity : 0; const quantityClaimed = inventory ? inventory.Quantity : 0;
const embed = CardDropHelperMetadata.GenerateDropEmbed({ card, series }, quantityClaimed, imageFileName); const embed = CardDropHelper.GenerateDropEmbed(card, quantityClaimed || 0);
const claimId = v4(); const claimId = v4();
const row = CardDropHelperMetadata.GenerateDropButtons({ card, series }, claimId, interaction.user.id); const row = CardDropHelper.GenerateDropButtons(card, claimId, interaction.user.id);
try { try {
await interaction.editReply({ await interaction.editReply({

View file

@ -1,12 +1,11 @@
import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js"; import { AttachmentBuilder, CacheType, CommandInteraction, DiscordAPIError, SlashCommandBuilder } from "discord.js";
import { Command } from "../../type/command"; import { Command } from "../../type/command";
import { CardRarity, CardRarityParse } from "../../constants/CardRarity"; import { CardRarity, CardRarityParse } from "../../constants/CardRarity";
import CardDropHelper from "../../helpers/CardDropHelper";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import Inventory from "../../database/entities/app/Inventory"; import Inventory from "../../database/entities/app/Inventory";
import { v4 } from "uuid"; import { v4 } from "uuid";
import { CoreClient } from "../../client/client"; import { CoreClient } from "../../client/client";
import CardDropHelperMetadata from "../../helpers/CardDropHelperMetadata";
import path from "path";
export default class Droprarity extends Command { export default class Droprarity extends Command {
constructor() { constructor() {
@ -39,35 +38,27 @@ export default class Droprarity extends Command {
return; return;
} }
const card = await CardDropHelperMetadata.GetRandomCardByRarity(rarityType); const card = await CardDropHelper.GetRandomCardByRarity(rarityType);
if (!card) { if (!card) {
await interaction.reply('Card not found'); await interaction.reply('Card not found');
return; return;
} }
let image: Buffer; const image = readFileSync(card.Path);
const imageFileName = card.card.path.split("/").pop()!;
try {
image = readFileSync(path.join(process.cwd(), 'cards', card.card.path));
} catch {
await interaction.reply(`Unable to fetch image for card ${card.card.id}`);
return;
}
await interaction.deferReply(); await interaction.deferReply();
const attachment = new AttachmentBuilder(image, { name: imageFileName }); const attachment = new AttachmentBuilder(image, { name: card.FileName });
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.card.id); const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, card.CardNumber);
const quantityClaimed = inventory ? inventory.Quantity : 0; const quantityClaimed = inventory ? inventory.Quantity : 0;
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, quantityClaimed, imageFileName); const embed = CardDropHelper.GenerateDropEmbed(card, quantityClaimed || 0);
const claimId = v4(); const claimId = v4();
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id); const row = CardDropHelper.GenerateDropButtons(card, claimId, interaction.user.id);
try { try {
await interaction.editReply({ await interaction.editReply({

View file

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

View file

@ -1,19 +0,0 @@
import { CardRarity } from "../constants/CardRarity";
export default interface SeriesMetadata {
id: number,
name: string,
cards: CardMetadata[],
}
export interface CardMetadata {
id: string,
name: string,
type: CardRarity,
path: string,
}
export interface DropResult {
series: SeriesMetadata,
card: CardMetadata,
}

View file

@ -0,0 +1,22 @@
import { DataSource } from "typeorm";
import * as dotenv from "dotenv";
dotenv.config();
const CardDataSource = new DataSource({
type: "sqlite",
database: process.env.DB_CARD_FILE!,
synchronize: true,
logging: process.env.DB_LOGGING == "true",
entities: [
"dist/database/entities/card/**/*.js",
],
migrations: [
"dist/database/migrations/card/**/*.js",
],
subscribers: [
"dist/database/subscribers/card/**/*.js",
],
});
export default CardDataSource;

View file

@ -0,0 +1,53 @@
import { Column, Entity, ManyToOne } from "typeorm";
import CardBaseEntity from "../../../contracts/CardBaseEntity";
import { CardRarity } from "../../../constants/CardRarity";
import Series from "./Series";
import CardDataSource from "../../dataSources/cardDataSource";
@Entity()
export default class Card extends CardBaseEntity {
constructor(cardNumber: string, name: string, rarity: CardRarity, path: string, fileName: string, series: Series) {
super();
this.CardNumber = cardNumber;
this.Name = name;
this.Rarity = rarity;
this.Path = path;
this.FileName = fileName;
this.Series = series;
}
@Column()
CardNumber: string;
@Column()
Name: string;
@Column()
Rarity: CardRarity;
@Column()
Path: string;
@Column()
FileName: string;
@ManyToOne(() => Series, x => x.Cards)
Series: Series;
public static async FetchOneByCardNumber(cardNumber: string, relations?: string[]): Promise<Card | null> {
const repository = CardDataSource.getRepository(Card);
const single = await repository.findOne({ where: { CardNumber: cardNumber }, relations: relations || [] });
return single;
}
public static async FetchAllByRarity(rarity: CardRarity, relations?: string[]): Promise<Card[]> {
const repository = CardDataSource.getRepository(Card);
const all = await repository.find({ where: { Rarity: rarity }, relations: relations || [] });
return all;
}
}

View file

@ -0,0 +1,23 @@
import { Column, Entity, OneToMany } from "typeorm";
import CardBaseEntity from "../../../contracts/CardBaseEntity";
import Card from "./Card";
@Entity()
export default class Series extends CardBaseEntity {
constructor(id: string, name: string, path: string) {
super();
this.Id = id;
this.Name = name;
this.Path = path;
}
@Column()
Name: string;
@Column()
Path: string;
@OneToMany(() => Card, x => x.Series)
Cards: Card[];
}

View file

View file

@ -1,11 +1,10 @@
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 { DropResult } from "../contracts/SeriesMetadata"; import Card from "../database/entities/card/Card";
import { CoreClient } from "../client/client";
export default class CardDropHelperMetadata { export default class CardDropHelper {
public static GetRandomCard(): DropResult | undefined { public static async GetRandomCard(): Promise<Card> {
const randomRarity = Math.random() * 100; const randomRarity = Math.random() * 100;
let cardRarity: CardRarity; let cardRarity: CardRarity;
@ -21,50 +20,39 @@ export default class CardDropHelperMetadata {
else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga; else if (randomRarity < mangaChance) cardRarity = CardRarity.Manga;
else cardRarity = CardRarity.Legendary; else cardRarity = CardRarity.Legendary;
const randomCard = this.GetRandomCardByRarity(cardRarity); const randomCard = await this.GetRandomCardByRarity(cardRarity);
return randomCard; return randomCard;
} }
public static GetRandomCardByRarity(rarity: CardRarity): DropResult | undefined { public static async GetRandomCardByRarity(rarity: CardRarity): Promise<Card> {
const allCards = CoreClient.Cards const allCards = await Card.FetchAllByRarity(rarity, [ "Series" ]);
.flatMap(x => x.cards)
.filter(x => x.type == rarity);
const randomCardIndex = Math.floor(Math.random() * allCards.length); const randomCardIndex = Math.floor(Math.random() * allCards.length);
const card = allCards[randomCardIndex]; const card = allCards[randomCardIndex];
const series = CoreClient.Cards
.find(x => x.cards.includes(card));
if (!series) { return card;
return undefined;
} }
return { public static GenerateDropEmbed(card: Card, quantityClaimed: Number): EmbedBuilder {
series: series,
card: card,
};
}
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: Number, imageFileName: string): EmbedBuilder {
let description = ""; let description = "";
description += `Series: ${drop.series.name}\n`; description += `Series: ${card.Series.Name}\n`;
description += `Claimed: ${quantityClaimed}\n`; description += `Claimed: ${quantityClaimed}\n`;
return new EmbedBuilder() return new EmbedBuilder()
.setTitle(drop.card.name) .setTitle(card.Name)
.setDescription(description) .setDescription(description)
.setFooter({ text: CardRarityToString(drop.card.type) }) .setFooter({ text: CardRarityToString(card.Rarity) })
.setColor(CardRarityToColour(drop.card.type)) .setColor(CardRarityToColour(card.Rarity))
.setImage(`attachment://${imageFileName}`); .setImage(`attachment://${card.FileName}`);
} }
public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string): ActionRowBuilder<ButtonBuilder> { public static GenerateDropButtons(card: Card, claimId: string, userId: string): ActionRowBuilder<ButtonBuilder> {
return new ActionRowBuilder<ButtonBuilder>() return new ActionRowBuilder<ButtonBuilder>()
.addComponents( .addComponents(
new ButtonBuilder() new ButtonBuilder()
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`) .setCustomId(`claim ${card.CardNumber} ${claimId} ${userId}`)
.setLabel("Claim") .setLabel("Claim")
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
new ButtonBuilder() new ButtonBuilder()

View file

@ -1,10 +1,10 @@
import { Request, Response } from "express"; import { Request, Response } from "express";
import CardMetadataFunction from "../Functions/CardMetadataFunction"; import CardSetupFunction from "../Functions/CardSetupFunction";
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 CardSetupFunction.Execute();
res.sendStatus(200); res.sendStatus(200);
} }

5784
yarn.lock Normal file

File diff suppressed because it is too large Load diff