feat: a lot of improvements

This commit is contained in:
TheClashFruit 2024-02-10 10:59:02 +01:00
parent 2cb17adb32
commit 14a1217549
Signed by: TheClashFruit
GPG key ID: D61666AC77D1C29F
18 changed files with 308 additions and 104 deletions

3
.gitignore vendored
View file

@ -130,7 +130,8 @@ dist
.yarn/install-state.gz
.pnp.*
# data
# data, tmp
tmp/
data/*.json5
!data/default.json5

View file

@ -1,6 +1,14 @@
[
{ text: 'Water', emoji: '💧', discovered: false },
{ text: 'Fire', emoji: '🔥', discovered: false },
{ text: 'Wind', emoji: '🌬️' , discovered: false },
{ text: 'Earth', emoji: '🌍', discovered: false }
]
{
config: {
showUsername: true,
inventory: {
items: 10
}
},
inventory: [
{ text: 'Water', emoji: '💧', discovered: false },
{ text: 'Fire', emoji: '🔥', discovered: false },
{ text: 'Wind', emoji: '🌬️' , discovered: false },
{ text: 'Earth', emoji: '🌍', discovered: false }
]
}

View file

@ -15,12 +15,17 @@
"discord.js": "^14.14.1",
"dotenv": "^16.4.1",
"json5": "^2.2.3",
"module-alias": "^2.2.3",
"node-fetch": "^3.3.2"
},
"devDependencies": {
"@types/module-alias": "^2.0.4",
"@types/node": "^20.11.17",
"nodemon": "^3.0.3",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"_moduleAliases": {
"@": "./src"
}
}

View file

@ -14,11 +14,17 @@ dependencies:
json5:
specifier: ^2.2.3
version: 2.2.3
module-alias:
specifier: ^2.2.3
version: 2.2.3
node-fetch:
specifier: ^3.3.2
version: 3.3.2
devDependencies:
'@types/module-alias':
specifier: ^2.0.4
version: 2.0.4
'@types/node':
specifier: ^20.11.17
version: 20.11.17
@ -164,6 +170,10 @@ packages:
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
dev: true
/@types/module-alias@2.0.4:
resolution: {integrity: sha512-5+G/QXO/DvHZw60FjvbDzO4JmlD/nG5m2/vVGt25VN1eeP3w2bCoks1Wa7VuptMPM1TxJdx6RjO70N9Fw0nZPA==}
dev: true
/@types/node@20.11.17:
resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==}
dependencies:
@ -417,6 +427,10 @@ packages:
brace-expansion: 1.1.11
dev: true
/module-alias@2.2.3:
resolution: {integrity: sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==}
dev: false
/ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true

View file

@ -1,10 +1,10 @@
import {
SlashCommandBuilder,
Interaction
SlashCommandBuilder
} from 'discord.js';
import Inventory from "../types/inventory.type";
import InventoryUtil from '../util/InventoryUtil';
import { Item } from '@/types';
import Data from '@/util/Data';
module.exports = {
builder: new SlashCommandBuilder()
@ -26,23 +26,27 @@ module.exports = {
),
async autocomplete(interaction: any) {
const focusedValue = interaction.options.getFocused().toLowerCase();
const choices = new InventoryUtil(interaction.user.id).getInventory();
const filtered = choices.filter(choice => choice.text.toLowerCase().startsWith(focusedValue));
await interaction.respond(
filtered.map(choice => ({ name: `${choice.emoji} ${choice.text}`, value: choice.text.toLowerCase() })).slice(0, 25),
new Data(interaction.user.id)
.inventory
.getInventory()
.filter(choice => choice.text.toLowerCase().startsWith(focusedValue))
.map(choice => ({ name: `${choice.emoji} ${choice.text}`, value: choice.text.toLowerCase() }))
.slice(0, 25)
);
},
async execute(interaction: any) {
const invUtil = new InventoryUtil(interaction.user.id);
const userData = new Data(interaction.user.id);
const inv: Inventory[] = invUtil.getInventory();
const inv: Item[] = userData.inventory.getInventory();
const firstItem = interaction.options.getString('first_item');
const secondItem = interaction.options.getString('second_item');
if (
invUtil.hasItem(firstItem) &&
invUtil.hasItem(secondItem)
userData.inventory.hasItem(firstItem) &&
userData.inventory.hasItem(secondItem)
) {
await interaction.deferReply();
@ -56,8 +60,8 @@ module.exports = {
.then(data => {
interaction.editReply('```json\n' + JSON.stringify(data, null, 2) + '```');
if(!invUtil.hasItem(data.result)) {
invUtil.addItem({
if(!userData.inventory.hasItem(data.result)) {
userData.inventory.addItem({
text: data.result,
emoji: data.emoji,
discovered: data.isNew

43
src/commands/export.ts Normal file
View file

@ -0,0 +1,43 @@
import {
SlashCommandBuilder
} from "discord.js";
import fs from 'fs';
import Data from "@/util/Data";
import errorMessage from "@/util/ErrorMessage";
module.exports = {
builder: new SlashCommandBuilder()
.setName('export')
.setDescription('Export inventory to a json file.')
.addUserOption(option =>
option
.setName('user')
.setDescription('User to export the inventory of.')
.setRequired(false)
),
async execute(interaction: any) {
try {
if(interaction.options.getUser('user')) {
const invUtil = new Data(interaction.options.getUser('user').id).inventory;
const inv = invUtil.getInventory();
fs.writeFileSync(`./tmp/${interaction.options.getUser('user').id}.json`, JSON.stringify({elements: inv}, null, 2));
await interaction.reply({ files: [ `./tmp/${interaction.options.getUser('user').id}.json` ] });
}
const invUtil = new Data(interaction.user.id).inventory;
const inv = invUtil.getInventory();
fs.writeFileSync(`./tmp/${interaction.user.id}.json`, JSON.stringify({elements: inv}, null, 2));
await interaction.reply({ files: [ `./tmp/${interaction.user.id}.json` ] });
} catch (e: any) {
await interaction.reply({ embeds: [ errorMessage('There was an error while exporting!', e) ], ephemeral: true });
}
}
}

View file

@ -1,5 +1,9 @@
import {SlashCommandBuilder} from "discord.js";
import InventoryUtil from "../util/InventoryUtil";
import {
SlashCommandBuilder
} from "discord.js";
import Data from "@/util/Data";
import errorMessage from "@/util/ErrorMessage";
module.exports = {
builder: new SlashCommandBuilder()
@ -12,22 +16,42 @@ module.exports = {
.setRequired(true)
),
async execute(interaction: any) {
const invUtil = new InventoryUtil(interaction.user.id);
const invUtil = new Data(interaction.user.id).inventory;
try {
if(interaction.options.getAttachment('file').size > 1048576) {
const size = (interaction.options.getAttachment('file').size / 1024) / 1024;
await interaction.reply(`The file you provided is too large. It must be less than 1 MiB, your file is ${size.toFixed(2)} MiB.`);
return;
}
fetch(interaction.options.getAttachment('file').attachment)
.then(res => res.text())
.then(async data => {
try {
if(!JSON.parse(data).elements) {
await interaction.reply('The file you provided is not a valid json file.');
return;
}
invUtil.setInventory(JSON.parse(data).elements);
await interaction.reply('Import successful!');
} catch (e) {
await interaction.reply('There was an error while importing.\n```\n' + e + '\n```');
} catch (e: any) {
if(e.name == 'SyntaxError') {
await interaction.reply('The file you provided is not a valid json file.');
return;
}
await interaction.reply({ embeds: [ errorMessage('There was an error while importing!', e) ], ephemeral: true });
}
});
} catch (e) {
await interaction.reply('There was an error while importing.\n```\n' + e + '\n```');
} catch (e: any) {
await interaction.reply({ embeds: [ errorMessage('There was an error while importing!', e) ], ephemeral: true });
}
}
}

View file

@ -1,14 +1,11 @@
import {
SlashCommandBuilder,
Interaction,
EmbedBuilder, Embed
EmbedBuilder,
} from 'discord.js';
import fs from "fs";
import Inventory from "../types/inventory.type";
import JSON5 from 'json5';
import InventoryUtil from "../util/InventoryUtil";
import {it} from "node:test";
import { Item } from '@/types';
import Data from '@/util/Data';
module.exports = {
builder: new SlashCommandBuilder()
@ -28,7 +25,7 @@ module.exports = {
.setRequired(false)
),
async execute(interaction: any) {
const inv: Inventory[] = new InventoryUtil(interaction.user.id).getInventory();
const inv: Item[] = new Data(interaction.user.id).inventory.getInventory();
let resEmbed: EmbedBuilder = new EmbedBuilder();
@ -43,7 +40,7 @@ module.exports = {
if(interaction.options.getUser('user')) {
const user = interaction.options.getUser('user').id;
const userInv: Inventory[] = new InventoryUtil(user).getInventory();
const userInv: Item[] = new Data(user).inventory.getInventory();
resEmbed.setColor(interaction.options.getUser('user').accentColor | 0x5865F2);
resEmbed.setTitle(`${interaction.options.getUser('user').displayName}'${interaction.options.getUser('user').displayName.endsWith('s') ? '' : 's'} Inventory`);
@ -62,7 +59,7 @@ module.exports = {
inline: true
});
if (items.split('\n').slice(10).length > 0) {
if (items.split('\n').slice(10).length > 1) {
fields.push({
name: ' ',
value: items.split('\n').slice(10).join('\n'),
@ -76,7 +73,7 @@ module.exports = {
text: `Page ${page} of ${Math.ceil(userInv.length / 20)} (${userInv.length} items)`
});
await interaction.reply({ embeds: [resEmbed] });
await interaction.reply({ embeds: [ resEmbed ], ephemeral: true });
return;
}
@ -101,7 +98,7 @@ module.exports = {
inline: true
});
if (items.split('\n').slice(10).length > 0) {
if (items.split('\n').slice(10).length > 1) {
fields.push({
name: ' ',
value: items.split('\n').slice(10).join('\n'),
@ -111,6 +108,6 @@ module.exports = {
resEmbed.addFields(fields);
await interaction.reply({ embeds: [resEmbed] });
await interaction.reply({ embeds: [ resEmbed ], ephemeral: true });
},
};

View file

@ -1,9 +1,10 @@
import 'dotenv/config'
import 'dotenv/config';
import 'module-alias/register';
import {Activity, Client, ActivityType, GatewayIntentBits, REST, Routes} from 'discord.js';
import {Activity, Client, ActivityType, GatewayIntentBits, REST, Routes, EmbedBuilder} from 'discord.js';
import fs from 'fs';
import Command from "./types/command.type";
import { Command } from '@/types';
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
@ -31,7 +32,7 @@ const rest = new REST().setToken(process.env.TOKEN!);
// The put method is used to fully refresh all commands in the guild with the current set
const data = await rest.put(
Routes.applicationCommands('872411739155726336'),
Routes.applicationGuildCommands('872411739155726336', '1127731341283307520'),
{ body: commandsArray.map(cmd => cmd.builder) },
);
@ -56,9 +57,23 @@ client.on('interactionCreate', async interaction => {
try {
await command.execute(interaction);
} catch (error) {
} catch (error: any) {
console.error(error);
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
const errorEmbed =
new EmbedBuilder()
.setTitle('Error')
.setDescription('There was an error while executing this command!')
.setColor(0xff0000)
.addFields([
{
name: 'Error',
value: `\`\`\`\n${error.stack}\n\`\`\``
}
])
.setTimestamp();
await interaction.reply({ embeds: [ errorEmbed ], ephemeral: true });
}
}

30
src/types.d.ts vendored Normal file
View file

@ -0,0 +1,30 @@
import {
SlashCommandBuilder
} from 'discord.js';
type Item = {
text: string,
emoji: string,
discovered: boolean
}
type Data = {
config: {
showUsername: boolean,
inventory: {
items: number
}
},
inventory: Item[]
}
type Command = {
builder: SlashCommandBuilder,
execute: (interaction: any) => void
}
export {
Item,
Command,
Data
};

View file

@ -1,8 +0,0 @@
import {SlashCommandBuilder} from "discord.js";
type Command = {
builder: SlashCommandBuilder,
execute: (interaction: any) => void
}
export default Command;

View file

@ -1,7 +0,0 @@
type Inventory = {
text: string,
emoji: string,
discovered: boolean
}
export default Inventory;

26
src/util/Config.ts Normal file
View file

@ -0,0 +1,26 @@
import fs from 'fs';
import JSON5 from 'json5';
import Inventory from "@/util/Inventory";
class Config {
id: string;
inventory: Inventory;
constructor(id: string) {
this.id = id;
this.inventory = new Inventory(id);
}
getConfig() {
if(!fs.existsSync(`./data/${this.id}.json5`))
fs.writeFileSync(`./data/${this.id}.json5`, JSON5.stringify(Config.getDefaultConfig(), null, 2));
return JSON5.parse(fs.readFileSync(`./data/${this.id}.json5`, 'utf-8'));
}
static getDefaultConfig() {
return JSON5.parse(fs.readFileSync('./data/default.json5', 'utf-8'));
}
}
export default Config;

28
src/util/Data.ts Normal file
View file

@ -0,0 +1,28 @@
import Config from "@/util/Config";
import Inventory from "@/util/Inventory";
import fs from 'fs';
import JSON5 from 'json5';
class Data {
id: string;
config: Config;
inventory: Inventory;
constructor(id: string) {
this.id = id;
if(!fs.existsSync(`./data/${this.id}.json5`))
fs.writeFileSync(`./data/${this.id}.json5`, JSON5.stringify(Data.getDefaultData(), null, 2));
this.config = new Config(id);
this.inventory = new Inventory(id);
}
static getDefaultData() {
return JSON5.parse(fs.readFileSync('./data/default.json5', 'utf-8'));
}
}
export default Data;

21
src/util/ErrorMessage.ts Normal file
View file

@ -0,0 +1,21 @@
import {
EmbedBuilder
} from "discord.js";
const errorMessage = (msg: string, e: any) => {
console.error(e);
return new EmbedBuilder()
.setTitle('Error')
.setDescription('There was an error while importing!')
.setColor(0xff0000)
.addFields([
{
name: 'Stack Trace',
value: `\`\`\`\n${e.stack}\n\`\`\``
}
])
.setTimestamp();
}
export default errorMessage;

40
src/util/Inventory.ts Normal file
View file

@ -0,0 +1,40 @@
import { Item as InvType, Data as DataType } from "@/types";
import JSON5 from "json5";
import fs from "fs";
class Inventory {
id: string;
constructor(id: string) {
this.id = id;
}
getInventory(): InvType[] {
return JSON5.parse(fs.readFileSync(`./data/${this.id}.json5`, 'utf-8')).inventory;
}
hasItem(item: string): boolean {
const inv: InvType[] = this.getInventory();
return inv.some(i => i.text.toLowerCase() === item.toLowerCase());
}
addItem(item: InvType): void {
const inv: InvType[] = this.getInventory();
inv.push(item);
this.setInventory(inv);
}
setInventory(inv: InvType[]): void {
const data: DataType = JSON5.parse(fs.readFileSync(`./data/${this.id}.json5`, 'utf-8'));
data.inventory = inv;
fs.writeFileSync(`./data/${this.id}.json5`, JSON5.stringify(data, null, 2));
}
}
export default Inventory;

View file

@ -1,42 +0,0 @@
import Inventory from "../types/inventory.type";
import JSON5 from "json5";
import fs from "fs";
class InventoryUtil {
id: string;
constructor(id: string) {
this.id = id;
}
getInventory(): Inventory[] {
if(!fs.existsSync(`./data/${this.id}.json5`))
fs.writeFileSync(`./data/${this.id}.json5`, JSON5.stringify(InventoryUtil.getDefaultInventory()));
return JSON5.parse(fs.readFileSync(`./data/${this.id}.json5`, 'utf-8'));
}
static getDefaultInventory(): Inventory[] {
return JSON5.parse(fs.readFileSync('./data/default.json5', 'utf-8'));
}
hasItem(item: string): boolean {
const inv: Inventory[] = this.getInventory();
return inv.some(i => i.text.toLowerCase() === item.toLowerCase());
}
addItem(item: Inventory): void {
const inv: Inventory[] = this.getInventory();
inv.push(item);
fs.writeFileSync(`./data/${this.id}.json5`, JSON5.stringify(inv));
}
setInventory(inv: Inventory[]): void {
fs.writeFileSync(`./data/${this.id}.json5`, JSON5.stringify(inv));
}
}
export default InventoryUtil;

View file

@ -104,6 +104,11 @@
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
"skipLibCheck": true, /* Skip type checking all .d.ts files. */
"baseUrl": "./src",
"paths": {
"@/*": ["*"],
}
}
}