diff --git a/src/commands/fun/8ball.ts b/src/commands/fun/8ball.ts index fc4358e..f636b88 100644 --- a/src/commands/fun/8ball.ts +++ b/src/commands/fun/8ball.ts @@ -1,5 +1,7 @@ import {NamedCommand, RestCommand} from "onion-lasers"; import {random} from "../../lib"; +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; const responses = [ "Most likely,", @@ -23,7 +25,14 @@ const responses = [ "Outlook not so good,", "Very doubtful," ]; - +export const header = new SlashCommandBuilder() + .setDescription("Answers your question in an 8-ball manner.") + .addStringOption((option) => + option.setName("question").setDescription("Question to ask the 8-ball.").setRequired(true) + ); +export async function handler(interaction: CommandInteraction) { + interaction.reply(`${random(responses)} ${interaction.user.tag}`); +} export default new NamedCommand({ description: "Answers your question in an 8-ball manner.", usage: "", diff --git a/src/commands/fun/cookie.ts b/src/commands/fun/cookie.ts index 4e5c3af..d5c6887 100644 --- a/src/commands/fun/cookie.ts +++ b/src/commands/fun/cookie.ts @@ -1,7 +1,8 @@ import {User} from "discord.js"; import {Command, NamedCommand} from "onion-lasers"; import {random, parseVars} from "../../lib"; - +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; const cookies = [ `has given %target% a chocolate chip cookie!`, `has given %target% a soft homemade oatmeal cookie!`, @@ -25,6 +26,20 @@ const cookies = [ `bakes %target% fresh cookies, it smells amazing.` ]; +export const header = new SlashCommandBuilder() + .setDescription("Gives specified user a cookie.") + .addUserOption((option) => + option.setName("user").setDescription("User you want to give a cookie to.").setRequired(true) + ); +export async function handler(interaction: CommandInteraction) { + const {options} = interaction; + + return interaction.reply( + `:cookie: ${interaction.user.tag} ${parseVars(random(cookies), { + target: options.getUser("user", true).tag.toString() + })}` + ); +} export default new NamedCommand({ description: "Gives specified user a cookie.", usage: "['all'/@user]", diff --git a/src/commands/fun/figlet.ts b/src/commands/fun/figlet.ts index ae97069..957cd73 100644 --- a/src/commands/fun/figlet.ts +++ b/src/commands/fun/figlet.ts @@ -1,7 +1,24 @@ import {NamedCommand, RestCommand} from "onion-lasers"; import figlet from "figlet"; import {Util} from "discord.js"; +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; +export const header = new SlashCommandBuilder() + .setDescription("Generates a figlet of your input.") + .addStringOption((option) => + option.setName("text").setDescription("Text used to create the figlet.").setRequired(true) + ); +export async function handler(interaction: CommandInteraction) { + const {options} = interaction; + return interaction.reply( + `\`\`\`\n${Util.cleanCodeBlockContent( + figlet.textSync(options.getString("text", true), { + horizontalLayout: "full" + }) + )}\n\`\`\`` + ); +} export default new NamedCommand({ description: "Generates a figlet of your input.", run: "You have to provide input for me to create a figlet!", diff --git a/src/commands/fun/insult.ts b/src/commands/fun/insult.ts index 05603d9..fa17437 100644 --- a/src/commands/fun/insult.ts +++ b/src/commands/fun/insult.ts @@ -1,5 +1,14 @@ import {NamedCommand} from "onion-lasers"; +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; + +export const header = new SlashCommandBuilder().setDescription("Insult TravBot >:D"); +export async function handler(interaction: CommandInteraction) { + interaction.reply( + `<@${interaction.user.id}> What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills. I am trained in gorilla warfare and I'm the top sniper in the entire US armed forces. You are nothing to me but just another target. I will wipe you the fuck out with precision the likes of which has never been seen before on this Earth, mark my fucking words. You think you can get away with saying that shit to me over the Internet? Think again, fucker. As we speak I am contacting my secret network of spies across the USA and your IP is being traced right now so you better prepare for the storm, maggot. The storm that wipes out the pathetic little thing you call your life. You're fucking dead, kid. I can be anywhere, anytime, and I can kill you in over seven hundred ways, and that's just with my bare hands. Not only am I extensively trained in unarmed combat, but I have access to the entire arsenal of the United States Marine Corps and I will use it to its full extent to wipe your miserable ass off the face of the continent, you little shit. If only you could have known what unholy retribution your little "clever" comment was about to bring down upon you, maybe you would have held your fucking tongue. But you couldn't, you didn't, and now you're paying the price, you goddamn idiot. I will shit fury all over you and you will drown in it. You're fucking dead, kiddo.` + ); +} export default new NamedCommand({ description: "Insult TravBot! >:D", async run({send, channel, author}) { diff --git a/src/commands/fun/love.ts b/src/commands/fun/love.ts index 4c0c037..6e2236b 100644 --- a/src/commands/fun/love.ts +++ b/src/commands/fun/love.ts @@ -1,5 +1,13 @@ +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; import {NamedCommand, CHANNEL_TYPE} from "onion-lasers"; +export const header = new SlashCommandBuilder().setDescription("Chooses someone to love."); +export async function handler(interaction: CommandInteraction) { + const member = interaction.guild!.members.cache.random(); + if (!member) return interaction.reply("For some reason, an error occurred fetching a member."); + return interaction.reply(`I love ${member.nickname ?? member.user.username}!`); +} export default new NamedCommand({ description: "Chooses someone to love.", channelType: CHANNEL_TYPE.GUILD, diff --git a/src/commands/fun/modules/eco-bet.ts b/src/commands/fun/modules/eco-bet.ts index 669a931..36d3ffe 100644 --- a/src/commands/fun/modules/eco-bet.ts +++ b/src/commands/fun/modules/eco-bet.ts @@ -1,5 +1,5 @@ import {Command, NamedCommand, confirm, poll} from "onion-lasers"; -import {pluralise} from "../../../lib"; +import {pluralise, parseDuration} from "../../../lib"; import {Storage} from "../../../structures"; import {isAuthorized, getMoneyEmbed} from "./eco-utils"; import {User} from "discord.js"; @@ -167,30 +167,3 @@ export const BetCommand = new NamedCommand({ }) }) }); - -/** - * 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/ok.ts b/src/commands/fun/ok.ts index 73c368b..11e7a81 100644 --- a/src/commands/fun/ok.ts +++ b/src/commands/fun/ok.ts @@ -1,3 +1,5 @@ +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; import {NamedCommand} from "onion-lasers"; import {random} from "../../lib"; @@ -58,7 +60,10 @@ const responses = [ "cat", "large man" ]; - +export const header = new SlashCommandBuilder().setDescription("Sends random ok message."); +export async function handler(interaction: CommandInteraction) { + interaction.reply(`ok ${random(responses)}`); +} export default new NamedCommand({ description: "Sends random ok message.", async run({send}) { diff --git a/src/commands/fun/owoify.ts b/src/commands/fun/owoify.ts index 1d4882c..915f1ed 100644 --- a/src/commands/fun/owoify.ts +++ b/src/commands/fun/owoify.ts @@ -1,6 +1,25 @@ import {NamedCommand, RestCommand} from "onion-lasers"; import {random} from "../../lib"; +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; +export const header = new SlashCommandBuilder() + .setDescription("OwO-ifies the input.") + .addStringOption((option) => option.setName("text").setDescription("Text to owoify").setRequired(true)); + +export async function handler(interaction: CommandInteraction) { + const {options} = interaction; + const faces = ["owo", "UwU", ">w<", "^w^"]; + const owoified = options + .getString("text", true) + .replace(/[rl]/g, "w") + .replace(/[RL]/g, "W") + .replace(/ove/g, "uv") + .replace(/n/g, "ny") + .replace(/N/g, "NY") + .replace(/\!/g, ` ${random(faces)} `); + interaction.reply(owoified); +} export default new NamedCommand({ description: "OwO-ifies the input.", run: "You need to specify some text to owoify.", diff --git a/src/commands/fun/pat.ts b/src/commands/fun/pat.ts index 18cff34..9f38c12 100644 --- a/src/commands/fun/pat.ts +++ b/src/commands/fun/pat.ts @@ -1,6 +1,23 @@ import {MessageAttachment, User} from "discord.js"; import {NamedCommand, Command, RestCommand, getUserByNickname} from "onion-lasers"; import petPetGif from "pet-pet-gif"; +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; + +//Ravioli ravioli... +//number from 1 to 9 +export const header = new SlashCommandBuilder() + .setDescription("Generates a pat GIF of the avatar of the mentioned user.") + .addUserOption((option) => option.setName("user").setDescription("User you want a pat gif of.").setRequired(true)); + +export async function handler(interaction: CommandInteraction) { + await interaction.reply("Generating pat gif..."); + const {options} = interaction; + const pfp = options.getUser("user", true).displayAvatarURL({format: "png"}); + const gif = await petPetGif(pfp); + const file = new MessageAttachment(gif, "pat.gif"); + await interaction.editReply({content: "Here you go!", files: [file]}); +} export default new NamedCommand({ description: "Generates a pat GIF of the provided attachment image OR the avatar of the mentioned user.", diff --git a/src/commands/fun/poll.ts b/src/commands/fun/poll.ts index 40f9287..ccaf09e 100644 --- a/src/commands/fun/poll.ts +++ b/src/commands/fun/poll.ts @@ -1,6 +1,23 @@ -import {MessageEmbed, Message, User} from "discord.js"; +import {MessageEmbed, Message, User, MessageActionRow, MessageButton} from "discord.js"; import {NamedCommand, RestCommand, poll, CHANNEL_TYPE, SendFunction, Command} from "onion-lasers"; -import {pluralise} from "../../lib"; +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; +import {pluralise, parseDuration} from "../../lib"; + +export const header = new SlashCommandBuilder() + .setDescription("Create a poll.") + .addStringOption((option) => option.setName("question").setDescription("Question for the poll").setRequired(true)) + .addIntegerOption((option) => + option.setName("duration").setDescription("Duration of the poll in seconds.").setRequired(false) + ); + +export async function handler(interaction: CommandInteraction) { + const {options} = interaction; + const question = options.getString("question", true); + var duration = options.getInteger("duration", false); + duration = parseDuration(duration + "s"); //override the duration variable with miliseconds one + execSlashPoll(interaction, question, duration || 60000); +} export default new NamedCommand({ description: "Create a poll.", @@ -27,6 +44,63 @@ export default new NamedCommand({ const AGREE = "✅"; const DISAGREE = "⛔"; +async function execSlashPoll(interaction: CommandInteraction, question: string, duration = 60000) { + const responseButtons = new MessageActionRow().addComponents( + new MessageButton().setCustomId("agree").setLabel(AGREE).setStyle("SUCCESS"), + new MessageButton().setCustomId("disagree").setLabel(DISAGREE).setStyle("DANGER") + ); + + const icon = + interaction.user.avatarURL({ + dynamic: true, + size: 2048 + }) || interaction.user.defaultAvatarURL; + const embed = new MessageEmbed() + .setAuthor(`Poll created by ${interaction.user.username}`, icon) + .setColor(0xffffff) + .setFooter("Click the buttons to vote.") + .setDescription(question) + .addField(`${AGREE} -`, `${pluralise(0, "", "people have voted")}\n`) + .addField(`${DISAGREE} -`, `${pluralise(0, "", "people have voted")}\n`); + const msg = await interaction.reply({ + embeds: [embed], + components: [responseButtons] + }); + var idsArray: string[] = []; + const collector = interaction.channel?.createMessageComponentCollector({time: duration}); + collector?.on("collect", async (i) => { + if (i.customId === "agree") { + if (idsArray.includes(i.user.id)) { + i.reply({content: "You have already voted!", ephemeral: true}); + } else { + idsArray.push(i.user.id); + var agree = +1; + embed.fields[0].value = `${pluralise(agree, "", "people who agree", "person who agrees")}\n`; + interaction.editReply({embeds: [embed]}); + i.reply({content: "You picked ✅!", ephemeral: true}); + } + } + if (i.customId === "disagree") { + if (idsArray.includes(i.user.id)) { + i.reply({content: "You have already voted!", ephemeral: true}); + } else { + idsArray.push(i.user.id); + var disagree = +1; + embed.fields[1].value = `${pluralise(disagree, "", "people who disagree", "person who disagrees")}\n`; + interaction.editReply({embeds: [embed]}); + i.reply({content: "You picked ⛔!", ephemeral: true}); + } + } + }); + //This solution looks messy but works really well and stops from stuff like vote fraud happening. + //I'm not sure if it's the best solution but if you have a better idea then please let me know. + + collector?.on("end", async (collected) => { + embed.setTitle(`The results of ${interaction.user.username}'s poll:`); + interaction.editReply({embeds: [embed]}); + }); +} + async function execPoll(send: SendFunction, message: Message, user: User, question: string, duration = 60000) { const icon = user.avatarURL({ diff --git a/src/commands/fun/ravi.ts b/src/commands/fun/ravi.ts index d5ef2ef..8334465 100644 --- a/src/commands/fun/ravi.ts +++ b/src/commands/fun/ravi.ts @@ -1,6 +1,34 @@ +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; import {Command, NamedCommand} from "onion-lasers"; import {Random} from "../../lib"; +//Ravioli ravioli... +//number from 1 to 9 +export const header = new SlashCommandBuilder() + .setDescription("Ravioli ravioli...") + .addIntegerOption((option) => option.setName("number").setDescription("Number from 1 to 9").setRequired(false)); +export async function handler(interaction: CommandInteraction) { + const {options} = interaction; + try { + //get the number from the options if it fails fallback to the randomizer + var response = options.getInteger("number", true); + } catch (e) { + var response = Random.int(1, 10); + } + console.log(response); + + interaction.reply({ + embeds: [ + { + title: "Ravioli ravioli...", + image: { + url: `https://raw.githubusercontent.com/keanuplayz/TravBot/master/assets/ravi${response}.png` + } + } + ] + }); +} export default new NamedCommand({ description: "Ravioli ravioli...", usage: "[number from 1 to 9]", diff --git a/src/commands/fun/thonk.ts b/src/commands/fun/thonk.ts index 2dc420c..ee698de 100644 --- a/src/commands/fun/thonk.ts +++ b/src/commands/fun/thonk.ts @@ -1,5 +1,6 @@ +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; import {NamedCommand, RestCommand} from "onion-lasers"; - const letters: {[letter: string]: string[]} = { a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""), e: "eéèẻẽẹêếềểễệ".split(""), @@ -29,8 +30,22 @@ function transform(str: string) { return out; } -let phrase = "I have no currently set phrase!"; +export const header = new SlashCommandBuilder() + .setDescription("Transforms your text into vietnamese.") + .addStringOption((option) => + option.setName("text").setDescription("The text you want to transform").setRequired(true) + ); +export async function handler(interaction: CommandInteraction) { + const {options} = interaction; + const response = options.getString("text", true); + + interaction.reply(transform(response)); + // You might notice the remove message code is missing here. It's because reactions collectors are + //not a thing in interactions. The best alternative would be buttons +} + +let phrase = "I have no currently set phrase!"; export default new NamedCommand({ description: "Transforms your text into vietnamese.", usage: "([text])", diff --git a/src/commands/fun/urban.ts b/src/commands/fun/urban.ts index e5d1efe..57e0d9c 100644 --- a/src/commands/fun/urban.ts +++ b/src/commands/fun/urban.ts @@ -1,7 +1,38 @@ +import {SlashCommandBuilder} from "@discordjs/builders"; import {NamedCommand, RestCommand} from "onion-lasers"; -import {MessageEmbed} from "discord.js"; +import {MessageEmbed, CommandInteraction} from "discord.js"; import urban from "relevant-urban"; +export const header = new SlashCommandBuilder() + .setDescription("Gives you a definition of the inputted word.") + .addStringOption((option) => + option.setName("word").setDescription("The word you're looking for").setRequired(true) + ); + +export async function handler(interaction: CommandInteraction) { + const {options} = interaction; + await interaction.reply("Working on it...."); + const response = options.getString("word", true); + // [Bug Fix]: Use encodeURIComponent() when emojis are used: "TypeError [ERR_UNESCAPED_CHARACTERS]: Request path contains unescaped characters" + urban(encodeURIComponent(response)) + .then(async (res) => { + const embed = new MessageEmbed() + .setColor(0x1d2439) + .setTitle(res.word) + .setURL(res.urbanURL) + .setDescription(`**Definition:**\n*${res.definition}*\n\n**Example:**\n*${res.example}*`) + // [Bug Fix] When an embed field is empty (if the author field is missing, like the top entry for "british"): "RangeError [EMBED_FIELD_VALUE]: MessageEmbed field values may not be empty." + .addField("Author", res.author || "N/A", true) + .addField("Rating", `**\`Upvotes: ${res.thumbsUp} | Downvotes: ${res.thumbsDown}\`**`); + if (res.tags && res.tags.length > 0 && res.tags.join(" ").length < 1024) + embed.addField("Tags", res.tags.join(", "), true); + interaction.editReply("Here you go!"); + await interaction.editReply({embeds: [embed]}); + }) + .catch(async () => { + await interaction.editReply("Sorry, that word was not found."); + }); +} export default new NamedCommand({ description: "Gives you a definition of the inputted word.", run: "Please input a word.", diff --git a/src/commands/fun/vaporwave.ts b/src/commands/fun/vaporwave.ts index 7997ba2..ab78943 100644 --- a/src/commands/fun/vaporwave.ts +++ b/src/commands/fun/vaporwave.ts @@ -1,5 +1,6 @@ +import {SlashCommandBuilder} from "@discordjs/builders"; +import {CommandInteraction} from "discord.js"; import {NamedCommand, RestCommand} from "onion-lasers"; - const vaporwave = (() => { const map = new Map(); const vaporwave = @@ -21,6 +22,17 @@ function getVaporwaveText(text: string): string { return output; } +export const header = new SlashCommandBuilder() + .setDescription("Transforms your text into vaporwave.") + .addStringOption((option) => + option.setName("text").setDescription("The text you want to vaporwave.").setRequired(true) + ); + +export async function handler(interaction: CommandInteraction) { + const {options} = interaction; + const response = options.getString("text", true); + await interaction.reply(getVaporwaveText(response)); +} export default new NamedCommand({ description: "Transforms your text into vaporwave.", run: "You need to enter some text!", diff --git a/src/commands/fun/weather.ts b/src/commands/fun/weather.ts index 9720256..ba82bbe 100644 --- a/src/commands/fun/weather.ts +++ b/src/commands/fun/weather.ts @@ -1,7 +1,47 @@ import {NamedCommand, RestCommand} from "onion-lasers"; -import {MessageEmbed} from "discord.js"; +import {SlashCommandBuilder} from "@discordjs/builders"; +import {MessageEmbed, CommandInteraction} from "discord.js"; import {find} from "weather-js"; +export const header = new SlashCommandBuilder() + .setDescription("Shows weather info of specified location.") + .addStringOption((option) => + option.setName("location").setDescription("The location you're looking for").setRequired(true) + ); + +export async function handler(interaction: CommandInteraction) { + const {options} = interaction; + await interaction.reply("Working on it...."); + const response = options.getString("location", true); + + find( + { + search: response, + degreeType: "C" + }, + async function (error, result) { + if (error) return await interaction.editReply(error.toString()); + if (result.length === 0) return await interaction.editReply("No city found by that name."); + var current = result[0].current; + var location = result[0].location; + const embed = new MessageEmbed() + .setDescription(`**${current.skytext}**`) + .setAuthor(`Weather for ${current.observationpoint}`) + .setThumbnail(current.imageUrl) + .setColor(0x00ae86) + .addField("Timezone", `UTC${location.timezone}`, true) + .addField("Degree Type", "C", true) + .addField("Temperature", `${current.temperature} Degrees`, true) + .addField("Feels like", `${current.feelslike} Degrees`, true) + .addField("Winds", current.winddisplay, true) + .addField("Humidity", `${current.humidity}%`, true); + interaction.editReply("Here you go!"); // remove the working on message + return await interaction.editReply({ + embeds: [embed] + }); + } + ); +} export default new NamedCommand({ description: "Shows weather info of specified location.", run: "You need to provide a city.", diff --git a/src/lib.ts b/src/lib.ts index b999791..6afe664 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -269,3 +269,29 @@ export function split(array: T[], lengthOfEachSection: number): T[][] { export function requireAllCasesHandledFor(variable: never): never { throw new Error(`This function should never be called but got the value: ${variable}`); } +/** + * Parses a duration string into milliseconds + * Examples: + * - 3d -> 3 days -> 259200000ms + * - 2h -> 2 hours -> 7200000ms + * - 7m -> 7 minutes -> 420000ms + * - 3s -> 3 seconds -> 3000ms + */ +export 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; +}