From 503278de445c3aa26961a7f57a5a1b89f5a3bd54 Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 23 Apr 2023 13:35:13 +0700 Subject: [PATCH 1/2] osu! code clean up --- src/discord/osu.ts | 497 ------------------------------------ src/discord/osu/Beatmaps.ts | 206 +++++++++++++++ src/discord/osu/Users.ts | 213 ++++++++++++++++ src/discord/osu/index.ts | 81 ++++++ src/utils/StringUtils.ts | 9 + 5 files changed, 509 insertions(+), 497 deletions(-) delete mode 100644 src/discord/osu.ts create mode 100644 src/discord/osu/Beatmaps.ts create mode 100644 src/discord/osu/Users.ts create mode 100644 src/discord/osu/index.ts create mode 100644 src/utils/StringUtils.ts diff --git a/src/discord/osu.ts b/src/discord/osu.ts deleted file mode 100644 index 40a8551..0000000 --- a/src/discord/osu.ts +++ /dev/null @@ -1,497 +0,0 @@ -import { Message, ActionRowBuilder, ButtonBuilder, CommandInteraction, BaseInteraction, ButtonStyle } from 'discord.js'; -import validator from 'validator'; -import countryLookup from 'country-code-lookup'; -import { countryCodeEmoji } from 'country-code-emoji'; - -import Prisma from '../providers/Prisma'; -import osuAPI from '../providers/osuAPI'; - -import DiscordModule, { HybridInteractionMessage } from '../utils/DiscordModule'; -import { makeInfoEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../utils/DiscordMessage'; - -const EMBEDS = { - osu_INFO: (data: HybridInteractionMessage) => { - 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.getUser() - }); - }, - NO_USER_FOUND: (data: HybridInteractionMessage) => { - return makeErrorEmbed({ - title: `That user doesn't exists on osu!`, - user: data.getUser() - }); - }, - NO_USER_MENTIONED: (data: HybridInteractionMessage) => { - return makeErrorEmbed({ - title: `No osu! username or user id provided`, - user: data.getUser() - }); - }, - INVALID_USER_MENTIONED: (data: HybridInteractionMessage) => { - return makeErrorEmbed({ - title: `Not a valid osu username or id`, - user: data.getUser() - }); - }, - INVALID_BEATMAP_ID_MENTIONED: (data: HybridInteractionMessage) => { - return makeErrorEmbed({ - title: `Not a valid osu beatmap id`, - user: data.getUser() - }); - }, - NO_BEATMAP_FOUND: (data: HybridInteractionMessage) => { - return makeErrorEmbed({ - title: `That beatmap doesn't exists on osu!`, - user: data.getUser() - }); - }, - NO_BEATMAP_MENTIONED: (data: HybridInteractionMessage) => { - return makeErrorEmbed({ - title: `No osu! beatmap id provided`, - user: data.getUser() - }); - }, - NOT_INITIALIZED: (data: HybridInteractionMessage) => { - return makeErrorEmbed({ - title: `osu! feature is disabled`, - user: data.getUser() - }); - } -}; - -export default class osu extends DiscordModule { - public id = 'Discord_osu'; - public commands = ['osu']; - public commandInteractionName = 'osu'; - - async GuildOnModuleCommand(args: any, message: Message) { - await this.run(new HybridInteractionMessage(message), args); - } - - async GuildModuleCommandInteractionCreate(interaction: CommandInteraction) { - await this.run(new HybridInteractionMessage(interaction), interaction.options); - } - - async run(data: HybridInteractionMessage, args: any) { - const Guild = await Prisma.client.guild.findFirst({ where: { id: data.getGuild()!.id } }); - if (!Guild) return; - - if (!osuAPI.client) - return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.NOT_INITIALIZED(data)] - }); - - const funct = { - user: async (data: HybridInteractionMessage) => { - let user; - if (data.isMessage()) { - if (typeof args[1] === 'undefined') - return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.NO_USER_MENTIONED(data)] - }); - const [removed, ...newArgs] = args; - user = newArgs.join(' '); - } else if (data.isApplicationCommand()) - user = data.getSlashCommand().options.get('user')?.value?.toString(); - - if (!validator.isNumeric(user) && !this.validate_osu_username(user)) - return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.INVALID_USER_MENTIONED(data)] - }); - - let result = await osuAPI.client!.getUser({ u: user }); - - if (result instanceof Array && result.length === 0) - return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.NO_USER_FOUND(data)] - }); - - if (data.isApplicationCommand()) { - await data.getSlashCommand().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.getUser() - }); - embed.setAuthor({ - name: `${result.name}'s osu profile`, - url: `https://osu.ppy.sh/users/${result.id}`, - iconURL: 'https://upload.wikimedia.org/wikipedia/commons/e/e3/Osulogo.png' - }); - - // 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 ActionRowBuilder().addComponents([ - new ButtonBuilder() - .setEmoji('🔗') - .setLabel(' Open Profile') - .setURL(`https://osu.ppy.sh/users/${result.id}`) - .setStyle(ButtonStyle.Link) - ]); - - return await sendHybridInteractionMessageResponse(data, { - embeds: [embed], - components: [row] - }); - }, - beatmap: async (data: HybridInteractionMessage) => { - let beatmap; - if (data.isMessage()) { - if (typeof args[1] === 'undefined') - return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.NO_BEATMAP_MENTIONED(data)] - }); - const [removed, ...newArgs] = args; - beatmap = newArgs.join(' '); - } else if (data.isApplicationCommand()) - beatmap = data.getSlashCommand().options.get('beatmap')?.value?.toString(); - - if (!validator.isNumeric(beatmap)) - return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.INVALID_BEATMAP_ID_MENTIONED(data)] - }); - - let result = await osuAPI.client!.getBeatmaps({ b: beatmap }); - - if (result instanceof Array && result.length === 0) - return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.NO_BEATMAP_FOUND(data)] - }); - - if (data.isApplicationCommand()) await data.getSlashCommand().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.getUser() - }); - - embed2.setAuthor({ - name: `osu! beatmap`, - url: `https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}${url_mode}/${bm_result.id}`, - iconURL: `https://upload.wikimedia.org/wikipedia/commons/e/e3/Osulogo.png` - }); - embed2.setImage(`https://assets.ppy.sh/beatmaps/${bm_result.beatmapSetId}/covers/cover.jpg`); - - const row = new ActionRowBuilder(); - if (bm_result.hasDownload) - row.addComponents([ - new ButtonBuilder() - .setEmoji('🌎') - .setLabel(' Download (Beatconnect)') - .setURL(`https://beatconnect.io/b/${bm_result.beatmapSetId}/`) - .setStyle(ButtonStyle.Link), - new ButtonBuilder() - .setEmoji('🔗') - .setLabel(' Open listing') - .setURL( - `https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}${url_mode}/${bm_result.id}` - ) - .setStyle(ButtonStyle.Link), - new ButtonBuilder() - .setEmoji('💬') - .setLabel(' Open discussion') - .setURL(`https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}/discussion`) - .setStyle(ButtonStyle.Link) - ]); - - return await sendHybridInteractionMessageResponse(data, { - embeds: [embed2], - components: [row] - }); - } - }; - - let query; - - if (data.isMessage()) { - if (args.length === 0) { - return await sendHybridInteractionMessageResponse(data, { - embeds: [EMBEDS.osu_INFO(data)] - }); - } - query = args[0].toLowerCase(); - } else if (data.isApplicationCommand()) { - 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; - } - } - - // Criteria from https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/app/Libraries/UsernameValidation.php - private validate_osu_username(username: string) { - //username_no_spaces - if (username.startsWith(' ') || username.endsWith(' ')) return false; - - //username_too_short - if (username.length < 3) return false; - - //username_too_long - if (username.length > 15) return false; - - //username_invalid_characters - if (username.includes(' ') || !/^[A-Za-z0-9-\[\]_ ]+$/.test(username)) return false; - - //username_no_space_userscore_mix - if (username.includes('_') && username.includes(' ')) return false; - - return true; - } -} diff --git a/src/discord/osu/Beatmaps.ts b/src/discord/osu/Beatmaps.ts new file mode 100644 index 0000000..90ee2b7 --- /dev/null +++ b/src/discord/osu/Beatmaps.ts @@ -0,0 +1,206 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import validator from 'validator'; + +import osuAPI from '../../providers/osuAPI'; + +import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule'; +import { makeInfoEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage'; +import StringUtils from '../../utils/StringUtils'; + +const EMBEDS = { + INVALID_BEATMAP_ID_MENTIONED: (data: HybridInteractionMessage) => { + return makeErrorEmbed({ + title: `Not a valid osu beatmap id`, + user: data.getUser() + }); + }, + NO_BEATMAP_FOUND: (data: HybridInteractionMessage) => { + return makeErrorEmbed({ + title: `That beatmap doesn't exists on osu!`, + user: data.getUser() + }); + }, + NO_BEATMAP_MENTIONED: (data: HybridInteractionMessage) => { + return makeErrorEmbed({ + title: `No osu! beatmap id provided`, + user: data.getUser() + }); + } +}; + +export default class osuBeatmaps { + public static async run(data: HybridInteractionMessage, args: any) { + let beatmap; + if (data.isMessage()) { + if (typeof args[1] === 'undefined') + return await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.NO_BEATMAP_MENTIONED(data)] + }); + const [removed, ...newArgs] = args; + beatmap = newArgs.join(' '); + } else if (data.isApplicationCommand()) + beatmap = data.getSlashCommand().options.get('beatmap')?.value?.toString(); + + if (!validator.isNumeric(beatmap)) + return await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.INVALID_BEATMAP_ID_MENTIONED(data)] + }); + + let result = await osuAPI.client!.getBeatmaps({ b: beatmap }); + + if (result instanceof Array && result.length === 0) + return await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.NO_BEATMAP_FOUND(data)] + }); + + if (data.isApplicationCommand()) await data.getSlashCommand().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: `**${StringUtils.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: `**${StringUtils.numberWithCommas(bm_result.counts.plays)}**`, + inline: true + }, + { + name: '🏁 Passes', + value: `**${StringUtils.numberWithCommas(bm_result.counts.passes)}**`, + inline: true + }, + { + name: '♥ Favorites', + value: `**${StringUtils.numberWithCommas(bm_result.counts.favorites)}** + \u200b`, + inline: true + }, + { + name: `📌 Tags`, + value: `\`\`${bm_result.tags.join(' ')}\`\`` + } + ], + user: data.getUser() + }); + + embed2.setAuthor({ + name: `osu! beatmap`, + url: `https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}${url_mode}/${bm_result.id}`, + iconURL: `https://upload.wikimedia.org/wikipedia/commons/e/e3/Osulogo.png` + }); + embed2.setImage(`https://assets.ppy.sh/beatmaps/${bm_result.beatmapSetId}/covers/cover.jpg`); + + const row = new ActionRowBuilder(); + if (bm_result.hasDownload) + row.addComponents([ + new ButtonBuilder() + .setEmoji('🌎') + .setLabel(' Download (Beatconnect)') + .setURL(`https://beatconnect.io/b/${bm_result.beatmapSetId}/`) + .setStyle(ButtonStyle.Link), + new ButtonBuilder() + .setEmoji('🔗') + .setLabel(' Open listing') + .setURL(`https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}${url_mode}/${bm_result.id}`) + .setStyle(ButtonStyle.Link), + new ButtonBuilder() + .setEmoji('💬') + .setLabel(' Open discussion') + .setURL(`https://osu.ppy.sh/beatmapsets/${bm_result.beatmapSetId}/discussion`) + .setStyle(ButtonStyle.Link) + ]); + + return await sendHybridInteractionMessageResponse(data, { + embeds: [embed2], + components: [row] + }); + } +} diff --git a/src/discord/osu/Users.ts b/src/discord/osu/Users.ts new file mode 100644 index 0000000..5c29edb --- /dev/null +++ b/src/discord/osu/Users.ts @@ -0,0 +1,213 @@ +import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'; +import validator from 'validator'; +import countryLookup from 'country-code-lookup'; +import { countryCodeEmoji } from 'country-code-emoji'; + +import osuAPI from '../../providers/osuAPI'; + +import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule'; +import { makeInfoEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage'; +import StringUtils from '../../utils/StringUtils'; + +const EMBEDS = { + NO_USER_FOUND: (data: HybridInteractionMessage) => { + return makeErrorEmbed({ + title: `That user doesn't exists on osu!`, + user: data.getUser() + }); + }, + NO_USER_MENTIONED: (data: HybridInteractionMessage) => { + return makeErrorEmbed({ + title: `No osu! username or user id provided`, + user: data.getUser() + }); + }, + INVALID_USER_MENTIONED: (data: HybridInteractionMessage) => { + return makeErrorEmbed({ + title: `Not a valid osu username or id`, + user: data.getUser() + }); + } +}; + +export default class osuUsers { + public static async run(data: HybridInteractionMessage, args: any) { + let user; + if (data.isMessage()) { + if (typeof args[1] === 'undefined') + return await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.NO_USER_MENTIONED(data)] + }); + const [removed, ...newArgs] = args; + user = newArgs.join(' '); + } else if (data.isApplicationCommand()) user = data.getSlashCommand().options.get('user')?.value?.toString(); + + if (!validator.isNumeric(user) && !this.validate_osu_username(user)) + return await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.INVALID_USER_MENTIONED(data)] + }); + + let result = await osuAPI.client!.getUser({ u: user }); + + if (result instanceof Array && result.length === 0) + return await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.NO_USER_FOUND(data)] + }); + + if (data.isApplicationCommand()) { + await data.getSlashCommand().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 **${StringUtils.numberWithCommas( + result.counts.plays + )}**, totaling in **${StringUtils.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: `**#${StringUtils.numberWithCommas(result.pp.rank)}**`, + inline: true + }, + { + name: `${countryCodeEmoji(result.country)} Country Ranking`, + value: `**#${StringUtils.numberWithCommas(result.pp.countryRank)}** ${ + countryLookup.byInternet(result.country)!.country + }`, + inline: true + }, + { + name: '🎀 Total Score', + value: `**${StringUtils.numberWithCommas(result.scores.total)}**`, + inline: true + }, + { + name: '✨ PP', + value: `**${StringUtils.numberWithCommas(result.pp.raw)}pp**`, + inline: true + }, + { + name: '⭕ Hit Accuracy', + value: `**${result.accuracyFormatted}**`, + inline: true + }, + { + name: '🌠 Ranked Score', + value: `**${StringUtils.numberWithCommas(result.scores.ranked)}** + \u200b`, + inline: true + }, + { + name: '🥇 SSH', + value: `**${StringUtils.numberWithCommas(result.counts.SSH)}**`, + inline: true + }, + { + name: '🥇 SH', + value: `**${StringUtils.numberWithCommas(result.counts.SH)}**`, + inline: true + }, + { + name: '🥇 SS', + value: `**${StringUtils.numberWithCommas(result.counts.SS)}**`, + inline: true + }, + { + name: '🥈 S', + value: `**${StringUtils.numberWithCommas(result.counts.S)}**`, + inline: true + }, + { + name: '🥉 A', + value: `**${StringUtils.numberWithCommas(result.counts.A)}**`, + inline: true + }, + { + name: '🏅 > A / Total plays', + value: `**${StringUtils.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.getUser() + }); + embed.setAuthor({ + name: `${result.name}'s osu profile`, + url: `https://osu.ppy.sh/users/${result.id}`, + iconURL: 'https://upload.wikimedia.org/wikipedia/commons/e/e3/Osulogo.png' + }); + + // 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 ActionRowBuilder().addComponents([ + new ButtonBuilder() + .setEmoji('🔗') + .setLabel(' Open Profile') + .setURL(`https://osu.ppy.sh/users/${result.id}`) + .setStyle(ButtonStyle.Link) + ]); + + return await sendHybridInteractionMessageResponse(data, { + embeds: [embed], + components: [row] + }); + } + + // Criteria from https://github.com/ppy/osu-web/blob/9de00a0b874c56893d98261d558d78d76259d81b/app/Libraries/UsernameValidation.php + private static validate_osu_username(username: string) { + //username_no_spaces + if (username.startsWith(' ') || username.endsWith(' ')) return false; + + //username_too_short + if (username.length < 3) return false; + + //username_too_long + if (username.length > 15) return false; + + //username_invalid_characters + if (username.includes(' ') || !/^[A-Za-z0-9-\[\]_ ]+$/.test(username)) return false; + + //username_no_space_userscore_mix + if (username.includes('_') && username.includes(' ')) return false; + + return true; + } +} diff --git a/src/discord/osu/index.ts b/src/discord/osu/index.ts new file mode 100644 index 0000000..fb564c8 --- /dev/null +++ b/src/discord/osu/index.ts @@ -0,0 +1,81 @@ +import { Message, CommandInteraction } from 'discord.js'; + +import Prisma from '../../providers/Prisma'; +import osuAPI from '../../providers/osuAPI'; + +import DiscordModule, { HybridInteractionMessage } from '../../utils/DiscordModule'; +import { makeInfoEmbed, makeErrorEmbed, sendHybridInteractionMessageResponse } from '../../utils/DiscordMessage'; + +import osuUsers from './Users'; +import osuBeatmaps from './Beatmaps'; + +const EMBEDS = { + osu_INFO: (data: HybridInteractionMessage) => { + 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.getUser() + }); + }, + NOT_INITIALIZED: (data: HybridInteractionMessage) => { + return makeErrorEmbed({ + title: `osu! feature is disabled`, + user: data.getUser() + }); + } +}; + +export default class osu extends DiscordModule { + public id = 'Discord_osu'; + public commands = ['osu']; + public commandInteractionName = 'osu'; + + async GuildOnModuleCommand(args: any, message: Message) { + await this.run(new HybridInteractionMessage(message), args); + } + + async GuildModuleCommandInteractionCreate(interaction: CommandInteraction) { + await this.run(new HybridInteractionMessage(interaction), interaction.options); + } + + async run(data: HybridInteractionMessage, args: any) { + const Guild = await Prisma.client.guild.findFirst({ where: { id: data.getGuild()!.id } }); + if (!Guild) return; + + if (!osuAPI.client) + return await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.NOT_INITIALIZED(data)] + }); + + let query; + if (data.isMessage()) { + if (args.length === 0) { + return await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.osu_INFO(data)] + }); + } + query = args[0].toLowerCase(); + } else if (data.isApplicationCommand()) { + query = args.getSubcommand(); + } + + switch (query) { + case 'user': + case 'u': + return await osuUsers.run(data, args); + case 'beatmap': + case 'b': + return await osuBeatmaps.run(data, args); + } + } +} diff --git a/src/utils/StringUtils.ts b/src/utils/StringUtils.ts new file mode 100644 index 0000000..f66d8f2 --- /dev/null +++ b/src/utils/StringUtils.ts @@ -0,0 +1,9 @@ +export default class StringUtils { + public static numberWithCommas(x: Number) { + try { + return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); + } catch (err) { + return x; + } + } +} From 7c648c1f55198b7b60b7f4f2133f1ebd9060acce Mon Sep 17 00:00:00 2001 From: Yuzu Date: Sun, 23 Apr 2023 13:41:09 +0700 Subject: [PATCH 2/2] Added Unload Global --- src/discord/InteractionManager.ts | 34 ++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/discord/InteractionManager.ts b/src/discord/InteractionManager.ts index c28c87f..44d43e4 100644 --- a/src/discord/InteractionManager.ts +++ b/src/discord/InteractionManager.ts @@ -25,7 +25,7 @@ const EMBEDS = { fields: [ { name: 'Available arguments', - value: '``reloadAll`` ``unloadAll`` ``reloadglobal``' + value: '``reloadAll`` ``unloadAll`` ``reloadGlobal`` ``unloadGlobal``' } ], user: data.getUser() @@ -126,6 +126,36 @@ export default class InteractionManager extends DiscordModule { ); } }, + unloadGlobal: async (data: HybridInteractionMessage) => { + let placeholder: HybridInteractionMessage | undefined; + + let _placeholder = await sendHybridInteractionMessageResponse(data, { + embeds: [EMBEDS.PROCESSING(data)] + }); + if (_placeholder) placeholder = new HybridInteractionMessage(_placeholder); + + try { + await unregisterAllGlobalCommands(); + + if (data && data.isMessage() && placeholder && placeholder.isMessage()) + return placeholder.getMessage().edit({ embeds: [EMBEDS.UNLOADALL_SUCCESS(data)] }); + else if (data.isApplicationCommand()) + return await sendHybridInteractionMessageResponse( + data, + { embeds: [EMBEDS.UNLOADALL_SUCCESS(data)] }, + true + ); + } catch (err) { + if (data && data.isMessage() && placeholder && placeholder.isMessage()) + return placeholder.getMessage().edit({ embeds: [EMBEDS.UNLOADALL_ERROR(data, err)] }); + else if (data.isApplicationCommand()) + return await sendHybridInteractionMessageResponse( + data, + { embeds: [EMBEDS.UNLOADALL_ERROR(data, err)] }, + true + ); + } + }, reloadGlobal: async (data: HybridInteractionMessage) => { let placeholder: HybridInteractionMessage | undefined; @@ -214,6 +244,8 @@ export default class InteractionManager extends DiscordModule { return await funct.reloadGlobal(data); case 'unloadall': return await funct.unloadAll(data); + case 'unloadglobal': + return await funct.unloadGlobal(data); } } }