From 984c354b8fbc7f35bf995215cccf4d4b1988b184 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 19 Sep 2021 08:40:09 -0700 Subject: [PATCH] Added osu! module --- src/discord/osu.ts | 414 +++++++++++++++++++++++++++++++++++++++ src/providers/App.ts | 4 +- src/providers/Discord.ts | 4 + 3 files changed, 420 insertions(+), 2 deletions(-) create mode 100644 src/discord/osu.ts diff --git a/src/discord/osu.ts b/src/discord/osu.ts new file mode 100644 index 0000000..c6a9a06 --- /dev/null +++ b/src/discord/osu.ts @@ -0,0 +1,414 @@ +import { GuildChannel, GuildMember, Message, Permissions, TextChannel, MessageActionRow, MessageButton, Interaction, CommandInteraction, Role, ThreadChannel } from "discord.js"; +import App from ".."; +import { getEmotes, makeSuccessEmbed, makeProcessingEmbed, makeInfoEmbed, makeErrorEmbed, sendMessage, sendReply, sendMessageOrInteractionResponse } from "../utils/DiscordMessage"; +import Logger from "../libs/Logger"; +import DiscordProvider from "../providers/Discord"; +import Prisma from "../providers/Prisma"; +import osuAPI from "../providers/osuAPI"; +import { countryCodeEmoji, emojiCountryCode } from "country-code-emoji"; +import countryLookup from "country-code-lookup"; +import { Beatmap } from "node-osu"; + +const EMBEDS = { + osu_INFO:(data: Message | Interaction) => { + return makeInfoEmbed ({ + title: 'osu!', + description: `[osu!](https://osu.ppy.sh/home) is a free-to-play rhythm game primarily developed, published, and created by Dean "peppy" Herbert`, + fields: [ + { + name: 'What can I do?', + value: 'You can view users or beatmaps stats and information' + }, + { + name: 'Available arguments', + value: '``user``' + } + ], + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_PERMISSION: (data: Message | Interaction) => { + return makeErrorEmbed ({ + title: 'You need ``ADMINISTRATOR`` permission on this guild!', + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_USER_FOUND: (data: Message | Interaction) => { + return makeErrorEmbed ({ + title: `That user doesn't exists on osu!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_USER_MENTIONED: (data: Message | Interaction) => { + return makeErrorEmbed ({ + title: `No osu! username or user id provided`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_BEATMAP_FOUND: (data: Message | Interaction) => { + return makeErrorEmbed ({ + title: `That beatmap doesn't exists on osu!`, + user: (data instanceof Interaction) ? data.user : data.author + }); + }, + NO_BEATMAP_MENTIONED: (data: Message | Interaction) => { + return makeErrorEmbed ({ + title: `No osu! beatmap id provided`, + user: (data instanceof Interaction) ? data.user : data.author + }); + } +} + +export default class osu { + + async onCommand(command: string, args: any, message: Message) { + + if(command.toLowerCase() !== 'osu') return; + await this.process(message, args); + } + + async interactionCreate(interaction: CommandInteraction) { + if(interaction.isCommand()) { + if(typeof interaction.commandName === 'undefined') return; + if((interaction.commandName).toLowerCase() !== 'osu') return; + await this.process(interaction, interaction.options); + } + } + + async process(data: Interaction | Message, args: any) { + + const isSlashCommand = data instanceof CommandInteraction && data.isCommand(); + const isMessage = data instanceof Message; + + if(!isSlashCommand && !isMessage) return; + + if(data.guild === null || data.guildId === null) return; + + const Guild = await Prisma.client.guild.findFirst({ where: { id: data.guildId }}) + if(!Guild) throw new Error('TODO: Handle if guild is not found'); + + + const funct = { + user: async(data: Message | CommandInteraction) => { + + let user; + if(data instanceof Message) { + if(typeof args[1] === 'undefined') + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_USER_MENTIONED(data)] }); + const [removed, ...newArgs] = args; + user = newArgs.join(" "); + } + else if(data instanceof CommandInteraction) + user = data.options.getString('user'); + + let result = await osuAPI.client.getUser({ u: user }); + + if(result instanceof Array && result.length === 0) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_USER_FOUND(data)] }); + + if(isSlashCommand) { + await (data as CommandInteraction).deferReply(); + } + + const level = { + number: (Math.round((result.level + Number.EPSILON) * 100) / 100).toFixed(2).split('.')[0], + progression: (Math.round((result.level + Number.EPSILON) * 100) / 100).toFixed(2).split('.')[1] + } + const embed = makeInfoEmbed ({ + icon: '', + title: `${countryCodeEmoji(result.country)} ${result.name}`, + //description: `${member.user.username}#${member.user.discriminator} (${member.user.id})`, + fields: [ + { + name: `🏆 Level **${level.number}** (${level.progression}% progress to the next level)`, + value: `Play Count **${this.numberWithCommas(result.counts.plays)}**, totaling in **${this.numberWithCommas(parseFloat(((Math.round(((result.secondsPlayed / (60 * 60)) + Number.EPSILON) * 100) / 100).toFixed(2))))} ${result.secondsPlayed < 60 ? 'hour' : 'hours'}** of songs played + \u200b`, + }, + { + name: "🌎 World Ranking", + value: `**#${this.numberWithCommas(result.pp.rank)}**`, + inline: true + }, + { + name: `${countryCodeEmoji(result.country)} Country Ranking`, + value: `**#${this.numberWithCommas(result.pp.countryRank)}** ${countryLookup.byInternet(result.country)!.country}`, + inline: true + }, + { + name: "🎀 Total Score", + value: `**${this.numberWithCommas(result.scores.total)}**`, + inline: true + }, + { + name: "✨ PP", + value: `**${this.numberWithCommas(result.pp.raw)}pp**`, + inline: true + }, + { + name: "⭕ Hit Accuracy", + value: `**${result.accuracyFormatted}**`, + inline: true + }, + { + name: "🌠 Ranked Score", + value: `**${this.numberWithCommas(result.scores.ranked)}** + \u200b`, + inline: true + }, + { + name: "🥇 SSH", + value: `**${this.numberWithCommas(result.counts.SSH)}**`, + inline: true + }, + { + name: "🥇 SH", + value: `**${this.numberWithCommas(result.counts.SH)}**`, + inline: true + }, + { + name: "🥇 SS", + value: `**${this.numberWithCommas(result.counts.SS)}**`, + inline: true + }, + { + name: "🥈 S", + value: `**${this.numberWithCommas(result.counts.S)}**`, + inline: true + }, + { + name: "🥉 A", + value: `**${this.numberWithCommas(result.counts.A)}**`, + inline: true + }, + { + name: "🏅 > A / Total plays", + value: `**${this.numberWithCommas(result.counts.SSH + result.counts.SH + result.counts.SS + result.counts.S + result.counts.A)}** (${(Math.round((((result.counts.SSH + result.counts.SH + result.counts.SS + result.counts.S + result.counts.A) / (result.counts.plays == 0 ? 1 : result.counts.plays)) + Number.EPSILON) * 10000) / 10000).toFixed(4)}) + \u200b`, + inline: true + }, + { + name: `❤ Account Information`, + value: `Joined: , ` + }//, + /*{ + name: `💌 Recent Events (Coming soon)`, + value: `*How about we explore the area ahead of us later?*` + }*/ + ], + user: (data instanceof Interaction) ? data.user : data.author + }); + + embed.setAuthor(`${result.name}'s osu profile`, 'https://upload.wikimedia.org/wikipedia/commons/4/44/Osu%21Logo_%282019%29.png', `https://osu.ppy.sh/users/${result.id}`); + // TODO: Fix for ppl with no image + embed.setThumbnail(`https://a.ppy.sh/${result.id}` || 'https://osu.ppy.sh/images/layout/avatar-guest.png'); + + const row = new MessageActionRow() + .addComponents( + new MessageButton() + .setEmoji('🔗') + .setLabel(' Open Profile') + .setURL(`https://osu.ppy.sh/users/${result.id}`) + .setStyle('LINK'), + ) + + return await sendMessageOrInteractionResponse(data, { embeds: [embed], components: [row] }); + }, + beatmap: async(data: Message | CommandInteraction) => { + + let beatmap; + if(data instanceof Message) { + if(typeof args[1] === 'undefined') + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_BEATMAP_MENTIONED(data)] }); + const [removed, ...newArgs] = args; + beatmap = newArgs.join(" "); + } + else if(data instanceof CommandInteraction) + beatmap = data.options.getString('beatmap'); + + let result = await osuAPI.client.getBeatmaps({ b: beatmap }); + + if(result instanceof Array && result.length === 0) + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.NO_BEATMAP_FOUND(data)] }); + + if(isSlashCommand) { + await (data as CommandInteraction).deferReply(); + } + + const bm_result = result[0]; + + let url_mode = "#osu"; + let statusEmoji = "⚪"; + let mode: unknown = bm_result.mode; + let status: unknown = bm_result.approvalStatus; + + if((mode as String) === "Taiko") + url_mode = "#taiko"; + else if((mode as String) === "Catch the Beat") + url_mode = "#fruits"; + else if((mode as String) === "Mania") + url_mode = "#mania"; + + if((status as String) === "Ranked") + statusEmoji = "🏆"; + else if((status as String) === "Loved") + statusEmoji = "❤"; + else if((status as String) === "Qualified") + statusEmoji = "✅"; + else if((status as String) === "WIP") + statusEmoji = "🛠"; + else if((status as String) === "Pending") + statusEmoji = "⌛"; + else if((status as String) === "Graveyard") + statusEmoji = "💀"; + + const embed2 = makeInfoEmbed ({ + icon: '', + title: `${bm_result.title} - ${bm_result.artist}`, + fields: [ + { + name: `Difficulty **[${bm_result.version}]**`, + value: `Mapper [${bm_result.creator}](${encodeURI('https://osu.ppy.sh/u/'+ bm_result.creator)}) + Rating **${bm_result.rating.toFixed(2)}/10** + \u200b`, + }, + { + name: "⭐ Star Difficulty", + value: `**${(Math.round((bm_result.difficulty.rating + Number.EPSILON) * 100) / 100).toFixed(2)}**`, + inline: true + }, + { + name: `⌛ Length`, + value: `**${(Math.round(((bm_result.length.total / 60) + Number.EPSILON) * 100) / 100).toFixed(2).replace('.', ':')}**`, + inline: true + }, + { + name: "🏓 Combo", + value: `**${this.numberWithCommas(bm_result.maxCombo)}**`, + inline: true + }, + { + name: "🕹 Mode", + value: `**${mode}**`, + inline: true + }, + { + name: "🎵 BPM", + value: `**${bm_result.bpm}**`, + inline: true + }, + { + name: `${statusEmoji} Status`, + value: `**${bm_result.approvalStatus}** + \u200b`, + inline: true + }, + { + name: "⭕ Circle", + value: `**${bm_result.objects.normal}**`, + inline: true + }, + { + name: "💨 Slider", + value: `**${bm_result.objects.slider}**`, + inline: true + }, + { + name: "💫 Spinner", + value: `**${bm_result.objects.spinner}** + \u200b`, + inline: true + }, + { + name: `🎶 Track Information`, + value: `Language: **${bm_result.language}** + Genre: **${bm_result.genre}** + Submission Date: , + Last updated: , + Approved: , + \u200b`, + }, + { + name: "▶ Plays", + value: `**${this.numberWithCommas(bm_result.counts.plays)}**`, + inline: true + }, + { + name: "🏁 Passes", + value: `**${this.numberWithCommas(bm_result.counts.passes)}**`, + inline: true + }, + { + name: "♥ Favorites", + value: `**${this.numberWithCommas(bm_result.counts.favorites)}** + \u200b`, + inline: true + }, + { + name: `📌 Tags`, + value: `\`\`${bm_result.tags.join(" ")}\`\``, + } + ], + user: (data instanceof Interaction) ? data.user : data.author + }); + + embed2.setAuthor(`osu! beatmap`, 'https://upload.wikimedia.org/wikipedia/commons/4/44/Osu%21Logo_%282019%29.png', `https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}${url_mode}/${bm_result.id}`); + embed2.setImage(`https://assets.ppy.sh/beatmaps/${bm_result.beatmapSetId}/covers/cover.jpg`); + // + const row = new MessageActionRow(); + if(bm_result.hasDownload) + row.addComponents( + new MessageButton() + .setEmoji('🌎') + .setLabel(' Download (Beatconnect)') + .setURL(`https://beatconnect.io/b/${bm_result.beatmapSetId}/`) + .setStyle('LINK'), + ) + row.addComponents( + new MessageButton() + .setEmoji('🔗') + .setLabel(' Open listing') + .setURL(`https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}${url_mode}/${bm_result.id}`) + .setStyle('LINK'), + ) + row.addComponents( + new MessageButton() + .setEmoji('💬') + .setLabel(' Open discussion') + .setURL(`https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}/discussion`) + .setStyle('LINK'), + ) + + return await sendMessageOrInteractionResponse(data, { embeds: [embed2], components: [row] }); + } + } + + let query; + + if(isMessage) { + if(args.length === 0) { + return await sendMessageOrInteractionResponse(data, { embeds: [EMBEDS.osu_INFO(data)] }); + } + query = args[0].toLowerCase(); + } + else if(isSlashCommand) { + query = args.getSubcommand(); + } + + switch(query) { + case "user": + case "u": + return await funct.user(data); + case "beatmap": + case "b": + return await funct.beatmap(data); + } + + } + private numberWithCommas(x: Number) { + try { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); + } catch(err) { + return x; + } + } +} \ No newline at end of file diff --git a/src/providers/App.ts b/src/providers/App.ts index f8a0811..7097fa7 100644 --- a/src/providers/App.ts +++ b/src/providers/App.ts @@ -3,11 +3,11 @@ import Logger from '../libs/Logger'; import Environment from './Environment'; import Prisma from './Prisma'; import Discord from './Discord'; -import osu from './osu'; +import osu from './osuAPI'; class App { - public readonly version = '0.01'; + public readonly version = '0.02'; public loadENV(): void { Logger.log('info', 'Loading environment'); diff --git a/src/providers/Discord.ts b/src/providers/Discord.ts index b74c505..b30dd90 100644 --- a/src/providers/Discord.ts +++ b/src/providers/Discord.ts @@ -11,6 +11,8 @@ import Discord_Invite from "../discord/Invite"; import Discord_Say from "../discord/Say"; import Discord_InteractionManager from "../discord/InteractionManager"; import Discord_MembershipScreening from "../discord/MembershipScreening"; +import Discord_osu from "../discord/osu"; + import Cache from "./Cache"; class Discord { @@ -39,6 +41,8 @@ class Discord { this.loaded_module["Discord_Invite"] = new Discord_Invite(); this.loaded_module["Discord_InteractionManager"] = new Discord_InteractionManager(); this.loaded_module["Discord_Say"] = new Discord_Say(); + this.loaded_module["Discord_osu"] = new Discord_osu(); + this.loaded_module["MembershipScreening"] = new Discord_MembershipScreening(); for(const module in this.loaded_module) {