buy, use, delete, edit item

This commit is contained in:
stjet
2024-08-16 04:38:24 +00:00
parent 38c2700c2f
commit 3145f52df7
9 changed files with 258 additions and 5 deletions

41
commands/buy.ts Normal file
View File

@@ -0,0 +1,41 @@
import type { ChatInputCommandInteraction } from "discord.js";
import type { CommandData } from "./index";
import type { StoreItem, User } from "../db";
import { get_item, add_item_to_user, sub_balance } from "../db";
import { BotError } from "./common/error";
import { item_name_autocomplete } from "./common/autocompletes";
import { has_role } from "../util";
import config from "../config.json";
async function run(interaction: ChatInputCommandInteraction, user: User) {
const options = interaction.options;
const name: string = (await options.get("name")).value as string;
const quantity: number = (await options.get("quantity")).value as number;
if (quantity <= 0) throw new BotError("Can't buy 0 or less of an item. Nice try");
const item = await get_item(name);
if (!item) throw new BotError("Item does not exist");
if (item.roles_required.length > 0) {
for (const role_id of item.roles_required) {
if (!has_role(interaction, role_id)) throw new BotError("Missing one of the required roles to buy this item");
}
}
const total_cost = item.price * quantity;
if (!(await sub_balance(user.user, total_cost))) throw new BotError("Not enough balance to buy this item");
await add_item_to_user(user.user, item.name, quantity);
return await interaction.editReply(`Bought ${quantity} of \`${name}\` for ${total_cost} ${ total_cost === 1 ? config.currency : config.currency_plural }`);
}
const data: CommandData = {
name: "buy",
description: "Buy an item from the store",
registered_only: true,
ephemeral: false,
admin_only: false,
run,
autocomplete: item_name_autocomplete, //autocompletes for the "name" option
};
export default data;

View File

@@ -1,4 +1,4 @@
//also: edit_item, delete_item, store, buy, use_item, (admin: /take_item, /add_item)
//also: edit_item
import type { ChatInputCommandInteraction } from "discord.js";

28
commands/delete_item.ts Normal file
View File

@@ -0,0 +1,28 @@
import type { ChatInputCommandInteraction } from "discord.js";
import type { CommandData } from "./index";
import { delete_item, get_item } from "../db";
import { BotError } from "./common/error";
import { item_name_autocomplete } from "./common/autocompletes";
async function run(interaction: ChatInputCommandInteraction) {
await interaction.deferReply();
const options = interaction.options;
const name: string = (await options.get("name")).value as string;
if (!(await get_item(name))) throw new BotError("No item with that name exists to delete");
await delete_item(name);
return await interaction.editReply(`Deleted item \`${name}\``);
}
const data: CommandData = {
name: "delete_item",
description: "Delete item from the store and all users",
registered_only: false,
ephemeral: false,
admin_only: true,
run,
autocomplete: item_name_autocomplete, //autocompletes for the "name" option
};
export default data;

47
commands/edit_item.ts Normal file
View File

@@ -0,0 +1,47 @@
import type { ChatInputCommandInteraction } from "discord.js";
import type { CommandData } from "./index";
import type { Items, StoreItem, User } from "../db";
import { edit_item, get_item } from "../db";
import { BotError } from "./common/error";
import { item_name_autocomplete } from "./common/autocompletes";
async function run(interaction: ChatInputCommandInteraction) {
await interaction.deferReply();
const options = interaction.options;
const name: string = (await options.get("name")).value as string;
const delete_existing_roles: boolean = (await options.get("delete_existing_roles")).value as boolean;
const item = await get_item(name);
if (!item) throw new BotError("No item of that name exists");
const price: number = ((await options.get("price"))?.value ?? item.price) as number;
const description: string = ((await options.get("description"))?.value ?? item.description) as string;
const usable: boolean = ((await options.get("usable"))?.value ?? item.usable) as boolean;
//to add multiple roles, people will have to use /edit_item, I guess? augh
const required_role = (await options.get("required_role"))?.role;
if (price < 0) throw new BotError("Price cannot be negative");
//name and description char limits (based on discord embed field name/value limits)
if (description.length > 900) throw new BotError("Item description cannot be more than 1024 characters"); //true limit is 1024 but we want some margin for other info
const existing = delete_existing_roles ? [] : item.roles_required;
const store_item: StoreItem = {
name,
price,
description,
roles_required: required_role ? [...existing, required_role.id] : existing,
usable,
};
await edit_item(store_item);
return await interaction.editReply("Item edited");
}
const data: CommandData = {
name: "edit_item",
description: "Edit item",
registered_only: false,
ephemeral: false,
admin_only: true,
run,
autocomplete: item_name_autocomplete, //autocompletes for the "name" option
};
export default data;

View File

@@ -5,6 +5,7 @@ import type { User } from "../db";
import { get_user } from "../db";
import { BotError } from "./common/error";
import config from "../config.json";
import { has_role } from "../util";
import say from "./say";
import roll from "./roll";
@@ -16,6 +17,10 @@ import items from "./items";
import create_item from "./create_item";
import store from "./store";
import change_item_balance from "./change_item_balance";
import buy from "./buy";
import use_item from "./use_item";
import delete_item from "./delete_item";
import edit_item from "./edit_item";
export interface CommandData {
name: string;
@@ -27,10 +32,10 @@ export interface CommandData {
autocomplete?: (interaction: AutocompleteInteraction) => Promise<any>;
};
const commands: CommandData[] = [say, roll, register_user, bal, change_bal, transfer, items, create_item, store, change_item_balance];
const commands: CommandData[] = [say, roll, register_user, bal, change_bal, transfer, items, create_item, store, change_item_balance, buy, use_item, delete_item, edit_item];
function is_admin(interaction: ChatInputCommandInteraction): boolean {
return (interaction.member as GuildMember).roles.cache.some((r) => r.id === config.admin_role);
return has_role(interaction, config.admin_role);
}
async function run(interaction: ChatInputCommandInteraction, found: CommandData, name: string) {

34
commands/use_item.ts Normal file
View File

@@ -0,0 +1,34 @@
import type { ChatInputCommandInteraction } from "discord.js";
import type { CommandData } from "./index";
import type { StoreItem, User } from "../db";
import { get_item, sub_item_to_user } from "../db";
import { BotError } from "./common/error";
import { item_name_autocomplete } from "./common/autocompletes";
async function run(interaction: ChatInputCommandInteraction, user: User) {
const options = interaction.options;
const name: string = (await options.get("name")).value as string;
const quantity: number = (await options.get("quantity")).value as number;
if (quantity <= 0) throw new BotError("Can't use 0 or less of an item");
const item = await get_item(name);
if (!item) throw new BotError("Item does not exist");
if (!item.usable) throw new BotError("That item is not usable");
if (!(await sub_item_to_user(user.user, name, quantity))) throw new BotError("You did not have enough of that item to use");
return await interaction.editReply(`Used ${quantity} of \`${name}\``);
}
const data: CommandData = {
name: "use_item",
description: "Use an item (subtracts from your items)",
registered_only: true,
ephemeral: false,
admin_only: false,
run,
autocomplete: item_name_autocomplete, //autocompletes for the "name" option
};
export default data;

4
db.ts
View File

@@ -124,7 +124,7 @@ export async function get_all_items(): Promise<StoreItem[]> {
return await (await store.find()).toArray();
}
export async function get_item(item: string): Promise<StoreItem[]> {
export async function get_item(item: string): Promise<StoreItem> {
return await store.findOne({ name: item });
}
@@ -134,7 +134,7 @@ export async function create_item(store_item: StoreItem) {
//assume name cannot be edited
export async function edit_item(store_item: StoreItem) {
return await store.updateOne({ name: store_item.name }, store_item);
return await store.replaceOne({ name: store_item.name }, store_item);
}
export async function delete_item(item: string) {

View File

@@ -203,6 +203,100 @@ const commands = [
},
],
},
{
name: "buy",
description: "Buy an item from the store",
options: [
{
type: 3,
name: "name",
description: "Name of the item",
required: true,
autocomplete: true,
},
{
type: 4,
name: "quantity",
description: "Amount of the item to buy",
required: true,
},
],
},
{
name: "use_item",
description: "Use an item (subtracts from your items)",
options: [
{
type: 3,
name: "name",
description: "Name of the item",
required: true,
autocomplete: true,
},
{
type: 4,
name: "quantity",
description: "Amount of the item to use",
required: true,
},
],
},
{
name: "delete_item",
description: "Delete item from the store and all users (admin only)",
options: [
{
type: 3,
name: "name",
description: "Name of the item",
required: true,
autocomplete: true,
},
],
},
{
name: "edit_item",
description: "Create item (admin only)",
options: [
{
type: 3,
name: "name",
description: "Name of the item",
required: true,
autocomplete: true,
},
{
type: 5,
name: "delete_existing_roles",
description: "If true, deletes existing required roles as requirements",
required: true,
},
{
type: 4,
name: "price",
description: "Price of the item",
required: false,
},
{
type: 3,
name: "description",
description: "Description of the item",
required: false,
},
{
type: 5,
name: "usable",
description: "Whether it can be /use'd",
required: false,
},
{
type: 8,
name: "required_role",
description: "Roles that are required to buy this item.",
required: false,
},
],
},
];
(new REST().setToken(process.env.DISCORD_TOKEN)).put(Routes.applicationCommands(process.env.CLIENT_ID), { body: commands }).then(() => console.log("Finished reloading slash commands"));

View File

@@ -1,6 +1,10 @@
import type { ChatInputCommandInteraction, GuildMember } from "discord.js";
import type { UpdateResult } from "mongodb";
export function did_update(result: UpdateResult): boolean {
return result.modifiedCount > 0;
}
export function has_role(interaction: ChatInputCommandInteraction, role_id: string): boolean {
return (interaction.member as GuildMember).roles.cache.some((r) => r.id === role_id);
}