diff --git a/src/commands/fun/eco.ts b/src/commands/fun/eco.ts index 022383a..da7d97a 100644 --- a/src/commands/fun/eco.ts +++ b/src/commands/fun/eco.ts @@ -3,7 +3,6 @@ import {isAuthorized, getMoneyEmbed} from "./modules/eco-utils"; import {DailyCommand, PayCommand, GuildCommand, LeaderboardCommand} from "./modules/eco-core"; import {BuyCommand, ShopCommand} from "./modules/eco-shop"; import {MondayCommand} from "./modules/eco-extras"; -import {BetCommand} from "./modules/eco-bet"; export default new NamedCommand({ description: "Economy command for Monika.", @@ -17,8 +16,7 @@ export default new NamedCommand({ leaderboard: LeaderboardCommand, buy: BuyCommand, shop: ShopCommand, - monday: MondayCommand, - bet: BetCommand + monday: MondayCommand }, id: "user", user: new Command({ diff --git a/src/commands/fun/modules/eco-bet.ts b/src/commands/fun/modules/eco-bet.ts deleted file mode 100644 index f3ea452..0000000 --- a/src/commands/fun/modules/eco-bet.ts +++ /dev/null @@ -1,198 +0,0 @@ -import {Command, NamedCommand, askYesOrNo} from "../../../core"; -import {pluralise} from "../../../lib"; -import {Storage} from "../../../structures"; -import {isAuthorized, getMoneyEmbed, getSendEmbed, ECO_EMBED_COLOR} from "./eco-utils"; -import {User} from "discord.js"; - -export const BetCommand = new NamedCommand({ - description: "Bet your Mons with other people.", - usage: " ", - run: "Who are you betting with?", - user: new Command({ - description: "User to bet with.", - // handles missing amount argument - async run({args, author, channel, guild}) { - if (isAuthorized(guild, channel)) { - const target = args[0]; - - // handle invalid target - if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); - else if (target.bot && process.argv[2] !== "dev") return channel.send("You can't bet Mons with a bot!"); - - return channel.send("How much are you betting?"); - } else return; - }, - number: new Command({ - description: "Amount of Mons to bet.", - // handles missing duration argument - async run({args, author, channel, guild}) { - if (isAuthorized(guild, channel)) { - const sender = Storage.getUser(author.id); - const target = args[0] as User; - const receiver = Storage.getUser(target.id); - const amount = Math.floor(args[1]); - - // handle invalid target - if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); - else if (target.bot && process.argv[2] !== "dev") - return channel.send("You can't bet Mons with a bot!"); - - // handle invalid amount - if (amount <= 0) return channel.send("You must bet at least one Mon!"); - else if (sender.money < amount) - return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); - else if (receiver.money < amount) - return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); - - return channel.send("How long until the bet ends?"); - } else return; - }, - any: new Command({ - description: "Duration of the bet.", - async run({client, args, author, message, channel, guild}) { - if (isAuthorized(guild, channel)) { - // [Pertinence to make configurable on the fly.] - // Lower and upper bounds for bet - const durationBounds = {min: "1m", max: "1d"}; - - const sender = Storage.getUser(author.id); - const target = args[0] as User; - const receiver = Storage.getUser(target.id); - const amount = Math.floor(args[1]); - const duration = parseDuration(args[2].trim()); - - // handle invalid target - if (target.id == author.id) return channel.send("You can't bet Mons with yourself!"); - else if (target.bot && process.argv[2] !== "dev") - return channel.send("You can't bet Mons with a bot!"); - - // handle invalid amount - if (amount <= 0) return channel.send("You must bet at least one Mon!"); - else if (sender.money < amount) - return channel.send("You don't have enough Mons for that.", getMoneyEmbed(author)); - else if (receiver.money < amount) - return channel.send("They don't have enough Mons for that.", getMoneyEmbed(target)); - - // handle invalid duration - if (duration <= 0) return channel.send("Invalid bet duration"); - else if (duration <= parseDuration(durationBounds.min)) - return channel.send(`Bet duration is too short, maximum duration is ${durationBounds.min}`); - else if (duration >= parseDuration(durationBounds.max)) - return channel.send(`Bet duration is too long, maximum duration is ${durationBounds.max}`); - - // Ask target whether or not they want to take the bet. - const takeBet = await askYesOrNo( - await channel.send( - `<@${target.id}>, do you want to take this bet of ${pluralise(amount, "Mon", "s")}` - ), - target.id - ); - - if (takeBet) { - // [MEDIUM PRIORITY: bet persistence to prevent losses in case of shutdown.] - // Remove amount money from both parts at the start to avoid duplication of money. - sender.money -= amount; - receiver.money -= amount; - // Very hacky solution for persistence but better than no solution, backup returns runs during the bot's setup code. - sender.ecoBetInsurance += amount; - receiver.ecoBetInsurance += amount; - Storage.save(); - - // Notify both users. - await channel.send( - `<@${target.id}> has taken <@${author.id}>'s bet, the bet amount of ${pluralise( - amount, - "Mon", - "s" - )} has been deducted from each of them.` - ); - - // Wait for the duration of the bet. - return client.setTimeout(async () => { - // In debug mode, saving the storage will break the references, so you have to redeclare sender and receiver for it to actually save. - const sender = Storage.getUser(author.id); - const receiver = Storage.getUser(target.id); - // [TODO: when D.JSv13 comes out, inline reply to clean up.] - // When bet is over, give a vote to ask people their thoughts. - const voteMsg = await channel.send( - `VOTE: do you think that <@${ - target.id - }> has won the bet?\nhttps://discord.com/channels/${guild!.id}/${channel.id}/${ - message.id - }` - ); - await voteMsg.react("✅"); - await voteMsg.react("❌"); - - // Filter reactions to only collect the pertinent ones. - voteMsg - .awaitReactions( - (reaction, user) => { - return ["✅", "❌"].includes(reaction.emoji.name); - }, - // [Pertinence to make configurable on the fly.] - {time: parseDuration("2m")} - ) - .then((reactions) => { - // Count votes - const okReaction = reactions.get("✅"); - const noReaction = reactions.get("❌"); - const ok = okReaction ? (okReaction.count ?? 1) - 1 : 0; - const no = noReaction ? (noReaction.count ?? 1) - 1 : 0; - - if (ok > no) { - receiver.money += amount * 2; - channel.send( - `By the people's votes, <@${target.id}> has won the bet that <@${author.id}> had sent them.` - ); - } else if (ok < no) { - sender.money += amount * 2; - channel.send( - `By the people's votes, <@${target.id}> has lost the bet that <@${author.id}> had sent them.` - ); - } else { - sender.money += amount; - receiver.money += amount; - channel.send( - `By the people's votes, <@${target.id}> couldn't be determined to have won or lost the bet that <@${author.id}> had sent them.` - ); - } - sender.ecoBetInsurance -= amount; - receiver.ecoBetInsurance -= amount; - Storage.save(); - }); - }, duration); - } else return await channel.send(`<@${target.id}> has rejected your bet, <@${author.id}>`); - } else return; - } - }) - }) - }) -}); - -/** - * Parses a duration string into milliseconds - * Examples: - * - 3d -> 3 days -> 259200000ms - * - 2h -> 2 hours -> 7200000ms - * - 7m -> 7 minutes -> 420000ms - * - 3s -> 3 seconds -> 3000ms - */ -function parseDuration(duration: string): number { - // extract last char as unit - const unit = duration[duration.length - 1].toLowerCase(); - // get the rest as value - let value: number = +duration.substring(0, duration.length - 1); - - if (!["d", "h", "m", "s"].includes(unit) || isNaN(value)) return 0; - - if (unit === "d") value *= 86400000; - // 1000ms * 60s * 60m * 24h - else if (unit === "h") value *= 3600000; - // 1000ms * 60s * 60m - else if (unit === "m") value *= 60000; - // 1000ms * 60s - else if (unit === "s") value *= 1000; // 1000ms - - return value; -} diff --git a/src/commands/fun/whois.ts b/src/commands/fun/whois.ts index d7285f6..cee90c0 100644 --- a/src/commands/fun/whois.ts +++ b/src/commands/fun/whois.ts @@ -32,8 +32,6 @@ const registry: {[id: string]: string} = { "You are, uhh, Stay Put, Soft Puppy, Es-Pee, Swift Pacemaker, Smug Poyo, and many more.\n...Seriously, this woman has too many names.", "243061915281129472": "Some random conlanger, worldbuilder and programmer doofus. ~~May also secretly be a nyan. :3~~", - "792751612904603668": - "Some random nyan. :3 ~~May also secretly be a conlanger, worldbuilder and programmer doofus.~~", "367439475153829892": "A weeb.", "760375501775700038": "˙qǝǝʍ ∀", "389178357302034442": "In his dreams, he is the star. its him. <:itsMe:808174425253871657>", diff --git a/src/commands/system/admin.ts b/src/commands/system/admin.ts index 5e0c225..ac0e9e0 100644 --- a/src/commands/system/admin.ts +++ b/src/commands/system/admin.ts @@ -119,32 +119,6 @@ export default new NamedCommand({ }) }) } - }), - stream: new NamedCommand({ - description: "Set a channel to send stream notifications. Type `#` to reference the channel.", - usage: "()", - async run({message, channel, guild, author, member, client, args}) { - const targetGuild = Storage.getGuild(guild!.id); - - if (targetGuild.streamingChannel) { - targetGuild.streamingChannel = null; - channel.send("Removed your server's stream notifications channel."); - } else { - targetGuild.streamingChannel = channel.id; - channel.send(`Set your server's stream notifications channel to ${channel}.`); - } - - Storage.save(); - }, - id: "channel", - channel: new Command({ - async run({message, channel, guild, author, member, client, args}) { - const result = args[0] as TextChannel; - Storage.getGuild(guild!.id).streamingChannel = result.id; - Storage.save(); - channel.send(`Successfully set this server's stream notifications channel to ${result}.`); - } - }) }) } }), diff --git a/src/commands/utility/react.ts b/src/commands/utility/react.ts index 2e018c9..43f3394 100644 --- a/src/commands/utility/react.ts +++ b/src/commands/utility/react.ts @@ -10,18 +10,7 @@ export default new NamedCommand({ let target: Message | undefined; let distance = 1; - // allows reactions by using an in-line reply - if (message.reference) { - const messageID = message.reference.messageID; - try { - target = await channel.messages.fetch(messageID!); - } catch { - return channel.send("Unknown error occurred!"); - } - } - - // handles reacts by message id/distance - else if (args.length >= 2) { + if (args.length >= 2) { const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator. const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,19})\/(\d{17,19})\/(\d{17,19}))$/; const copyIDPattern = /^(?:(\d{17,19})-(\d{17,19}))$/; diff --git a/src/commands/utility/streaminfo.ts b/src/commands/utility/streaminfo.ts deleted file mode 100644 index 969e668..0000000 --- a/src/commands/utility/streaminfo.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {Command, NamedCommand} from "../../core"; -import {streamList} from "../../modules/streamNotifications"; - -export default new NamedCommand({ - description: "Sets the description of your stream. You can embed links by writing `[some name](some link)`", - async run({message, channel, guild, author, member, client, args}) { - const userID = author.id; - - if (streamList.has(userID)) { - const stream = streamList.get(userID)!; - stream.description = args.join(" ") || "No description set."; - stream.update(); - } else { - // Alternatively, I could make descriptions last outside of just one stream. - channel.send("You can only use this command when streaming."); - } - } -}); diff --git a/src/index.ts b/src/index.ts index 6c917d7..67cacb5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -70,4 +70,3 @@ import "./modules/channelListener"; import "./modules/intercept"; import "./modules/messageEmbed"; import "./modules/guildMemberAdd"; -import "./modules/streamNotifications"; diff --git a/src/modules/ready.ts b/src/modules/ready.ts index 3b83964..eac1fa0 100644 --- a/src/modules/ready.ts +++ b/src/modules/ready.ts @@ -1,5 +1,5 @@ import {client} from "../index"; -import {Config, Storage} from "../structures"; +import {Config} from "../structures"; client.once("ready", () => { if (client.user) { @@ -10,21 +10,5 @@ client.once("ready", () => { type: "LISTENING", name: `${Config.prefix}help` }); - - // Run this setup block once to restore eco bet money in case the bot went down. (And I guess search the client for those users to let them know too.) - for (const id in Storage.users) { - const user = Storage.users[id]; - - if (user.ecoBetInsurance > 0) { - client.users.cache - .get(id) - ?.send( - `Because my system either crashed or restarted while you had a pending bet, the total amount of money that you bet, which was \`${user.ecoBetInsurance}\`, has been restored.` - ); - user.money += user.ecoBetInsurance; - user.ecoBetInsurance = 0; - } - } - Storage.save(); } }); diff --git a/src/modules/streamNotifications.ts b/src/modules/streamNotifications.ts deleted file mode 100644 index f6e2843..0000000 --- a/src/modules/streamNotifications.ts +++ /dev/null @@ -1,84 +0,0 @@ -import {GuildMember, VoiceChannel, MessageEmbed, TextChannel, Permissions, Message, Collection} from "discord.js"; -import {client} from "../index"; -import {Storage} from "../structures"; - -type Stream = { - streamer: GuildMember; - channel: VoiceChannel; - description?: string; - message: Message; - update: () => void; -}; - -// A list of user IDs and message embeds. -export const streamList = new Collection(); - -// Probably find a better, DRY way of doing this. -function getStreamEmbed(streamer: GuildMember, channel: VoiceChannel, description?: string): MessageEmbed { - const user = streamer.user; - const embed = new MessageEmbed() - .setTitle(`Stream: \`#${channel.name}\``) - .setAuthor( - streamer.nickname ?? user.username, - user.avatarURL({ - dynamic: true, - format: "png" - }) ?? user.defaultAvatarURL - ) - .setColor(streamer.displayColor); - - if (description) { - embed.setDescription(description); - } - - return embed; -} - -client.on("voiceStateUpdate", async (before, after) => { - const isStartStreamEvent = !before.streaming && after.streaming; - const isStopStreamEvent = before.streaming && (!after.streaming || !after.channel); // If you were streaming before but now are either not streaming or have left the channel. - // Note: isStopStreamEvent can be called twice in a row - If Discord crashes/quits while you're streaming, it'll call once with a null channel and a second time with a channel. - - if (isStartStreamEvent || isStopStreamEvent) { - const {streamingChannel} = Storage.getGuild(after.guild.id); - - if (streamingChannel) { - const member = after.member!; - const voiceChannel = after.channel!; - const textChannel = client.channels.cache.get(streamingChannel); - - if (textChannel instanceof TextChannel) { - if (isStartStreamEvent) { - streamList.set(member.id, { - streamer: member, - channel: voiceChannel, - message: await textChannel.send(getStreamEmbed(member, voiceChannel)), - update(this: Stream) { - this.message.edit(getStreamEmbed(this.streamer, this.channel, this.description)); - } - }); - } else if (isStopStreamEvent) { - if (streamList.has(member.id)) { - const {message} = streamList.get(member.id)!; - message.delete(); - streamList.delete(member.id); - } - } - } else { - console.error( - `The streaming notifications channel ${streamingChannel} for guild ${after.guild.id} either doesn't exist or isn't a text channel.` - ); - } - } - } -}); - -client.on("channelUpdate", (before, after) => { - if (before.type === "voice" && after.type === "voice") { - for (const stream of streamList.values()) { - if (after.id === stream.channel.id) { - stream.update(); - } - } - } -}); diff --git a/src/structures.ts b/src/structures.ts index f764d5a..aac1958 100644 --- a/src/structures.ts +++ b/src/structures.ts @@ -31,7 +31,6 @@ class User { public timezone: number | null; // This is for the standard timezone only, not the daylight savings timezone public daylightSavingsRegion: "na" | "eu" | "sh" | null; public todoList: {[timestamp: string]: string}; - public ecoBetInsurance: number; constructor(data?: GenericJSON) { this.money = select(data?.money, 0, Number); @@ -42,7 +41,6 @@ class User { ? data?.daylightSavingsRegion : null; this.todoList = {}; - this.ecoBetInsurance = select(data?.ecoBetInsurance, 0, Number); if (data) { for (const timestamp in data.todoList) { @@ -60,13 +58,11 @@ class Guild { public welcomeType: "none" | "text" | "graphical"; public welcomeChannel: string | null; public welcomeMessage: string | null; - public streamingChannel: string | null; constructor(data?: GenericJSON) { this.prefix = select(data?.prefix, null, String); this.welcomeChannel = select(data?.welcomeChannel, null, String); this.welcomeMessage = select(data?.welcomeMessage, null, String); - this.streamingChannel = select(data?.streamingChannel, null, String); switch (data?.welcomeType) { case "text":