From 75076d3cf1a7968c96352861b2c13f529b241fbb Mon Sep 17 00:00:00 2001 From: ZRunner Date: Fri, 26 Jan 2024 23:04:35 -0500 Subject: [PATCH] feat(mc): convert command to slash cmd --- docs/minecraft.rst | 39 +++++--------- docs/perms.rst | 2 +- fcts/minecraft.py | 117 ++++++++++++++++++++++++----------------- lang/commands/en.json | 25 +++++++++ lang/commands/fr.json | 25 +++++++++ lang/minecraft/en.json | 9 ++-- lang/minecraft/fr.json | 9 ++-- 7 files changed, 145 insertions(+), 81 deletions(-) diff --git a/docs/minecraft.rst b/docs/minecraft.rst index c4944f80a..7fb3ed1ff 100755 --- a/docs/minecraft.rst +++ b/docs/minecraft.rst @@ -4,38 +4,27 @@ At the very beginning, Axobot was a single server bot, and focused on the world-famous Minecraft game. -Even after diversifying, the bot has not forgotten its origins and remains very open to this cubic world, offering several commands related to the game. You will find a huge database on all the blocks, entities, items, commands, progress, potion effects, enchantments, and more. As well as a command to obtain the status of a Minecraft server (it is possible to display it permanently so that the information is refreshed regularly). And another one for the state of Mojang's servers. If you find this content is very low, don't worry: other orders are in preparation! +Even after diversifying, the bot has not forgotten its origins and remains very open to this cubic world, offering several commands related to the game. You will still find some cool commands to get information about Minecraft Java servers, player skins, or mods. There's even a command to get the status of a Minecraft Java server in real time, right into your Discord server! -.. note:: The whole database comes from a single Minecraft site (French, like Axobot): `fr-minecraft.net `__ . The search engine and the information collected are therefore those appearing on this site. If you observe any error in this database, do not hesitate to contact me so that I relay it to the administrator of the site! +-------------------------- +Get a server/skin/mod info +-------------------------- -.. warning:: Most of these commands are reserved for certain roles only. To allow roles to use a command, see the `config` command +**Syntax:** :code:`minecraft (server|skin|mod) ` +Mods info come from either the `CurseForge API `__ or the `Modrinth API `__ (depending on which one offers the closest result to your query), so Axobot may not be able to find some mods. Please also note that their search engines sometimes behave very strangely and may not give the best results. Player and server searches use the official Mojang API and tools. ---- -MC ---- +.. warning:: The bot needs the "`Embed links `__" permission to send its search query, as well as "`Read message history `__" and to display the status of a server (enabled with `add` subcommand) -**Syntax:** :code:`mc ` -This command is the main command of this module: the one that allows to search the information in the database, or to get those from a Minecraft server. To ask the bot to send the status of a server and to refresh this message regularly, use the `add` subcommand followed by the server ip. The bot will then try to edit the last message about this server, and if it can't, it will send a new one. +-------------------------- +Subscribe to a server info +-------------------------- -To search in the database, the command is disconcertingly simple: you just have to write the type of your search (entity, block, mod, etc.) followed by its name (partial or total, French or English) or its identifier (numerical or textual). The rest does itself! +**Syntax:** :code:`minecraft follow-server [port] [channel]` -To see the list of available types, enter the help mc command in the chat. If you don't find what you're looking for, don't worry: this type is probably planned for later! +This command allows you to follow a Minecraft Java server right into your channel. Axobot will post a simple embed containing some information about the server (its version, number of connected players, motd, etc.), and update it on a regular basis. You can also specify a port if the server is not running on the default port (25565), and a channel if you want to post the embed in another channel than the one where you typed the command. -Mods info come from the `CurseForge API `__ (currently managed by Twitch), so Axobot may not be able to find some mods. Please also note that their search engine is very weird, and may not have the best results. Players search use the official Mojang API, and other data come from the french `fr-minecraft.net `__ website. - -.. warning:: +.. note:: * The bot needs the "`Embed links `__" permission to send its search query, as well as "`Read message history `__" and to display the status of a server (enabled with `add` subcommand) - * Adding server tracking automatically with `add` is considered the same way as an rss feed, which means that it takes a place in your feed list (limited to a certain number, except for a few special cases). - - ------- -Mojang ------- - -**Syntax:** :code:`mojang` or :code:`mojang_status` - -This command, much more basic, uses the Mojang API to get the status of its servers. For each server you will thus have its state, its url, as well as a short description. - -.. note:: The bot does not need any specific permission for this command, however note that the appearance will look better if ""`Embed links `__" permission is enabled. + * Adding server tracking automatically with `follow-server` is considered the same way as an rss feed, which means that it takes a place in your feeds list (limited to a certain number). diff --git a/docs/perms.rst b/docs/perms.rst index e7326f433..37baf29f6 100644 --- a/docs/perms.rst +++ b/docs/perms.rst @@ -118,7 +118,7 @@ Allows the bot to send a TTS (text-to-speech) message, i.e. a message that will Embed Links ----------- -Allows the bot the bot to send an embed. Some commands will need that permissions, some others will only look worse. Examples of use for a better display: `membercount `__ , `mojang `__, `XP system `__ . Examples of required permission: `infos `__ , `mc `__ , `config see `__, `embeds generator `__ +Allows the bot the bot to send an embed. Some commands will need that permissions, some others will only look worse. Examples of use for a better display: `membercount `__ , `mojang `__, `XP system `__ . Examples of required permission: `infos `__ , `minecraft `__ , `config see `__, `embeds generator `__ Attach Files ------------ diff --git a/fcts/minecraft.py b/fcts/minecraft.py index b038d7614..157f9b26b 100644 --- a/fcts/minecraft.py +++ b/fcts/minecraft.py @@ -6,9 +6,13 @@ from typing import Any, Optional, Union import aiohttp +from asyncache import cached +from cachetools import TTLCache import discord from dateutil.parser import isoparse +from discord import app_commands from discord.ext import commands +from fcts.rss import can_use_rss from libs.bot_classes import Axobot, MyContext from libs.checks import checks @@ -21,8 +25,7 @@ def _similar(input_1: str, input_2: str): return SequenceMatcher(None, input_1, input_2).ratio() class Minecraft(commands.Cog): - """Cog gathering all commands related to the Minecraft® game. -Every information come from the website www.fr-minecraft.net""" + """Cog gathering all commands related to the Minecraft® game""" def __init__(self, bot: Axobot): self.bot = bot @@ -45,10 +48,9 @@ async def cog_unload(self): self._session = None - @commands.group(name="minecraft", aliases=["mc"]) - @commands.cooldown(5, 30, commands.BucketType.user) + @commands.hybrid_group(name="minecraft") async def mc_main(self, ctx: MyContext): - """Search for Minecraft game items/servers + """Search for info about servers/players/mods from Minecraft ..Doc minecraft.html#mc""" if ctx.subcommand_passed is None: @@ -62,28 +64,23 @@ async def send_embed(self, ctx: MyContext, embed: discord.Embed): self.bot.dispatch("error", err, ctx) await ctx.send(await self.bot._(ctx.channel, "minecraft.serv-error")) - @mc_main.command(name="mod", aliases=["mods"]) - async def mc_mod(self, ctx: MyContext, *, value: str = 'help'): - """Get info about any mod registered on CurseForge + @mc_main.command(name="mod") + @app_commands.checks.cooldown(5, 20) + async def mc_mod(self, ctx: MyContext, *, mod_name: str): + """Get info about any mod registered on CurseForge or Modrinth - ..Example mc mod Minecolonies + ..Example minecraft mod Minecolonies ..Doc minecraft.html#mc""" - if value == 'help': - await ctx.send(await self.bot._(ctx.channel, "minecraft.mod-help", p=ctx.prefix)) - return - if not ctx.can_send_embed: - await ctx.send(await self.bot._(ctx.channel, "minecraft.no-embed")) - return await ctx.defer() - if cf_result := await self.get_mod_from_curseforge(ctx, value): + if cf_result := await self.get_mod_from_curseforge(ctx, mod_name): cf_embed, cf_pertinence = cf_result if cf_pertinence > 0.9: await self.send_embed(ctx, cf_embed) return else: cf_embed, cf_pertinence = None, 0 - if mr_result := await self.get_mod_from_modrinth(ctx, value): + if mr_result := await self.get_mod_from_modrinth(ctx, mod_name): mr_embed, mr_pertinence = mr_result if mr_pertinence > 0.9: await self.send_embed(ctx, mr_embed) @@ -217,16 +214,15 @@ async def get_mod_from_modrinth(self, ctx: MyContext, search_value: str): return embed, pertinence @mc_main.command(name="skin") + @app_commands.checks.cooldown(5, 20) @commands.check(checks.bot_can_embed) - async def mc_skin(self, ctx: MyContext, username): - """Get the skin of any Java player + async def mc_skin(self, ctx: MyContext, username: str): + """Get the skin of any Minecraft Java player - ..Example mc skin Notch + ..Example minecraft skin Notch ..Doc minecraft.html#mc""" - if not ctx.can_send_embed: - await ctx.send(await self.bot._(ctx.channel, "minecraft.no-embed")) - return + await ctx.defer() uuid = await self.username_to_uuid(username) if uuid is None: await ctx.send(await self.bot._(ctx.channel, "minecraft.player-not-found")) @@ -239,50 +235,72 @@ async def mc_skin(self, ctx: MyContext, username): await self.send_embed(ctx, emb) @mc_main.command(name="server") - async def mc_server(self, ctx: MyContext, ip: str, port: int = None): - """Get infos about any Minecraft server + @app_commands.checks.cooldown(5, 20) + async def mc_server(self, ctx: MyContext, ip: str, port: Optional[int] = None): + """Get infos about any Minecraft Java server - ..Example mc server play.gunivers.net + ..Example minecraft server play.gunivers.net ..Doc minecraft.html#mc""" if ":" in ip and port is None: - i = ip.split(":") - ip, port = i[0], i[1] - obj = await self.create_server_1(ctx.guild, ip, port) - await self.send_msg_server(obj, ctx.channel, (ip, port)) + ip, port_str = ip.split(":") + if not port_str.isnumeric(): + await ctx.send(await self.bot._(ctx.guild, "minecraft.invalid-port")) + return + port = int(port_str) + elif port is None: + port_str = None + else: + port_str = str(port) + await ctx.defer() + obj = await self.create_server_1(ctx.guild, ip, port_str) + embed = await self.form_msg_server(obj, ctx.guild, (ip, port_str)) + await ctx.send(embed=embed) - @mc_main.command(name="add") + @mc_main.command(name="follow-server") @commands.guild_only() - async def mc_add_server(self, ctx: MyContext, ip, port: int = None): - """Follow a server's info (regularly displayed on this channel) + @commands.check(can_use_rss) + @app_commands.checks.cooldown(5, 20) + async def mc_follow_server(self, ctx: MyContext, ip: str, port: Optional[int] = None, + channel: Optional[discord.TextChannel] = None): + """Follow a server's info in real time in your channel - ..Example mc add mc.hypixel.net + ..Example minecraft follow-server mc.hypixel.net ..Doc minecraft.html#mc""" if not ctx.bot.database_online: - return await ctx.send(await self.bot._(ctx.guild.id, "cases.no_database")) + await ctx.send(await self.bot._(ctx.guild.id, "cases.no_database")) + return if ":" in ip and port is None: - i = ip.split(":") - ip, port = i[0], i[1] - elif port is None: - port = '' + ip, port_str = ip.split(":") + if not port_str.isnumeric(): + await ctx.send(await self.bot._(ctx.guild, "minecraft.invalid-port")) + return + port = int(port_str) + # TODO: add proper ip validation and testing + await ctx.defer() is_over, flow_limit = await self.bot.get_cog('Rss').is_overflow(ctx.guild) if is_over: await ctx.send(await self.bot._(ctx.guild.id, "rss.flow-limit", limit=flow_limit)) return + if channel is None: + channel = ctx.channel + if not channel.permissions_for(ctx.guild.me).send_messages or channel.permissions_for(ctx.guild.me).embed_links: + await ctx.send(await self.bot._(ctx.guild.id, "minecraft.serv-follow.missing-perms")) + return try: if port is None: display_ip = ip else: display_ip = f"{ip}:{port}" - await self.bot.get_cog('Rss').db_add_feed(ctx.guild.id, ctx.channel.id, 'mc', f"{ip}:{port}") - await ctx.send(await self.bot._(ctx.guild, "minecraft.success-add", ip=display_ip, channel=ctx.channel.mention)) + await self.bot.get_cog('Rss').db_add_feed(ctx.guild.id, channel.id, 'mc', f"{ip}:{port or ''}") + await ctx.send(await self.bot._(ctx.guild, "minecraft.serv-follow.success", ip=display_ip, channel=channel.mention)) except Exception as err: cmd = await self.bot.get_command_mention("about") await ctx.send(await self.bot._(ctx.guild, "errors.unknown2", about=cmd)) self.bot.dispatch("error", err, ctx) - async def create_server_1(self, guild: discord.Guild, ip: str, port=None) -> Union[str, 'MCServer']: + async def create_server_1(self, guild: discord.Guild, ip: str, port: Optional[str]=None) -> Union[str, 'MCServer']: "Collect and serialize server data from a given IP, using minetools.eu" if port is None: url = "https://api.minetools.eu/ping/"+str(ip) @@ -326,7 +344,7 @@ async def create_server_1(self, guild: discord.Guild, ip: str, port=None) -> Uni ping=l, desc=data['description'], api='api.minetools.eu' ).clear_desc() - async def create_server_2(self, guild: discord.Guild, ip: str, port: str): + async def create_server_2(self, guild: discord.Guild, ip: str, port: Optional[str]): "Collect and serialize server data from a given IP, using mcsrvstat.us" if port is None: url = "https://api.mcsrvstat.us/1/"+str(ip) @@ -335,7 +353,7 @@ async def create_server_2(self, guild: discord.Guild, ip: str, port: str): try: async with self.session.get(url, timeout=5) as resp: data: dict = await resp.json() - except aiohttp.ClientConnectorError: + except (aiohttp.ClientConnectorError, aiohttp.ContentTypeError): return await self.bot._(guild, "minecraft.no-api") except json.decoder.JSONDecodeError: return await self.bot._(guild, "minecraft.serv-error") @@ -368,6 +386,7 @@ async def create_server_2(self, guild: discord.Guild, ip: str, port: str): ping=l, desc=desc, api="api.mcsrvstat.us" ).clear_desc() + @cached(TTLCache(maxsize=1_000, ttl=60*60*24*7)) # 1 week async def username_to_uuid(self, username: str) -> str: """Convert a minecraft username to its uuid""" if username in self.uuid_cache: @@ -431,19 +450,19 @@ async def create_msg(self, guild: discord.Guild, translate): else: embed.add_field(name=await translate(guild, "minecraft.serv-2"), value=", ".join(p)) if self.ping is not None: - embed.add_field(name=await translate(guild, "minecraft.serv-3"), value=f"{self.ping} ms") + embed.add_field(name=await translate(guild, "minecraft.serv-3"), value=f"{self.ping:.0f} ms") if self.desc: embed.add_field(name="Description", value="```\n" + self.desc + "\n```", inline=False) return embed - async def send_msg_server(self, obj: Union[str, "MCServer"], channel: discord.abc.Messageable, ip: str): + async def send_msg_server(self, obj: Union[str, "MCServer"], channel: discord.abc.Messageable, ip: tuple[str, Optional[str]]): "Send the message into a Discord channel" guild = None if isinstance(channel, discord.DMChannel) else channel.guild - e = await self.form_msg_server(obj, guild, ip) + embed = await self.form_msg_server(obj, guild, ip) if self.bot.zombie_mode: return if isinstance(channel, discord.DMChannel) or channel.permissions_for(channel.guild.me).embed_links: - msg = await channel.send(embed=e) + msg = await channel.send(embed=embed) else: try: await channel.send(await self.bot._(guild, "minecraft.cant-embed")) @@ -452,7 +471,7 @@ async def send_msg_server(self, obj: Union[str, "MCServer"], channel: discord.ab msg = None return msg - async def form_msg_server(self, obj: Union[str, "MCServer"], guild: discord.Guild, ip: str): + async def form_msg_server(self, obj: Union[str, "MCServer"], guild: discord.Guild, ip: tuple[str, Optional[str]]): "Create the embed from the saved data" if isinstance(obj, str): if ip[1] is None: diff --git a/lang/commands/en.json b/lang/commands/en.json index 6083b5e52..fa0507e55 100644 --- a/lang/commands/en.json +++ b/lang/commands/en.json @@ -38,6 +38,10 @@ "kick": "kick", "last-post": "last-post", "membercount": "membercount", + "minecraft follow-server": "follow-server", + "minecraft mod": "mod", + "minecraft server": "server", + "minecraft skin": "skin", "modlogs disable": "disable", "modlogs enable": "enable", "modlogs list": "list", @@ -119,6 +123,7 @@ "config": "config", "emoji": "emoji", "event": "event", + "minecraft": "minecraft", "modlogs": "modlogs", "profile": "profile", "reminders": "reminders", @@ -137,6 +142,10 @@ "cases remove": "Permanently delete a moderation case", "cases search": "Find the details of a moderation case", "membercount": "Get some stats about your server members", + "minecraft follow-server": "Follow a server's info in real time in your channel", + "minecraft mod": "Get info about any mod registered on CurseForge or Modrinth", + "minecraft server": "Get infos about any Minecraft Java server", + "minecraft skin": "Get the skin of any Minecraft Java player", "random-tip": "Get a random tip or trivia about the bot", "set-xp": "Set the XP of a user in your server" }, @@ -146,6 +155,7 @@ "config": "Configure the bot for your server", "emoji": "Manage your emojis", "event": "Participate in bot special events", + "minecraft": "Search for info about servers/players/mods from Minecraft", "modlogs": "Enable or disable server logs in specific channels", "profile": "Configure the bot for your own usage", "reminders": "Manage your pending reminders", @@ -243,6 +253,21 @@ "message": "message", "emoji": "emoji" }, + "minecraft follow-server": { + "ip": "ip", + "port": "port", + "channel": "channel" + }, + "minecraft mod": { + "mod_name": "mod-name" + }, + "minecraft server": { + "ip": "ip", + "port": "port" + }, + "minecraft skin": { + "username": "username" + }, "role set-color": { "role": "role", "color": "color" diff --git a/lang/commands/fr.json b/lang/commands/fr.json index fb6fc5a26..d5eda0e90 100644 --- a/lang/commands/fr.json +++ b/lang/commands/fr.json @@ -38,6 +38,10 @@ "kick": "expulser", "last-post": "dernier-post", "membercount": "nb-membres", + "minecraft follow-server": "suivre-serveur", + "minecraft mod": "mod", + "minecraft server": "serveur", + "minecraft skin": "skin", "modlogs disable": "désactiver", "modlogs enable": "activer", "modlogs list": "liste", @@ -114,6 +118,7 @@ "config": "config", "emoji": "émoji", "event": "événement", + "minecraft": "minecraft", "modlogs": "logs-modération", "profile": "profil", "reminders": "rappels", @@ -135,6 +140,10 @@ "cases search": "Affiche les détails d'un casier de modération", "clear": "Supprime des messages du salon", "membercount": "Donne quelques stats sur les membres du serveur", + "minecraft follow-server": "Suis les informations d'un serveur en temps réel dans ton salon", + "minecraft mod": "Obtiens des informations sur tout mod enregistré sur CurseForge ou Modrinth", + "minecraft server": "Obtiens des informations sur un serveur Minecraft Java", + "minecraft skin": "Obtiens le skin de n'importe quel joueur Minecraft Java", "random-tip": "Affiche une astuce aléatoire à propos du bot", "set-xp": "Modifie l'XP d'un utilisateur dans le serveur", "slowmode": "Ralenti le spam du salon en activant le mode lent" @@ -145,6 +154,7 @@ "config": "Configure le bot pour ton serveur", "emoji": "Gère les émojis de votre serveur", "event": "Participe aux événements spéciaux du bot", + "minecraft": "Recherche d'informations sur les serveurs/joueurs/mods de Minecraft", "modlogs": "Active ou désactive les logs de modération", "profile": "Configure le bot pour ta propre utilisation", "reminders": "Gère tes rappels en attente", @@ -242,6 +252,21 @@ "message": "message", "emoji": "émoji" }, + "minecraft follow-server": { + "ip": "ip", + "port": "port", + "channel": "salon" + }, + "minecraft mod": { + "mod_name": "nom" + }, + "minecraft server": { + "ip": "ip", + "port": "port" + }, + "minecraft skin": { + "username": "joueur" + }, "role set-color": { "role": "rôle", "color": "couleur" diff --git a/lang/minecraft/en.json b/lang/minecraft/en.json index 743d71009..4d9d8c2f3 100644 --- a/lang/minecraft/en.json +++ b/lang/minecraft/en.json @@ -1,5 +1,6 @@ { "cant-embed": "Cannot send embed. Please make sure the \"Embed links\" permission is enabled.", + "invalid-port": "Invalid server port", "mod-fields": { "author": "Author", "authors": "Authors", @@ -12,7 +13,6 @@ "summary": "Summary", "versions": "Game versions" }, - "mod-help": "This command provides information about a large number of Minecraft mods. You can give its full or partial name, just enter `%{p}mc mod `. The mod must be registered on the CurseForge platform to appear", "mod-title": "Mod", "no-api": "Error: Unable to connect to API", "no-embed": "Unable to send the result! Please give me 'Embed links' permission to send it", @@ -26,7 +26,10 @@ "serv-1": "List of the first 20 players connected", "serv-2": "List of online players", "serv-3": "Latency", + "serv-follow": { + "missing-perms": "Error: I cannot send messages and embeds in the selected channel", + "success": "A message with server details %{ip} has been added to the channel %{channel}, and will be updated regularly!" + }, "serv-error": "Oops, an unknown error occurred. Please try again later :confused:", - "serv-title": "Server information %{ip}", - "success-add": "A message with server details %{ip} has been added to the channel %{channel}!" + "serv-title": "Server information %{ip}" } \ No newline at end of file diff --git a/lang/minecraft/fr.json b/lang/minecraft/fr.json index f14f6df06..15319ce79 100644 --- a/lang/minecraft/fr.json +++ b/lang/minecraft/fr.json @@ -1,5 +1,6 @@ { "cant-embed": "Impossible d'envoyez l'embed. Vérifiez que la permission \"Embed links\" est bien activée", + "invalid-port": "Numéro de port invalide", "mod-fields": { "author": "Auteur", "authors": "Auteurs", @@ -12,7 +13,6 @@ "summary": "Résumé", "versions": "Versions du jeu" }, - "mod-help": "Cette commande permet d'obtenir des informations sur un grand nombre de mods Minecraft. Vous pouvez donner son nom complet ou partiel, il suffit d'entrer `%{p}mc mod `. Le mod doit être enregistré sur la plateforme CurseForge pour apparaître", "mod-title": "Mod", "no-api": "Erreur : Impossible de se connecter à l'API", "no-embed": "Impossible d'envoyer le résultat ! Veuillez me donner la permission 'Intégrer les liens' pour que je puisse l'envoyer.", @@ -26,7 +26,10 @@ "serv-1": "Liste des 20 premiers joueurs connectés", "serv-2": "Liste des joueurs connectés", "serv-3": "Latence", + "serv-follow": { + "missing-perms": "Impossible de faire cela : je ne peux pas envoyer de messages et d'embeds dans ce salon", + "success": "Un message avec les détails du serveur %{ip} a bien été ajouté dans le salon %{channel}, et sera régulièrement mis à jour !" + }, "serv-error": "Oups, une erreur inconnue s'est produite. Veuillez réessayer plus tard :confused:", - "serv-title": "Informations du serveur %{ip}", - "success-add": "Un message avec les détails du serveur %{ip} a bien été ajouté dans le salon %{channel} !" + "serv-title": "Informations du serveur %{ip}" } \ No newline at end of file