diff --git a/package.json b/package.json index 3d3888b..208ddfc 100644 --- a/package.json +++ b/package.json @@ -1,41 +1,42 @@ -{ - "name": "d.js-v12-bot", - "version": "0.0.1", - "description": "A Discord bot built on Discord.JS v12", - "main": "dist/index.js", - "private": true, - "dependencies": { - "chalk": "^4.1.0", - "discord.js": "^12.4.0", - "discord.js-lavalink-lib": "^0.1.7", - "inquirer": "^7.3.3", - "moment": "^2.29.1", - "ms": "^2.1.2", - "os": "^0.1.1" - }, - "devDependencies": { - "@types/inquirer": "^6.5.0", - "@types/mocha": "^8.0.3", - "@types/ms": "^0.7.31", - "@types/node": "^14.14.2", - "@types/ws": "^7.2.7", - "mocha": "^8.2.0", - "prettier": "2.1.2", - "ts-node": "^9.0.0", - "tsc-watch": "^4.2.9", - "typescript": "^3.9.7" - }, - "scripts": { - "build": "tsc && npm prune --production", - "start": "node dist/index.js", - "once": "tsc && npm start", - "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"", - "test": "mocha --require ts-node/register --extension ts --recursive" - }, - "keywords": [ - "discord.js", - "bot" - ], - "author": "Keanu Timmermans", - "license": "MIT" -} +{ + "name": "d.js-v12-bot", + "version": "0.0.1", + "description": "A Discord bot built on Discord.JS v12", + "main": "dist/index.js", + "private": true, + "dependencies": { + "chalk": "^4.1.0", + "discord.js": "^12.4.0", + "discord.js-lavalink-lib": "^0.1.7", + "inquirer": "^7.3.3", + "moment": "^2.29.1", + "ms": "^2.1.2", + "os": "^0.1.1" + }, + "devDependencies": { + "@types/inquirer": "^6.5.0", + "@types/mocha": "^8.0.3", + "@types/ms": "^0.7.31", + "@types/node": "^14.14.2", + "@types/ws": "^7.2.7", + "mocha": "^8.2.0", + "prettier": "2.1.2", + "ts-node": "^9.0.0", + "tsc-watch": "^4.2.9", + "typescript": "^3.9.7" + }, + "scripts": { + "build": "tsc && npm prune --production", + "start": "node dist/index.js", + "once": "tsc && npm start", + "dev": "tsc-watch --onSuccess \"node dist/index.js dev\"", + "test": "mocha --require ts-node/register --extension ts --recursive", + "format": "prettier --write **/*" + }, + "keywords": [ + "discord.js", + "bot" + ], + "author": "Keanu Timmermans", + "license": "MIT" +} \ No newline at end of file diff --git a/prettier.config.js b/prettier.config.js index 7584823..4cbaf1d 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -10,4 +10,5 @@ module.exports = { bracketSpacing: true, jsxBracketSameLine: true, arrowParens: 'always', + endOfLine: 'auto', }; diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts new file mode 100644 index 0000000..ceab224 --- /dev/null +++ b/src/commands/fun/eco.ts @@ -0,0 +1,35 @@ +import Command from '../../core/command'; +import { isAuthorized, getMoneyEmbed } from './subcommands/eco-utils'; +import { DailyCommand, PayCommand, GuildCommand } from './subcommands/eco-core'; +import { BuyCommand, ShopCommand } from './subcommands/eco-shop'; + +export default new Command({ + description: 'Economy command for Monika.', + + async run({ guild, channel, author }) { + if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(author)); + }, + subcommands: { + daily: DailyCommand, + pay: PayCommand, + guild: GuildCommand, + buy: BuyCommand, + shop: ShopCommand, + }, + user: new Command({ + description: + 'See how much money someone else has by using their user ID or pinging them.', + async run({ guild, channel, args }) { + if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(args[0])); + }, + }), + any: new Command({ + description: 'See how much money someone else has by using their username.', + async run({ guild, channel, args, callMemberByUsername, message }) { + if (isAuthorized(guild, channel)) + callMemberByUsername(message, args.join(' '), (member) => { + channel.send(getMoneyEmbed(member.user)); + }); + }, + }), +}); diff --git a/src/commands/fun/subcommands/eco-core.ts b/src/commands/fun/subcommands/eco-core.ts new file mode 100644 index 0000000..e2c8c5c --- /dev/null +++ b/src/commands/fun/subcommands/eco-core.ts @@ -0,0 +1,180 @@ +import Command from '../../../core/command'; +import $ from '../../../core/lib'; +import { Storage } from '../../../core/structures'; +import { isAuthorized, getMoneyEmbed, getSendEmbed } from './eco-utils'; + +export const DailyCommand = new Command({ + description: + 'Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.', + async run({ author, channel, guild }) { + if (isAuthorized(guild, channel)) { + const user = Storage.getUser(author.id); + const now = Date.now(); + + if (now - user.lastReceived >= 79200000) { + user.money++; + user.lastReceived = now; + Storage.save(); + channel.send({ + embed: { + title: 'Daily Reward', + description: 'You received 1 Mon!', + color: 0xf1c40f, + }, + }); + } else + channel.send({ + embed: { + title: 'Daily Reward', + description: `It's too soon to pick up your daily credits. You have about ${( + (user.lastReceived + 79200000 - now) / + 3600000 + ).toFixed(1)} hours to go.`, + color: 0xf1c40f, + }, + }); + } + }, +}); + +export const GuildCommand = new Command({ + description: 'See the richest players.', + async run({ guild, channel, client }) { + if (isAuthorized(guild, channel)) { + const users = Storage.users; + const ids = Object.keys(users); + ids.sort((a, b) => users[b].money - users[a].money); + const fields = []; + + for (let i = 0, limit = Math.min(10, ids.length); i < limit; i++) { + const id = ids[i]; + const user = await client.users.fetch(id); + + fields.push({ + name: `#${i + 1}. ${user.username}#${user.discriminator}`, + value: $(users[id].money).pluralise('credit', 's'), + }); + } + + channel.send({ + embed: { + title: 'Top 10 Richest Players', + color: '#ffff00', + fields: fields, + }, + }); + } + }, +}); + +export const PayCommand = new Command({ + description: 'Send money to someone.', + usage: ' ', + run: 'Who are you sending this money to?', + user: new Command({ + run: "You need to enter an amount you're sending!", + number: new Command({ + async run({ args, author, channel, guild }): Promise { + if (isAuthorized(guild, channel)) { + const amount = Math.floor(args[1]); + const sender = Storage.getUser(author.id); + const target = args[0]; + const receiver = Storage.getUser(target.id); + + if (amount <= 0) + return channel.send('You must send at least one Mon!'); + else if (sender.money < amount) + return channel.send( + "You don't have enough Mons for that.", + getMoneyEmbed(author), + ); + else if (target.id === author.id) + return channel.send("You can't send Mons to yourself!"); + else if (target.bot && process.argv[2] !== 'dev') + return channel.send("You can't send Mons to a bot!"); + + sender.money -= amount; + receiver.money += amount; + Storage.save(); + return channel.send(getSendEmbed(author, target, amount)); + } + }, + }), + }), + number: new Command({ + run: 'You must use the format `money send `!', + }), + any: new Command({ + async run({ args, author, channel, guild, prompt }) { + if (isAuthorized(guild, channel)) { + const last = args.pop(); + + if (!/\d+/g.test(last) && args.length === 0) + return channel.send("You need to enter an amount you're sending!"); + + const amount = Math.floor(last); + const sender = Storage.getUser(author.id); + + if (amount <= 0) + return channel.send('You must send at least one credit!'); + else if (sender.money < amount) + return channel.send( + "You don't have enough money to do that!", + getMoneyEmbed(author), + ); + else if (!guild) + return channel.send( + 'You have to use this in a server if you want to send money with a username!', + ); + + const username = args.join(' '); + const member = ( + await guild.members.fetch({ + query: username, + limit: 1, + }) + ).first(); + + if (!member) + return channel.send( + `Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`, + ); + else if (member.user.id === author.id) + return channel.send("You can't send money to yourself!"); + else if (member.user.bot && process.argv[2] !== 'dev') + return channel.send("You can't send money to a bot!"); + + const target = member.user; + + return prompt( + await channel.send( + `Are you sure you want to send ${$(amount).pluralise( + 'credit', + 's', + )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, + { + embed: { + color: '#ffff00', + author: { + name: `${target.username}#${target.discriminator}`, + icon_url: target.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + }, + }, + ), + author.id, + () => { + const receiver = Storage.getUser(target.id); + sender.money -= amount; + receiver.money += amount; + Storage.save(); + channel.send(getSendEmbed(author, target, amount)); + }, + ); + } + }, + }), +}); diff --git a/src/commands/fun/subcommands/eco-shop-items.ts b/src/commands/fun/subcommands/eco-shop-items.ts new file mode 100644 index 0000000..b7531cd --- /dev/null +++ b/src/commands/fun/subcommands/eco-shop-items.ts @@ -0,0 +1,73 @@ +import { Message } from 'discord.js'; +import $ from '../../../core/lib'; + +export interface ShopItem { + cost: number; + title: string; + description: string; + usage: string; + run(message: Message, cost: number, amount: number): void; +} + +export const ShopItems: ShopItem[] = [ + { + cost: 1, + title: 'Hug', + description: 'Hug Monika.', + usage: 'hug', + run(message, cost) { + message.channel.send( + `Transaction of ${cost} Mon completed successfully. <@394808963356688394>`, + ); + }, + }, + { + cost: 2, + title: 'Handholding', + description: "Hold Monika's hand.", + usage: 'handhold', + run(message, cost) { + message.channel.send( + `Transaction of ${cost} Mons completed successfully. <@394808963356688394>`, + ); + }, + }, + { + cost: 1, + title: 'Cute', + description: 'Calls Monika cute.', + usage: 'cute', + run(message) { + message.channel.send('<:MoniCheeseBlushRed:637513137083383826>'); + }, + }, + { + cost: 3, + title: 'Laser Bridge', + description: 'Buys what is technically a laser bridge.', + usage: 'laser bridge', + run(message) { + message.channel.send($(lines).random(), { + files: [ + { + attachment: + 'https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/TheUltimateLaser.gif', + }, + ], + }); + }, + }, +]; + +const lines = [ + "It's technically a laser bridge. No refunds.", + 'You want a laser bridge? You got one!', + "Now what'd they say about building bridges... Oh wait, looks like I nuked the planet again. Whoops!", + 'I saw this redhead the other day who was so excited to buy what I was selling. Needless to say, she was not very happy with me afterward.', + "Sorry, but you'll have to wait until the Laser Bridge Builder leaves early access.", + 'Thank you for your purchase! For you see, this is the legendary laser of obliteration that has been defended and preserved for countless generations!', + 'They say that a certain troll dwells under this laser bridge, waiting for an unlucky person to fall for th- I mean- Thank you for your purchase!', + "Buy?! Hah! How about our new rental service for just under $9.99 a month? But wait, there's more! For just $99.99, you can rent this laser bridge for an entire year and save 16.67% as opposed to renting it monthly!", + 'Good choice. Owning a laser bridge is the penultimate experience that all true seekers strive for!', + 'I can already imagine the reviews...\n"9/10 needs more lasers"', +]; diff --git a/src/commands/fun/subcommands/eco-shop.ts b/src/commands/fun/subcommands/eco-shop.ts new file mode 100644 index 0000000..9c3a5f8 --- /dev/null +++ b/src/commands/fun/subcommands/eco-shop.ts @@ -0,0 +1,100 @@ +import Command from '../../../core/command'; +import $ from '../../../core/lib'; +import { Storage, getPrefix } from '../../../core/structures'; +import { isAuthorized, getMoneyEmbed, getSendEmbed } from './eco-utils'; +import { ShopItems, ShopItem } from './eco-shop-items'; +import { EmbedField } from 'discord.js'; + +export const ShopCommand = new Command({ + description: 'Displays the list of items you can buy in the shop.', + async run({ guild, channel, author }) { + if (isAuthorized(guild, channel)) { + function getShopEmbed(selection: ShopItem[], title = 'Shop') { + const fields: EmbedField[] = []; + + for (const item of selection) + fields.push({ + name: `**${item.title}** (${getPrefix(guild)}eco buy ${ + item.usage + })`, + value: `${item.description} Costs ${$(item.cost).pluralise( + 'Mon', + 's', + )}.`, + inline: false, + }); + + return { + embed: { + color: 0xf1c40f, + title: title, + fields: fields, + footer: { + text: 'Mon Shop | TravBot Services', + }, + }, + }; + } + + // In case there's just one page, omit unnecessary details. + if (ShopItems.length <= 5) channel.send(getShopEmbed(ShopItems)); + else { + const shopPages = $(ShopItems).split(5); + const pageAmount = shopPages.length; + const msg = await channel.send( + getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`), + ); + + $.paginate(msg, author.id, pageAmount, (page) => { + msg.edit( + getShopEmbed( + shopPages[page], + `Shop (Page ${page + 1} of ${pageAmount})`, + ), + ); + }); + } + } + }, +}); + +export const BuyCommand = new Command({ + description: 'Buys an item from the shop.', + usage: '', + async run({ guild, channel, args, message, author }) { + if (isAuthorized(guild, channel)) { + let found = false; + + let amount = 1; // The amount the user is buying. + + // For now, no shop items support being bought multiple times. Uncomment these 2 lines when it's supported/needed. + //if (/\d+/g.test(args[args.length - 1])) + //amount = parseInt(args.pop()); + + let requested = args.join(' '); // The item the user is buying. + + for (let item of ShopItems) { + if (item.usage === requested) { + const user = Storage.getUser(author.id); + const cost = item.cost * amount; + + if (cost > user.money) { + channel.send('Not enough Mons!'); + } else { + user.money -= cost; + Storage.save(); + item.run(message, cost, amount); + } + + found = true; + break; + } + } + + if (!found) + channel.send( + `There's no item in the shop that goes by \`${requested}\`!`, + ); + } + }, +}); diff --git a/src/commands/fun/subcommands/eco-utils.ts b/src/commands/fun/subcommands/eco-utils.ts new file mode 100644 index 0000000..3af6971 --- /dev/null +++ b/src/commands/fun/subcommands/eco-utils.ts @@ -0,0 +1,81 @@ +import $ from '../../../core/lib'; +import { Storage } from '../../../core/structures'; +import { User, Guild, TextChannel, DMChannel, NewsChannel } from 'discord.js'; + +export function getMoneyEmbed(user: User): object { + const profile = Storage.getUser(user.id); + + return { + embed: { + color: 0xffff00, + author: { + name: user.username, + icon_url: user.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + fields: [ + { + name: 'Balance', + value: $(profile.money).pluralise('credit', 's'), + }, + ], + }, + }; +} + +export function getSendEmbed( + sender: User, + receiver: User, + amount: number, +): object { + return { + embed: { + color: 0xffff00, + author: { + name: sender.username, + icon_url: sender.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + title: 'Transaction', + description: `${sender.toString()} has sent ${$(amount).pluralise( + 'credit', + 's', + )} to ${receiver.toString()}!`, + fields: [ + { + name: `Sender: ${sender.username}#${sender.discriminator}`, + value: $(Storage.getUser(sender.id).money).pluralise('credit', 's'), + }, + { + name: `Receiver: ${receiver.username}#${receiver.discriminator}`, + value: $(Storage.getUser(receiver.id).money).pluralise('credit', 's'), + }, + ], + footer: { + text: receiver.username, + icon_url: receiver.displayAvatarURL({ + format: 'png', + dynamic: true, + }), + }, + }, + }; +} + +export function isAuthorized( + guild: Guild | null, + channel: TextChannel | DMChannel | NewsChannel, +): boolean { + if (guild?.id === '637512823676600330' || process.argv[2] === 'dev') + return true; + else { + channel.send( + "Sorry, this command can only be used in Monika's emote server.", + ); + return false; + } +} diff --git a/src/commands/money.ts b/src/commands/money.ts deleted file mode 100644 index 7abffb9..0000000 --- a/src/commands/money.ts +++ /dev/null @@ -1,260 +0,0 @@ -import Command from '../core/command'; -import $, { CommonLibrary } from '../core/lib'; -import { Storage } from '../core/structures'; -import { User } from 'discord.js'; - -export function getMoneyEmbed(user: User): object { - const profile = Storage.getUser(user.id); - - return { - embed: { - color: 0xffff00, - author: { - name: user.username, - icon_url: user.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - fields: [ - { - name: 'Balance', - value: $(profile.money).pluralise('credit', 's'), - }, - ], - }, - }; -} - -function getSendEmbed(sender: User, receiver: User, amount: number): object { - return { - embed: { - color: 0xffff00, - author: { - name: sender.username, - icon_url: sender.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - title: 'Transaction', - description: `${sender.toString()} has sent ${$(amount).pluralise( - 'credit', - 's', - )} to ${receiver.toString()}!`, - fields: [ - { - name: `Sender: ${sender.username}#${sender.discriminator}`, - value: $(Storage.getUser(sender.id).money).pluralise('credit', 's'), - }, - { - name: `Receiver: ${receiver.username}#${receiver.discriminator}`, - value: $(Storage.getUser(receiver.id).money).pluralise('credit', 's'), - }, - ], - footer: { - text: receiver.username, - icon_url: receiver.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - }, - }; -} - -export default new Command({ - description: - 'See how much money you have. Also provides other commands related to money.', - async run($: CommonLibrary): Promise { - $.channel.send(getMoneyEmbed($.author)); - }, - subcommands: { - get: new Command({ - description: - 'Pick up your daily credits. The cooldown is per user and every 22 hours to allow for some leeway.', - async run($: CommonLibrary): Promise { - const user = Storage.getUser($.author.id); - const now = Date.now(); - - if (user.lastReceived === -1) { - user.money = 100; - user.lastReceived = now; - Storage.save(); - $.channel.send( - "Here's 100 credits to get started, the price of a sandwich in Rookie Harbor.", - getMoneyEmbed($.author), - ); - } else if (now - user.lastReceived >= 79200000) { - user.money += 25; - user.lastReceived = now; - Storage.save(); - $.channel.send( - "Here's your daily 25 credits.", - getMoneyEmbed($.author), - ); - } else - $.channel.send( - `It's too soon to pick up your daily credits. You have about ${( - (user.lastReceived + 79200000 - now) / - 3600000 - ).toFixed(1)} hours to go.`, - ); - }, - }), - send: new Command({ - description: 'Send money to someone.', - usage: ' ', - run: 'Who are you sending this money to?', - user: new Command({ - run: "You need to enter an amount you're sending!", - number: new Command({ - async run($: CommonLibrary): Promise { - const amount = Math.floor($.args[1]); - const author = $.author; - const sender = Storage.getUser(author.id); - const target = $.args[0]; - const receiver = Storage.getUser(target.id); - - if (amount <= 0) - return $.channel.send('You must send at least one credit!'); - else if (sender.money < amount) - return $.channel.send( - "You don't have enough money to do that!", - getMoneyEmbed(author), - ); - else if (target.id === author.id) - return $.channel.send("You can't send money to yourself!"); - else if (target.bot && process.argv[2] !== 'dev') - return $.channel.send("You can't send money to a bot!"); - - sender.money -= amount; - receiver.money += amount; - Storage.save(); - $.channel.send(getSendEmbed(author, target, amount)); - }, - }), - }), - number: new Command({ - run: 'You must use the format `money send `!', - }), - any: new Command({ - async run($: CommonLibrary): Promise { - const last = $.args.pop(); - - if (!/\d+/g.test(last) && $.args.length === 0) - return $.channel.send( - "You need to enter an amount you're sending!", - ); - - const amount = Math.floor(last); - const author = $.author; - const sender = Storage.getUser(author.id); - - if (amount <= 0) - return $.channel.send('You must send at least one credit!'); - else if (sender.money < amount) - return $.channel.send( - "You don't have enough money to do that!", - getMoneyEmbed(author), - ); - else if (!$.guild) - return $.channel.send( - 'You have to use this in a server if you want to send money with a username!', - ); - - const username = $.args.join(' '); - const member = ( - await $.guild.members.fetch({ - query: username, - limit: 1, - }) - ).first(); - - if (!member) - return $.channel.send( - `Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`, - ); - else if (member.user.id === author.id) - return $.channel.send("You can't send money to yourself!"); - else if (member.user.bot && process.argv[2] !== 'dev') - return $.channel.send("You can't send money to a bot!"); - - const target = member.user; - - $.prompt( - await $.channel.send( - `Are you sure you want to send ${$(amount).pluralise( - 'credit', - 's', - )} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`, - { - embed: { - color: '#ffff00', - author: { - name: `${target.username}#${target.discriminator}`, - icon_url: target.displayAvatarURL({ - format: 'png', - dynamic: true, - }), - }, - }, - }, - ), - $.author.id, - () => { - const receiver = Storage.getUser(target.id); - sender.money -= amount; - receiver.money += amount; - Storage.save(); - $.channel.send(getSendEmbed(author, target, amount)); - }, - ); - }, - }), - }), - leaderboard: new Command({ - description: - 'See the richest players tracked by this bot (across servers).', - async run($: CommonLibrary): Promise { - const users = Storage.users; - const ids = Object.keys(users); - ids.sort((a, b) => users[b].money - users[a].money); - const fields = []; - - for (let i = 0, limit = Math.min(10, ids.length); i < limit; i++) { - const id = ids[i]; - const user = await $.client.users.fetch(id); - - fields.push({ - name: `#${i + 1}. ${user.username}#${user.discriminator}`, - value: $(users[id].money).pluralise('credit', 's'), - }); - } - - $.channel.send({ - embed: { - title: 'Top 10 Richest Players', - color: '#ffff00', - fields: fields, - }, - }); - }, - }), - }, - user: new Command({ - description: - 'See how much money someone else has by using their user ID or pinging them.', - async run($: CommonLibrary): Promise { - $.channel.send(getMoneyEmbed($.args[0])); - }, - }), - any: new Command({ - description: 'See how much money someone else has by using their username.', - async run($: CommonLibrary): Promise { - $.callMemberByUsername($.message, $.args.join(' '), (member) => { - $.channel.send(getMoneyEmbed(member.user)); - }); - }, - }), -});