Compare commits
22 commits
Author | SHA1 | Date | |
---|---|---|---|
|
b06fe11871 | ||
|
5c317be4d5 | ||
|
6a18f34949 | ||
|
edf6c99bad | ||
|
f888f9dd37 | ||
|
2945638b16 | ||
|
5751694018 | ||
|
1a4993b091 | ||
|
5d44c46222 | ||
|
a0a864ef44 | ||
|
9ce4d49b6a | ||
|
d7b56a72b9 | ||
|
1841b49da6 | ||
|
146f0dbf5a | ||
|
599328a3c1 | ||
|
90a7dbee39 | ||
|
9e366eab5d | ||
|
7385a1bdb4 | ||
|
9d8107d318 | ||
|
c0ac18515f | ||
|
6561a1c998 | ||
|
e9876570d2 |
|
@ -14,6 +14,10 @@ BOT_CLIENTID=682942374040961060
|
||||||
BOT_ENV=4
|
BOT_ENV=4
|
||||||
BOT_ADMINS=147392775707426816,887272961504071690
|
BOT_ADMINS=147392775707426816,887272961504071690
|
||||||
BOT_LOGLEVEL=info
|
BOT_LOGLEVEL=info
|
||||||
|
BOT_LOG_DISCORD_ENABLE=false
|
||||||
|
BOT_LOG_DISCORD_LEVEL=warn
|
||||||
|
BOT_LOG_DISCORD_WEBHOOK=
|
||||||
|
BOT_LOG_DISCORD_SERVICE=
|
||||||
|
|
||||||
ABOUT_FUNDING=
|
ABOUT_FUNDING=
|
||||||
ABOUT_REPO=
|
ABOUT_REPO=
|
||||||
|
|
|
@ -55,12 +55,16 @@ jobs:
|
||||||
GDRIVESYNC_AUTO: ${{ vars.PROD_GDRIVESYNC_AUTO }}
|
GDRIVESYNC_AUTO: ${{ vars.PROD_GDRIVESYNC_AUTO }}
|
||||||
EXPRESS_PORT: ${{ secrets.PROD_EXPRESS_PORT }}
|
EXPRESS_PORT: ${{ secrets.PROD_EXPRESS_PORT }}
|
||||||
BOT_LOGLEVEL: ${{ vars.PROD_BOT_LOGLEVEL }}
|
BOT_LOGLEVEL: ${{ vars.PROD_BOT_LOGLEVEL }}
|
||||||
|
BOT_LOG_DISCORD_ENABLE: ${{ vars.PROD_BOT_LOG_DISCORD_ENABLE }}
|
||||||
|
BOT_LOG_DISCORD_LEVEL: ${{ vars.PROD_BOT_LOG_DISCORD_LEVEL }}
|
||||||
|
BOT_LOG_DISCORD_WEBHOOK: ${{ secrets.PROD_BOT_LOG_DISCORD_WEBHOOK }}
|
||||||
|
BOT_LOG_DISCORD_SERVICE: ${{ vars.PROD_BOT_LOG_DISCORD_SERVICE }}
|
||||||
with:
|
with:
|
||||||
host: ${{ secrets.PROD_SSH_HOST }}
|
host: ${{ secrets.PROD_SSH_HOST }}
|
||||||
username: ${{ secrets.PROD_SSH_USER }}
|
username: ${{ secrets.PROD_SSH_USER }}
|
||||||
key: ${{ secrets.PROD_SSH_KEY }}
|
key: ${{ secrets.PROD_SSH_KEY }}
|
||||||
port: ${{ secrets.PROD_SSH_PORT }}
|
port: ${{ secrets.PROD_SSH_PORT }}
|
||||||
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL
|
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL,BOT_LOG_DISCORD_ENABLE,BOT_LOG_DISCORD_LEVEL,BOT_LOG_DISCORD_WEBHOOK,BOT_LOG_DISCORD_SERVICE
|
||||||
script: |
|
script: |
|
||||||
source .sshrc \
|
source .sshrc \
|
||||||
&& cd /home/vylpes/apps/card-drop/card-drop_prod \
|
&& cd /home/vylpes/apps/card-drop/card-drop_prod \
|
||||||
|
|
|
@ -55,12 +55,16 @@ jobs:
|
||||||
GDRIVESYNC_AUTO: ${{ vars.STAGE_GDRIVESYNC_AUTO }}
|
GDRIVESYNC_AUTO: ${{ vars.STAGE_GDRIVESYNC_AUTO }}
|
||||||
EXPRESS_PORT: ${{ secrets.STAGE_EXPRESS_PORT }}
|
EXPRESS_PORT: ${{ secrets.STAGE_EXPRESS_PORT }}
|
||||||
BOT_LOGLEVEL: ${{ vars.STAGE_BOT_LOGLEVEL }}
|
BOT_LOGLEVEL: ${{ vars.STAGE_BOT_LOGLEVEL }}
|
||||||
|
BOT_LOG_DISCORD_ENABLE: ${{ vars.STAGE_BOT_LOG_DISCORD_ENABLE }}
|
||||||
|
BOT_LOG_DISCORD_LEVEL: ${{ vars.STAGE_BOT_LOG_DISCORD_LEVEL }}
|
||||||
|
BOT_LOG_DISCORD_WEBHOOK: ${{ secrets.STAGE_BOT_LOG_DISCORD_WEBHOOK }}
|
||||||
|
BOT_LOG_DISCORD_SERVICE: ${{ vars.STAGE_BOT_LOG_DISCORD_SERVICE }}
|
||||||
with:
|
with:
|
||||||
host: ${{ secrets.STAGE_SSH_HOST }}
|
host: ${{ secrets.STAGE_SSH_HOST }}
|
||||||
username: ${{ secrets.STAGE_SSH_USER }}
|
username: ${{ secrets.STAGE_SSH_USER }}
|
||||||
key: ${{ secrets.STAGE_SSH_KEY }}
|
key: ${{ secrets.STAGE_SSH_KEY }}
|
||||||
port: ${{ secrets.STAGE_SSH_PORT }}
|
port: ${{ secrets.STAGE_SSH_PORT }}
|
||||||
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL
|
envs: DB_NAME,DB_AUTH_USER,DB_AUTH_PASS,DB_HOST,DB_PORT,DB_ROOT_HOST,DB_SYNC,DB_LOGGING,DB_DATA_LOCATION,BOT_TOKEN,BOT_VER,BOT_AUTHOR,BOT_OWNERID,BOT_CLIENTID,ABOUT_FUNDING,ABOUT_REPO,BOT_ENV,BOT_ADMINS,DATA_DIR,GDRIVESYNC_AUTO,SERVER_PATH,EXPRESS_PORT,BOT_LOGLEVEL,BOT_LOG_DISCORD_ENABLE,BOT_LOG_DISCORD_LEVEL,BOT_LOG_DISCORD_WEBHOOK,BOT_LOG_DISCORD_SERVICE
|
||||||
script: |
|
script: |
|
||||||
source .sshrc \
|
source .sshrc \
|
||||||
&& cd /home/vylpes/apps/card-drop/card-drop_stage \
|
&& cd /home/vylpes/apps/card-drop/card-drop_stage \
|
||||||
|
|
11
package.json
11
package.json
|
@ -31,26 +31,29 @@
|
||||||
"@types/jest": "^29.0.0",
|
"@types/jest": "^29.0.0",
|
||||||
"@types/uuid": "^9.0.0",
|
"@types/uuid": "^9.0.0",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
|
"canvas": "^2.11.2",
|
||||||
"clone-deep": "^4.0.1",
|
"clone-deep": "^4.0.1",
|
||||||
"cron": "^3.1.7",
|
"cron": "^3.1.7",
|
||||||
"discord.js": "^14.3.0",
|
"discord.js": "^14.15.3",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"glob": "^10.3.10",
|
"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.4",
|
"minimatch": "9.0.5",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"ts-jest": "^29.0.0",
|
"ts-jest": "^29.0.0",
|
||||||
"typeorm": "0.3.20",
|
"typeorm": "0.3.20",
|
||||||
"winston": "^3.11.0"
|
"winston": "^3.11.0",
|
||||||
|
"winston-daily-rotate-file": "^5.0.0",
|
||||||
|
"winston-discord-transport": "^1.3.0"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"undici": "^5.28.3"
|
"undici": "^5.28.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.0.0",
|
"@types/node": "^20.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||||
"@typescript-eslint/parser": "^6.16.0",
|
"@typescript-eslint/parser": "^6.16.0",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"np": "^9.0.0",
|
"np": "^9.0.0",
|
||||||
|
|
|
@ -20,6 +20,14 @@ export default class Claim extends ButtonEvent {
|
||||||
const droppedBy = interaction.customId.split(" ")[3];
|
const droppedBy = interaction.customId.split(" ")[3];
|
||||||
const userId = interaction.user.id;
|
const userId = interaction.user.id;
|
||||||
|
|
||||||
|
const whenDropped = interaction.message.createdAt;
|
||||||
|
const lastClaimableDate = new Date(Date.now() - (1000 * 60 * 5)); // 5 minutes ago
|
||||||
|
|
||||||
|
if (whenDropped < lastClaimableDate) {
|
||||||
|
await interaction.channel.send(`${interaction.user}, Cards can only be claimed within 5 minutes of it being dropped!`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Claim", `Parameters: cardNumber=${cardNumber}, claimId=${claimId}, droppedBy=${droppedBy}, userId=${userId}`);
|
AppLogger.LogSilly("Button/Claim", `Parameters: cardNumber=${cardNumber}, claimId=${claimId}, droppedBy=${droppedBy}, userId=${userId}`);
|
||||||
|
|
||||||
const user = await User.FetchOneById(User, userId) || new User(userId, CardConstants.StartingCurrency);
|
const user = await User.FetchOneById(User, userId) || new User(userId, CardConstants.StartingCurrency);
|
||||||
|
@ -68,7 +76,7 @@ export default class Claim extends ButtonEvent {
|
||||||
|
|
||||||
const imageFileName = card.card.path.split("/").pop()!;
|
const imageFileName = card.card.path.split("/").pop()!;
|
||||||
|
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(card, inventory.Quantity, imageFileName, interaction.user.username, user.Currency);
|
||||||
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true);
|
const row = CardDropHelperMetadata.GenerateDropButtons(card, claimId, interaction.user.id, true);
|
||||||
|
|
||||||
await interaction.editReply({
|
await interaction.editReply({
|
||||||
|
@ -76,4 +84,4 @@ export default class Claim extends ButtonEvent {
|
||||||
components: [ row ],
|
components: [ row ],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,8 @@ export default class Inventory extends ButtonEvent {
|
||||||
const page = interaction.customId.split(" ")[2];
|
const page = interaction.customId.split(" ")[2];
|
||||||
|
|
||||||
AppLogger.LogSilly("Button/Inventory", `Parameters: userid=${userid}, page=${page}`);
|
AppLogger.LogSilly("Button/Inventory", `Parameters: userid=${userid}, page=${page}`);
|
||||||
|
|
||||||
|
await interaction.deferUpdate();
|
||||||
|
|
||||||
const member = interaction.guild.members.cache.find(x => x.id == userid) || await interaction.guild.members.fetch(userid);
|
const member = interaction.guild.members.cache.find(x => x.id == userid) || await interaction.guild.members.fetch(userid);
|
||||||
|
|
||||||
|
@ -24,14 +26,20 @@ export default class Inventory extends ButtonEvent {
|
||||||
|
|
||||||
const embed = await InventoryHelper.GenerateInventoryPage(member.user.username, member.user.id, Number(page));
|
const embed = await InventoryHelper.GenerateInventoryPage(member.user.username, member.user.id, Number(page));
|
||||||
|
|
||||||
await interaction.update({
|
if (!embed) {
|
||||||
|
await interaction.followUp("No page for user found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.followUp({
|
||||||
|
files: [ embed.image ],
|
||||||
embeds: [ embed.embed ],
|
embeds: [ embed.embed ],
|
||||||
components: [ embed.row ],
|
components: [ embed.row ],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.LogError("Button/Inventory", `Error generating inventory page for ${member.user.username} with id ${member.user.id}: ${e}`);
|
AppLogger.LogError("Button/Inventory", `Error generating inventory page for ${member.user.username} with id ${member.user.id}: ${e}`);
|
||||||
|
|
||||||
await interaction.reply("No page for user found.");
|
await interaction.followUp("An error has occurred running this command.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default class Reroll extends ButtonEvent {
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency);
|
||||||
|
|
||||||
const claimId = v4();
|
const claimId = v4();
|
||||||
|
|
||||||
|
@ -78,4 +78,4 @@ export default class Reroll extends ButtonEvent {
|
||||||
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`);
|
await interaction.editReply(`Unable to send next drop. Please try again, and report this if it keeps happening. (${randomCard.card.id})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import path from "path";
|
||||||
import { Logger, createLogger, format, transports } from "winston";
|
import { Logger, createLogger, format, transports } from "winston";
|
||||||
|
import DailyRotateFile from "winston-daily-rotate-file";
|
||||||
|
import DiscordTransport from "winston-discord-transport";
|
||||||
|
|
||||||
export default class AppLogger {
|
export default class AppLogger {
|
||||||
public static Logger: Logger;
|
public static Logger: Logger;
|
||||||
|
@ -19,12 +22,21 @@ export default class AppLogger {
|
||||||
customFormat,
|
customFormat,
|
||||||
),
|
),
|
||||||
defaultMeta: { service: "bot" },
|
defaultMeta: { service: "bot" },
|
||||||
transports: [
|
transports: [],
|
||||||
new transports.File({ filename: "error.log", level: "error" }),
|
|
||||||
new transports.File({ filename: "combined.log" }),
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (process.env.DATA_DIR) {
|
||||||
|
const logDir = path.join(process.env.DATA_DIR, "logs");
|
||||||
|
|
||||||
|
logger.add(new DailyRotateFile({
|
||||||
|
filename: "bot-%DATE%.log",
|
||||||
|
dirname: logDir,
|
||||||
|
datePattern: "YYYY-MM-DD-HH",
|
||||||
|
maxSize: "20m",
|
||||||
|
maxFiles: "14d",
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
if (outputToConsole) {
|
if (outputToConsole) {
|
||||||
logger.add(new transports.Console({
|
logger.add(new transports.Console({
|
||||||
format: format.combine(
|
format: format.combine(
|
||||||
|
@ -34,6 +46,18 @@ export default class AppLogger {
|
||||||
)}));
|
)}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.BOT_LOG_DISCORD_ENABLE == "true") {
|
||||||
|
if (process.env.BOT_LOG_DISCORD_WEBHOOK) {
|
||||||
|
logger.add(new DiscordTransport({
|
||||||
|
webhook: process.env.BOT_LOG_DISCORD_WEBHOOK.toString(),
|
||||||
|
defaultMeta: { service: process.env.BOT_LOG_DISCORD_SERVICE },
|
||||||
|
level: process.env.BOT_LOG_DISCORD_LEVEL,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
throw "BOT_LOG_DISCORD_WEBHOOK is required to enable discord logger support.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AppLogger.Logger = logger;
|
AppLogger.Logger = logger;
|
||||||
|
|
||||||
AppLogger.LogInfo("AppLogger", `Log Level: ${logLevel}`);
|
AppLogger.LogInfo("AppLogger", `Log Level: ${logLevel}`);
|
||||||
|
@ -62,4 +86,4 @@ export default class AppLogger {
|
||||||
public static LogSilly(label: string, message: string) {
|
public static LogSilly(label: string, message: string) {
|
||||||
AppLogger.Logger.silly({ label, message });
|
AppLogger.Logger.silly({ label, message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { SeriesMetadata } from "../contracts/SeriesMetadata";
|
||||||
import AppLogger from "./appLogger";
|
import AppLogger from "./appLogger";
|
||||||
import TimerHelper from "../helpers/TimerHelper";
|
import TimerHelper from "../helpers/TimerHelper";
|
||||||
import GiveCurrency from "../timers/GiveCurrency";
|
import GiveCurrency from "../timers/GiveCurrency";
|
||||||
|
import PurgeClaims from "../timers/PurgeClaims";
|
||||||
|
|
||||||
export class CoreClient extends Client {
|
export class CoreClient extends Client {
|
||||||
private static _commandItems: ICommandItem[];
|
private static _commandItems: ICommandItem[];
|
||||||
|
@ -79,8 +80,10 @@ export class CoreClient extends Client {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
AppLogger.LogInfo("Client", "App Data Source Initialised");
|
AppLogger.LogInfo("Client", "App Data Source Initialised");
|
||||||
|
|
||||||
const timerId = this._timerHelper.AddTimer("*/20 * * * *", "Europe/London", GiveCurrency, false);
|
this._timerHelper.AddTimer("*/20 * * * *", "Europe/London", GiveCurrency, false);
|
||||||
this._timerHelper.StartTimer(timerId);
|
this._timerHelper.AddTimer("0 0 * * *", "Europe/London", PurgeClaims, false);
|
||||||
|
|
||||||
|
this._timerHelper.StartAllTimers();
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
AppLogger.LogError("Client", "App Data Source Initialisation Failed");
|
AppLogger.LogError("Client", "App Data Source Initialisation Failed");
|
||||||
|
|
|
@ -67,7 +67,7 @@ export default class Drop extends Command {
|
||||||
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
const inventory = await Inventory.FetchOneByCardNumberAndUserId(interaction.user.id, randomCard.card.id);
|
||||||
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
const quantityClaimed = inventory ? inventory.Quantity : 0;
|
||||||
|
|
||||||
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName);
|
const embed = CardDropHelperMetadata.GenerateDropEmbed(randomCard, quantityClaimed, imageFileName, undefined, user.Currency);
|
||||||
|
|
||||||
const claimId = v4();
|
const claimId = v4();
|
||||||
|
|
||||||
|
@ -88,4 +88,4 @@ export default class Drop extends Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,8 @@ export default class Inventory extends Command {
|
||||||
|
|
||||||
const user = userOption ? userOption.user! : interaction.user;
|
const user = userOption ? userOption.user! : interaction.user;
|
||||||
|
|
||||||
|
await interaction.deferReply();
|
||||||
|
|
||||||
AppLogger.LogSilly("Commands/Inventory", `Parameters: page=${page?.value}, user=${user.id}`);
|
AppLogger.LogSilly("Commands/Inventory", `Parameters: page=${page?.value}, user=${user.id}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -37,14 +39,20 @@ export default class Inventory extends Command {
|
||||||
|
|
||||||
const embed = await InventoryHelper.GenerateInventoryPage(user.username, user.id, pageNumber);
|
const embed = await InventoryHelper.GenerateInventoryPage(user.username, user.id, pageNumber);
|
||||||
|
|
||||||
await interaction.reply({
|
if (!embed) {
|
||||||
|
await interaction.followUp("No page for user found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await interaction.followUp({
|
||||||
|
files: [ embed.image ],
|
||||||
embeds: [ embed.embed ],
|
embeds: [ embed.embed ],
|
||||||
components: [ embed.row ],
|
components: [ embed.row ],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
AppLogger.LogError("Commands/Inventory", e as string);
|
AppLogger.LogError("Commands/Inventory", e as string);
|
||||||
|
|
||||||
await interaction.reply("No page for user found.");
|
await interaction.followUp("An error has occurred running this command.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,12 @@ export default class AppBaseEntity {
|
||||||
await repository.remove(entity);
|
await repository.remove(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async RemoveMany<T extends AppBaseEntity>(target: EntityTarget<T>, entity: T[]): Promise<void> {
|
||||||
|
const repository = AppDataSource.getRepository<T>(target);
|
||||||
|
|
||||||
|
await repository.remove(entity);
|
||||||
|
}
|
||||||
|
|
||||||
public static async FetchAll<T extends AppBaseEntity>(target: EntityTarget<T>, relations?: string[]): Promise<T[]> {
|
public static async FetchAll<T extends AppBaseEntity>(target: EntityTarget<T>, relations?: string[]): Promise<T[]> {
|
||||||
const repository = AppDataSource.getRepository<T>(target);
|
const repository = AppDataSource.getRepository<T>(target);
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import CardRarityChances from "../constants/CardRarityChances";
|
||||||
import { DropResult } from "../contracts/SeriesMetadata";
|
import { DropResult } from "../contracts/SeriesMetadata";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
|
import CardConstants from "../constants/CardConstants";
|
||||||
|
|
||||||
export default class CardDropHelperMetadata {
|
export default class CardDropHelperMetadata {
|
||||||
public static GetRandomCard(): DropResult | undefined {
|
public static GetRandomCard(): DropResult | undefined {
|
||||||
|
@ -77,25 +78,41 @@ export default class CardDropHelperMetadata {
|
||||||
return { card, series };
|
return { card, series };
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string): EmbedBuilder {
|
public static GenerateDropEmbed(drop: DropResult, quantityClaimed: number, imageFileName: string, claimedBy?: string, currency?: number): EmbedBuilder {
|
||||||
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`);
|
AppLogger.LogSilly("CardDropHelperMetadata/GenerateDropEmbed", `Parameters: drop=${drop.card.id}, quantityClaimed=${quantityClaimed}, imageFileName=${imageFileName}`);
|
||||||
|
|
||||||
let description = "";
|
const description = drop.series.name;
|
||||||
description += `Series: ${drop.series.name}\n`;
|
|
||||||
description += `Claimed: ${quantityClaimed}\n`;
|
|
||||||
|
|
||||||
if (claimedBy != null) {
|
const embed = new EmbedBuilder()
|
||||||
description += `Claimed by: ${claimedBy}\n`;
|
|
||||||
} else {
|
|
||||||
description += "Claimed by: (UNCLAIMED)\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
return new EmbedBuilder()
|
|
||||||
.setTitle(drop.card.name)
|
.setTitle(drop.card.name)
|
||||||
.setDescription(description)
|
.setDescription(description)
|
||||||
.setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` })
|
.setFooter({ text: `${CardRarityToString(drop.card.type)} · ${drop.card.id}` })
|
||||||
.setColor(CardRarityToColour(drop.card.type))
|
.setColor(CardRarityToColour(drop.card.type))
|
||||||
.setImage(`attachment://${imageFileName}`);
|
.setImage(`attachment://${imageFileName}`)
|
||||||
|
.addFields([
|
||||||
|
{
|
||||||
|
name: "Claimed",
|
||||||
|
value: `${quantityClaimed}`,
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Claimed by",
|
||||||
|
value: claimedBy ?? "(UNCLAIMED)",
|
||||||
|
inline: true,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (currency != null) {
|
||||||
|
embed.addFields([
|
||||||
|
{
|
||||||
|
name: "Currency",
|
||||||
|
value: `${currency}`,
|
||||||
|
inline: true,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return embed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string, disabled: boolean = false): ActionRowBuilder<ButtonBuilder> {
|
public static GenerateDropButtons(drop: DropResult, claimId: string, userId: string, disabled: boolean = false): ActionRowBuilder<ButtonBuilder> {
|
||||||
|
@ -105,7 +122,7 @@ export default class CardDropHelperMetadata {
|
||||||
.addComponents(
|
.addComponents(
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`)
|
.setCustomId(`claim ${drop.card.id} ${claimId} ${userId}`)
|
||||||
.setLabel("Claim")
|
.setLabel(`Claim (${CardConstants.ClaimCost} 🪙)`)
|
||||||
.setStyle(ButtonStyle.Primary)
|
.setStyle(ButtonStyle.Primary)
|
||||||
.setDisabled(disabled),
|
.setDisabled(disabled),
|
||||||
new ButtonBuilder()
|
new ButtonBuilder()
|
||||||
|
@ -113,4 +130,4 @@ export default class CardDropHelperMetadata {
|
||||||
.setLabel("Reroll")
|
.setLabel("Reroll")
|
||||||
.setStyle(ButtonStyle.Secondary));
|
.setStyle(ButtonStyle.Secondary));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
import { ActionRowBuilder, AttachmentBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
|
||||||
import Inventory from "../database/entities/app/Inventory";
|
import Inventory from "../database/entities/app/Inventory";
|
||||||
import { CoreClient } from "../client/client";
|
import { CoreClient } from "../client/client";
|
||||||
import EmbedColours from "../constants/EmbedColours";
|
import EmbedColours from "../constants/EmbedColours";
|
||||||
import { CardRarity, CardRarityToString } from "../constants/CardRarity";
|
import { CardRarity, CardRarityToString } from "../constants/CardRarity";
|
||||||
import cloneDeep from "clone-deep";
|
import cloneDeep from "clone-deep";
|
||||||
import AppLogger from "../client/appLogger";
|
import AppLogger from "../client/appLogger";
|
||||||
|
import { createCanvas, loadImage } from "canvas";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
interface InventoryPage {
|
interface InventoryPage {
|
||||||
id: number,
|
id: number,
|
||||||
|
@ -18,16 +20,26 @@ interface InventoryPageCards {
|
||||||
name: string,
|
name: string,
|
||||||
type: CardRarity,
|
type: CardRarity,
|
||||||
quantity: number,
|
quantity: number,
|
||||||
|
path: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ReturnedInventoryPage {
|
||||||
|
embed: EmbedBuilder,
|
||||||
|
row: ActionRowBuilder<ButtonBuilder>,
|
||||||
|
image: AttachmentBuilder,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default class InventoryHelper {
|
export default class InventoryHelper {
|
||||||
public static async GenerateInventoryPage(username: string, userid: string, page: number): Promise<{ embed: EmbedBuilder, row: ActionRowBuilder<ButtonBuilder> }> {
|
public static async GenerateInventoryPage(username: string, userid: string, page: number): Promise<ReturnedInventoryPage | undefined> {
|
||||||
AppLogger.LogSilly("Helpers/InventoryHelper", `Parameters: username=${username}, userid=${userid}, page=${page}`);
|
AppLogger.LogSilly("Helpers/InventoryHelper", `Parameters: username=${username}, userid=${userid}, page=${page}`);
|
||||||
|
|
||||||
const cardsPerPage = 15;
|
const cardsPerPage = 9;
|
||||||
|
|
||||||
const inventory = await Inventory.FetchAllByUserId(userid);
|
const inventory = await Inventory.FetchAllByUserId(userid);
|
||||||
|
|
||||||
|
if (!inventory || inventory.length == 0) return undefined;
|
||||||
|
|
||||||
const clientCards = cloneDeep(CoreClient.Cards);
|
const clientCards = cloneDeep(CoreClient.Cards);
|
||||||
|
|
||||||
const allSeriesClaimed = clientCards
|
const allSeriesClaimed = clientCards
|
||||||
|
@ -62,6 +74,7 @@ export default class InventoryHelper {
|
||||||
name: card.name,
|
name: card.name,
|
||||||
type: card.type,
|
type: card.type,
|
||||||
quantity: item.Quantity,
|
quantity: item.Quantity,
|
||||||
|
path: card.path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,15 +90,15 @@ export default class InventoryHelper {
|
||||||
const currentPage = pages[page];
|
const currentPage = pages[page];
|
||||||
|
|
||||||
if (!currentPage) {
|
if (!currentPage) {
|
||||||
AppLogger.LogError("Helpers/InventoryHelper", "Unable to find page");
|
return undefined;
|
||||||
return Promise.reject("Unable to find page");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const embed = new EmbedBuilder()
|
const embed = new EmbedBuilder()
|
||||||
.setTitle(username)
|
.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")}`)
|
.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}` })
|
.setFooter({ text: `Page ${page + 1} of ${pages.length}` })
|
||||||
.setColor(EmbedColours.Ok);
|
.setColor(EmbedColours.Ok)
|
||||||
|
.setImage("attachment://page.png");
|
||||||
|
|
||||||
const row = new ActionRowBuilder<ButtonBuilder>()
|
const row = new ActionRowBuilder<ButtonBuilder>()
|
||||||
.addComponents(
|
.addComponents(
|
||||||
|
@ -100,6 +113,44 @@ export default class InventoryHelper {
|
||||||
.setStyle(ButtonStyle.Primary)
|
.setStyle(ButtonStyle.Primary)
|
||||||
.setDisabled(page + 1 == pages.length));
|
.setDisabled(page + 1 == pages.length));
|
||||||
|
|
||||||
return { embed, row };
|
const buffer = await this.GenerateInventoryImage(currentPage);
|
||||||
|
const image = new AttachmentBuilder(buffer, { name: "page.png" });
|
||||||
|
|
||||||
|
return { embed, row, image };
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private static async GenerateInventoryImage(page: InventoryPage): Promise<Buffer> {
|
||||||
|
const gridWidth = 3;
|
||||||
|
const gridHeight = Math.ceil(page.cards.length / gridWidth);
|
||||||
|
|
||||||
|
const imageWidth = 526;
|
||||||
|
const imageHeight = 712;
|
||||||
|
|
||||||
|
const canvasWidth = imageWidth * gridWidth;
|
||||||
|
const canvasHeight = imageHeight * gridHeight;
|
||||||
|
|
||||||
|
const canvas = createCanvas(canvasWidth, canvasHeight);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
for (let i = 0; i < page.cards.length; i++) {
|
||||||
|
const card = page.cards[i];
|
||||||
|
|
||||||
|
const image = await loadImage(path.join(process.env.DATA_DIR!, "cards", card.path));
|
||||||
|
|
||||||
|
if (!image) {
|
||||||
|
AppLogger.LogError("InventoryHelper/GenerateInventoryImage", `Failed to load image for card ${card.id}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const x = i % gridWidth;
|
||||||
|
const y = Math.floor(i / gridWidth);
|
||||||
|
|
||||||
|
const imageX = imageWidth * x;
|
||||||
|
const imageY = imageHeight * y;
|
||||||
|
|
||||||
|
ctx.drawImage(image, imageX, imageY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return canvas.toBuffer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
14
src/timers/PurgeClaims.ts
Normal file
14
src/timers/PurgeClaims.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import AppLogger from "../client/appLogger";
|
||||||
|
import Claim from "../database/entities/app/Claim";
|
||||||
|
|
||||||
|
export default async function PurgeClaims() {
|
||||||
|
const claims = await Claim.FetchAll(Claim);
|
||||||
|
|
||||||
|
const whenLastClaimable = new Date(Date.now() - (1000 * 60 * 5)); // 5 minutes ago
|
||||||
|
|
||||||
|
const expiredClaims = claims.filter(x => x.WhenCreated < whenLastClaimable);
|
||||||
|
|
||||||
|
await Claim.RemoveMany(Claim, expiredClaims);
|
||||||
|
|
||||||
|
AppLogger.LogInfo("Timers/PurgeClaims", `Purged ${expiredClaims.length} claims from the database`);
|
||||||
|
}
|
Loading…
Reference in a new issue