diff --git a/README.md b/README.md index 66dc7d5..a708d72 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ COMMAND|FUNCTION|USER|ALIASES|DISABLEABLE `inventory [target]`|Get inventory contents of target or self|anyone|`inventory` `inv`|:white_check_mark: `give {target} {item} {amount}`|Give targeted user amount of items|anyone|`give`|:white_check_mark: `use {item} ...`|Use item. More info at [The items section](#items)|anyone|`use`|:white_check_mark: -`admingive {target} {item} {amount}`|Give targeted user amount of new items|admins|`admingive`|:x: +`admingive {target} {item} {amount}`|Give targeted user amount of new items|admins|`admingive`|:white_check_mark: ### Administrative commands diff --git a/bot/auth.ts b/bot/auth.ts index ecbaada..c9b859d 100644 --- a/bot/auth.ts +++ b/bot/auth.ts @@ -6,7 +6,7 @@ async function initAuth(userId: string, clientId: string, clientSecret: string, const redirectURL = process.env.REDIRECT_URL ?? `http://localhost:${port}`; // Set the default url and port to http://localhost:3456 - const state = Bun.randomUUIDv7().replace(/-/g, "").slice(0, 32); + const state = Bun.randomUUIDv7().replace(/-/g, "").slice(0, 32).toUpperCase(); // Generate random state variable to prevent cross-site-scripting attacks const instruction = `Visit this URL as ${streamer ? 'the streamer' : 'the chatter'} to authenticate the bot.` diff --git a/bot/commands/addadmin.ts b/bot/commands/addadmin.ts index 85c3a21..a3a3cd3 100644 --- a/bot/commands/addadmin.ts +++ b/bot/commands/addadmin.ts @@ -2,10 +2,8 @@ import { Command, sendMessage } from "."; import { addAdmin } from "../lib/admins"; import parseCommandArgs from "../lib/parseCommandArgs"; import { User } from "../user"; -import { unbannableUsers } from ".."; -export default new Command('addadmin', ['addadmin'], [], async msg => { - if (!unbannableUsers.includes(msg.chatterId)) return; +export default new Command('addadmin', ['addadmin'], 'unbannable', async msg => { const args = parseCommandArgs(msg.messageText); if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; const target = await User.initUsername(args[0].toLowerCase()); diff --git a/bot/commands/admingive.ts b/bot/commands/admingive.ts index 17bf5e3..2541717 100644 --- a/bot/commands/admingive.ts +++ b/bot/commands/admingive.ts @@ -1,12 +1,10 @@ import { Command, sendMessage } from "."; import { getUserRecord } from "../db/dbUser"; import items, { changeItemCount } from "../items"; -import { isAdmin } from "../lib/admins"; import parseCommandArgs from "../lib/parseCommandArgs"; import { User } from "../user"; -export default new Command('admingive', ['admingive'], [], async msg => { - if (!await isAdmin(msg.chatterId)) return; +export default new Command('admingive', ['admingive'], 'admin', async msg => { const args = parseCommandArgs(msg.messageText); if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; }; const target = await User.initUsername(args[0].toLowerCase()); @@ -28,4 +26,4 @@ export default new Command('admingive', ['admingive'], [], async msg => { await sendMessage(`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)}`, msg.messageId); }; await target.clearLock(); -}, false); +}); diff --git a/bot/commands/disablecommand.ts b/bot/commands/disablecommand.ts index 415c19f..428fb63 100644 --- a/bot/commands/disablecommand.ts +++ b/bot/commands/disablecommand.ts @@ -1,10 +1,8 @@ import { redis } from "bun"; import commands, { Command, sendMessage } from "."; -import { isAdmin } from "../lib/admins"; import parseCommandArgs from "../lib/parseCommandArgs"; -export default new Command('disablecommand', ['disablecommand'], [], async msg => { - if (!await isAdmin(msg.chatterId)) return; +export default new Command('disablecommand', ['disablecommand'], 'admin', async msg => { const args = parseCommandArgs(msg.messageText); if (!args[0]) { await sendMessage('Please specify a command to disable', msg.messageId); return; }; const selection = commands.get(args[0].toLowerCase()); diff --git a/bot/commands/enablecommand.ts b/bot/commands/enablecommand.ts index bec566b..903221d 100644 --- a/bot/commands/enablecommand.ts +++ b/bot/commands/enablecommand.ts @@ -1,10 +1,8 @@ import { redis } from "bun"; import commands, { Command, sendMessage } from "."; -import { isAdmin } from "../lib/admins"; import parseCommandArgs from "../lib/parseCommandArgs"; -export default new Command('enablecommand', ['enablecommand'], [], async msg => { - if (!await isAdmin(msg.chatterId)) return; +export default new Command('enablecommand', ['enablecommand'], 'admin', async msg => { const args = parseCommandArgs(msg.messageText); if (!args[0]) { await sendMessage('Please specify a command to enable', msg.messageId); return; }; const selection = commands.get(args[0].toLowerCase()); diff --git a/bot/commands/getadmins.ts b/bot/commands/getadmins.ts index 35ae4cc..e38de6a 100644 --- a/bot/commands/getadmins.ts +++ b/bot/commands/getadmins.ts @@ -2,7 +2,7 @@ import { Command, sendMessage } from "."; import { getAdmins } from "../lib/admins"; import { User } from "../user"; -export default new Command('getadmins', ['getadmins'], [], async msg => { +export default new Command('getadmins', ['getadmins'], 'chatter', async msg => { const admins = await getAdmins() const adminnames: string[] = []; for (const id of admins) { diff --git a/bot/commands/getcommands.ts b/bot/commands/getcommands.ts index 8dd85ef..213fd42 100644 --- a/bot/commands/getcommands.ts +++ b/bot/commands/getcommands.ts @@ -2,14 +2,14 @@ import { redis } from "bun"; import { basecommands, Command, sendMessage } from "."; import parseCommandArgs from "../lib/parseCommandArgs"; -export default new Command('getcommands', ['getcommands', 'getc'], [], async msg => { +export default new Command('getcommands', ['getcommands', 'getc'], 'chatter', async msg => { const args = parseCommandArgs(msg.messageText); - if (!args[0]) { await sendMessage(`A list of commands can be found here: https://github.com/qwerinope/qweribot#commands`, msg.messageId); return; }; + if (!args[0]) { await sendMessage(`A full list of commands can be found here: https://github.com/qwerinope/qweribot#commands`, msg.messageId); return; }; const disabledcommands = await redis.smembers('disabledcommands'); if (args[0].toLowerCase() === 'enabled') { const commandnames: string[] = []; for (const [name, command] of Array.from(basecommands.entries())) { - if (!command.disableable) continue; + if (command.usertype !== 'chatter') continue; // Admin only commands should be somewhat hidden if (disabledcommands.includes(name)) continue; commandnames.push(name); }; diff --git a/bot/commands/give.ts b/bot/commands/give.ts index 0412cda..3050471 100644 --- a/bot/commands/give.ts +++ b/bot/commands/give.ts @@ -5,7 +5,7 @@ import items, { changeItemCount } from "../items"; import parseCommandArgs from "../lib/parseCommandArgs"; import { User } from "../user"; -export default new Command('give', ['give'], [], async (msg, user) => { +export default new Command('give', ['give'], 'chatter', async (msg, user) => { const args = parseCommandArgs(msg.messageText); if (!args[0]) { await sendMessage('Please specify a user', msg.messageId); return; }; const target = await User.initUsername(args[0].toLowerCase()); @@ -34,6 +34,7 @@ export default new Command('give', ['give'], [], async (msg, user) => { const newamount = tempdata.inventory[item.name]!; await sendMessage(`${user.displayName} gave ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)} to ${target.displayName}. They now have ${newamount} ${item.prettyName + (newamount === 1 ? '' : item.plural)}`, msg.messageId); } else { + // TODO: Rewrite this section await sendMessage(`Failed to give ${target.displayName} ${amount} ${item.prettyName + (amount === 1 ? '' : item.plural)}`, msg.messageId); console.error(`WARNING: Item donation failed: target success: ${data[0] !== false}, donator success: ${data[0] !== false}`); }; diff --git a/bot/commands/index.ts b/bot/commands/index.ts index 45caf4c..83f2127 100644 --- a/bot/commands/index.ts +++ b/bot/commands/index.ts @@ -1,17 +1,19 @@ import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"; import { User } from "../user"; +export type userType = 'chatter' | 'admin' | 'unbannable'; + /** The Command class represents a command */ export class Command { public readonly name: string; public readonly aliases: string[]; - public readonly requiredIntents: string[]; + public readonly usertype: userType; public readonly disableable: boolean; public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User) => Promise; - constructor(name: string, aliases: string[], requiredIntents: string[], execution: (message: EventSubChannelChatMessageEvent, sender: User) => Promise, disableable?: boolean) { + constructor(name: string, aliases: string[], usertype: userType, execution: (message: EventSubChannelChatMessageEvent, sender: User) => Promise, disableable?: boolean) { this.name = name.toLowerCase(); this.aliases = aliases; - this.requiredIntents = requiredIntents; + this.usertype = usertype; this.execute = execution; this.disableable = disableable ?? true; }; @@ -19,7 +21,6 @@ export class Command { import { readdir } from 'node:fs/promises'; const commands = new Map; -const commandintents: string[] = []; const basecommands = new Map; const files = await readdir(import.meta.dir); @@ -27,7 +28,6 @@ for (const file of files) { if (!file.endsWith('.ts')) continue; if (file === import.meta.file) continue; const command: Command = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); - commandintents.push(...command.requiredIntents); basecommands.set(command.name, command); for (const alias of command.aliases) { commands.set(alias, command); // Since it's not a primitive type the map is filled with references to the command, not the actual object @@ -40,7 +40,7 @@ for (const [name, item] of Array.from(items)) { }; export default commands; -export { commandintents, basecommands }; +export { basecommands }; import { singleUserMode, chatterApi, chatterId, streamerId } from ".."; diff --git a/bot/commands/inventory.ts b/bot/commands/inventory.ts index 83ac6e1..9978e1b 100644 --- a/bot/commands/inventory.ts +++ b/bot/commands/inventory.ts @@ -4,7 +4,7 @@ import parseCommandArgs from "../lib/parseCommandArgs"; import { User } from "../user"; import items from "../items"; -export default new Command('inventory', ['inv', 'inventory'], [], async (msg, user) => { +export default new Command('inventory', ['inv', 'inventory'], 'chatter', async (msg, user) => { const args = parseCommandArgs(msg.messageText); let target: User = user; if (args[0]) { diff --git a/bot/commands/iteminfo.ts b/bot/commands/iteminfo.ts index 77bcbe4..accb3c7 100644 --- a/bot/commands/iteminfo.ts +++ b/bot/commands/iteminfo.ts @@ -2,7 +2,7 @@ import { Command, sendMessage } from "."; import items from "../items"; import parseCommandArgs from "../lib/parseCommandArgs"; -export default new Command('iteminfo', ['iteminfo', 'itemhelp', 'info'], [], async msg => { +export default new Command('iteminfo', ['iteminfo', 'itemhelp', 'info'], 'chatter', async msg => { const messagequery = parseCommandArgs(msg.messageText).join(' '); if (!messagequery) { await sendMessage('Please specify an item you would like to get info about', msg.messageId); return; }; const selection = items.get(messagequery.toLowerCase()); diff --git a/bot/commands/ping.ts b/bot/commands/ping.ts index 3866dfe..ded8838 100644 --- a/bot/commands/ping.ts +++ b/bot/commands/ping.ts @@ -1,10 +1,6 @@ import { Command, sendMessage } from "."; // This command is purely for testing -export default new Command('ping', - ['ping'], - [], - async msg => { - await sendMessage('pong!', msg.messageId); - } -); +export default new Command('ping', ['ping'], 'chatter', async msg => { + await sendMessage('pong!', msg.messageId); +}); diff --git a/bot/commands/removeadmin.ts b/bot/commands/removeadmin.ts index 3924783..1bf0f59 100644 --- a/bot/commands/removeadmin.ts +++ b/bot/commands/removeadmin.ts @@ -4,8 +4,7 @@ import { removeAdmin } from "../lib/admins"; import parseCommandArgs from "../lib/parseCommandArgs"; import { User } from "../user"; -export default new Command('removeadmin', ['removeadmin'], [], async msg => { - if (!unbannableUsers.includes(msg.chatterId)) return; +export default new Command('removeadmin', ['removeadmin'], 'unbannable', async msg => { const args = parseCommandArgs(msg.messageText); if (!args[0]) { await sendMessage('Please specify a target', msg.messageId); return; }; const target = await User.initUsername(args[0].toLowerCase()); diff --git a/bot/commands/seiso.ts b/bot/commands/seiso.ts index 885278e..842332f 100644 --- a/bot/commands/seiso.ts +++ b/bot/commands/seiso.ts @@ -1,15 +1,15 @@ import { Command, sendMessage } from "."; import { timeout } from "../lib/timeout"; -export default new Command('seiso', ['seiso'], ['moderator:manage:banned_users'], async (msg, user) => { +export default new Command('seiso', ['seiso'], 'chatter', async (msg, user) => { const rand = Math.floor(Math.random() * 101); if (rand > 75) await sendMessage(`${rand}% seiso YAAAA`, msg.messageId); else if (rand > 51) await sendMessage(`${rand}% seiso POGGERS`, msg.messageId); else if (rand === 50) await sendMessage(`${rand}% seiso ok`, msg.messageId); else if (rand > 30) await sendMessage(`${rand}% seiso SWEAT`, msg.messageId); else if (rand > 10) await sendMessage(`${rand}% seiso catErm`, msg.messageId); - else { - await sendMessage(`${rand}% seiso RIPBOZO`); - timeout(user, 'TOO YABAI!', 60); - }; + else await Promise.all([ + sendMessage(`${rand}% seiso RIPBOZO`), + timeout(user, 'TOO YABAI!', 60) + ]); }); diff --git a/bot/commands/useitem.ts b/bot/commands/useitem.ts index 73dafd6..22fcfed 100644 --- a/bot/commands/useitem.ts +++ b/bot/commands/useitem.ts @@ -1,10 +1,12 @@ +import { redis } from "bun"; import { Command, sendMessage } from "."; import items from "../items"; -export default new Command('use', ['use'], [], async (msg, user) => { +export default new Command('use', ['use'], 'chatter', async (msg, user) => { const messagequery = msg.messageText.trim().split(' ').slice(1); if (!messagequery[0]) { await sendMessage('Please specify an item you would like to use', msg.messageId); return; }; const selection = items.get(messagequery[0].toLowerCase()); if (!selection) { await sendMessage(`'${messagequery[0]}' is not an item`, msg.messageId); return; }; + if (await redis.sismember('disabledcommands', selection.name)) { await sendMessage(`The ${selection.prettyName} item is disabled`, msg.messageId); return; }; await selection.execute(msg, user); }); diff --git a/bot/commands/vulnchatters.ts b/bot/commands/vulnchatters.ts index 7360d84..b9ae474 100644 --- a/bot/commands/vulnchatters.ts +++ b/bot/commands/vulnchatters.ts @@ -1,12 +1,8 @@ import { redis } from "bun"; import { Command, sendMessage } from "."; -export default new Command('vulnchatters', - ['vulnchatters', 'vulnc'], - [], - async msg => { - const data = await redis.keys('vulnchatters:*'); - const one = data.length === 1; - await sendMessage(`There ${one ? 'is' : 'are'} ${data.length} vulnerable chatter${one ? '' : 's'}`, msg.messageId); - } -); +export default new Command('vulnchatters', ['vulnchatters', 'vulnc'], 'chatter', async msg => { + const data = await redis.keys('vulnchatters:*'); + const one = data.length === 1; + await sendMessage(`There ${one ? 'is' : 'are'} ${data.length} vulnerable chatter${one ? '' : 's'}`, msg.messageId); +}); diff --git a/bot/commands/yabai.ts b/bot/commands/yabai.ts index 74e6921..19a1cce 100644 --- a/bot/commands/yabai.ts +++ b/bot/commands/yabai.ts @@ -2,18 +2,14 @@ import { Command, sendMessage } from "."; import { timeout } from "../lib/timeout"; // Remake of the !yabai command in ttv/kiara_tv -export default new Command('yabai', - ['yabai', 'goon'], - ['moderator:manage:banned_users'], - async (msg, user) => { - const rand = Math.floor(Math.random() * 101); - if (rand < 25) sendMessage(`${rand}% yabai! GIGACHAD`, msg.messageId); - else if (rand < 50) sendMessage(`${rand}% yabai POGGERS`, msg.messageId); - else if (rand === 50) sendMessage(`${rand}% yabai ok`, msg.messageId); - else if (rand < 90) sendMessage(`${rand}% yabai AINTNOWAY`, msg.messageId); - else { - sendMessage(`${msg.chatterDisplayName} is ${rand}% yabai CAUGHT`); - timeout(user, "TOO YABAI!", 60); - }; - } -); +export default new Command('yabai', ['yabai', 'goon'], 'chatter', async (msg, user) => { + const rand = Math.floor(Math.random() * 101); + if (rand < 25) sendMessage(`${rand}% yabai! GIGACHAD`, msg.messageId); + else if (rand < 50) sendMessage(`${rand}% yabai POGGERS`, msg.messageId); + else if (rand === 50) sendMessage(`${rand}% yabai ok`, msg.messageId); + else if (rand < 90) sendMessage(`${rand}% yabai AINTNOWAY`, msg.messageId); + else await Promise.all([ + sendMessage(`${msg.chatterDisplayName} is ${rand}% yabai CAUGHT`), + timeout(user, "TOO YABAI!", 60) + ]); +}); diff --git a/bot/events/message.ts b/bot/events/message.ts index 58d834a..e981a23 100644 --- a/bot/events/message.ts +++ b/bot/events/message.ts @@ -2,6 +2,7 @@ import { chatterId, streamerId, eventSub, commandPrefix, singleUserMode, unbanna import { User } from "../user"; import commands from "../commands"; import { redis } from "bun"; +import { isAdmin } from "../lib/admins"; console.info(`Loaded the following commands: ${commands.keys().toArray().join(', ')}`); @@ -30,6 +31,16 @@ eventSub.onChannelChatMessage(streamerId, streamerId, async msg => { const selected = commands.get(commandSelection.toLowerCase()); if (!selected) return; if (disabledcommands.includes(selected.name)) return; + + switch (selected.usertype) { + case "admin": + if (!await isAdmin(user!.id)) return; + break; + case "unbannable": + if (!unbannableUsers.includes(msg.chatterId)) return; + break; + }; + try { await selected.execute(msg, user!); } catch (err) { console.error(err); }; }; diff --git a/bot/index.ts b/bot/index.ts index 72d0b6c..4b5cf3d 100644 --- a/bot/index.ts +++ b/bot/index.ts @@ -1,12 +1,10 @@ import { createAuthProvider } from "./auth"; import { ApiClient } from "@twurple/api"; import { EventSubHttpListener, ReverseProxyAdapter } from "@twurple/eventsub-http"; -import { commandintents } from "./commands"; -import { itemintents } from "./items"; import { addAdmin } from "./lib/admins"; -const CHATTERBASEINTENTS = ["user:read:chat", "user:write:chat", "user:bot"]; -const STREAMERBASEINTENTS = ["user:read:chat", "moderation:read", "channel:manage:moderators"]; +const CHATTERINTENTS = ["user:read:chat", "user:write:chat", "user:bot"]; +const STREAMERINTENTS = ["user:read:chat", "moderation:read", "channel:manage:moderators", "moderator:manage:banned_users"]; export const singleUserMode = process.env.CHATTER_IS_STREAMER === 'true'; export const chatterId = process.env.CHATTER_ID ?? ""; @@ -19,11 +17,8 @@ if (hostName === "") { console.error('Please set a EVENTSUB_HOSTNAME in the .env const port = Number(process.env.EVENTSUB_PORT) ?? 0; if (port === 0) { console.error('Please set a EVENTSUB_PORT in the .env'); process.exit(1); }; -const streamerIntents = STREAMERBASEINTENTS.concat(commandintents, itemintents); -const chatterIntents = singleUserMode ? CHATTERBASEINTENTS.concat(streamerIntents) : CHATTERBASEINTENTS; - -export const chatterAuthProvider = await createAuthProvider(chatterId, chatterIntents); -export const streamerAuthProvider = singleUserMode ? undefined : await createAuthProvider(streamerId, streamerIntents, true); +export const chatterAuthProvider = await createAuthProvider(chatterId, singleUserMode ? CHATTERINTENTS.concat(STREAMERINTENTS) : CHATTERINTENTS); +export const streamerAuthProvider = singleUserMode ? undefined : await createAuthProvider(streamerId, STREAMERINTENTS, true); /** chatterApi should be used for sending messages, retrieving user data, etc */ export const chatterApi = new ApiClient({ authProvider: chatterAuthProvider }); diff --git a/bot/items/blaster.ts b/bot/items/blaster.ts index 286245e..44c770d 100644 --- a/bot/items/blaster.ts +++ b/bot/items/blaster.ts @@ -9,7 +9,7 @@ const ITEMNAME = 'blaster'; export default new Item(ITEMNAME, 'Blaster', 's', 'Times a specific person out for 60 seconds', - ['blaster', 'blast'], ['moderator:manage:banned_users'], + ['blaster', 'blast'], async (msg, user) => { const userObj = await getUserRecord(user); if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any blasters!`, msg.messageId); return; }; diff --git a/bot/items/grenade.ts b/bot/items/grenade.ts index bd56f6d..1e240a6 100644 --- a/bot/items/grenade.ts +++ b/bot/items/grenade.ts @@ -10,7 +10,6 @@ const ITEMNAME = 'grenade'; export default new Item(ITEMNAME, 'Grenade', 's', 'Give a random chatter a 60s timeout', ['grenade'], - ['moderator:manage:banned_users'], async (msg, user) => { const userObj = await getUserRecord(user); if (userObj.inventory[ITEMNAME]! < 1) { await sendMessage(`You don't have any grenades!`, msg.messageId); return; }; diff --git a/bot/items/index.ts b/bot/items/index.ts index 718d244..edc5861 100644 --- a/bot/items/index.ts +++ b/bot/items/index.ts @@ -1,5 +1,6 @@ import { EventSubChannelChatMessageEvent } from "@twurple/eventsub-base"; import { User } from "../user"; +import { type userType } from "../commands"; export class Item { public readonly name: string; @@ -7,24 +8,25 @@ export class Item { public readonly plural: string; public readonly description: string; public readonly aliases: string[]; - public readonly requiredIntents: string[]; + public readonly usertype: userType; public readonly execute: (message: EventSubChannelChatMessageEvent, sender: User) => Promise; + public readonly disableable: boolean; /** Creates an item object * @param name - internal name of item * @param prettyName - name of item for presenting to chat * @param plural - plural appendage; example: lootbox(es) * @param description - description of what item does * @param aliases - alternative ways to activate item - * @param requiredIntents - required twitch API scopes to use item * @param execution - code that gets executed when item gets used */ - constructor(name: string, prettyName: string, plural: string, description: string, aliases: string[], requiredIntents: string[], execution: (message: EventSubChannelChatMessageEvent, sender: User) => Promise) { + constructor(name: string, prettyName: string, plural: string, description: string, aliases: string[], execution: (message: EventSubChannelChatMessageEvent, sender: User) => Promise) { this.name = name; this.prettyName = prettyName; this.plural = plural; this.description = description; this.aliases = aliases; - this.requiredIntents = requiredIntents; + this.usertype = 'chatter'; // Items are usable by everyone this.execute = execution; + this.disableable = true; }; }; @@ -32,7 +34,6 @@ import { readdir } from 'node:fs/promises'; import type { userRecord } from "../db/connection"; import { updateUserRecord } from "../db/dbUser"; const items = new Map; -const itemintents: string[] = []; const emptyInventory: inventory = {}; const itemarray: string[] = []; @@ -43,14 +44,13 @@ for (const file of files) { const item: Item = await import(import.meta.dir + '/' + file.slice(0, -3)).then(a => a.default); emptyInventory[item.name] = 0; itemarray.push(item.name); - itemintents.push(...item.requiredIntents); for (const alias of item.aliases) { items.set(alias, item); // Since it's not a primitive type the map is filled with references to the item, not the actual object }; }; export default items; -export { itemintents, emptyInventory, itemarray }; +export { emptyInventory, itemarray }; export type inventory = { [key: string]: number; };