From 7962205d162698f6344f78026b8d2df989d9dd18 Mon Sep 17 00:00:00 2001 From: Romain J Date: Mon, 25 Jan 2021 17:28:59 +0100 Subject: [PATCH] feat(commands|Network>iplocalise): feat iplocalise command todo: - fix l'INET6 qui s'affiche comme 10 plutot que 6 - suppr le message Retrieving info si la commande n'a pas pu etre faite - this shit https://canary.discord.com/channels/767804368233037886/768097484655689758/803299217279811634 --- .idea/dictionaries/romain.xml | 1 + setup.cfg | 1 + tuxbot/cogs/Admin/locales/messages.pot | 2 +- tuxbot/cogs/Custom/locales/messages.pot | 2 +- tuxbot/cogs/Network/__init__.py | 19 ++ tuxbot/cogs/Network/config.py | 15 ++ tuxbot/cogs/Network/functions/converters.py | 39 ++++ tuxbot/cogs/Network/functions/exceptions.py | 9 + tuxbot/cogs/Network/locales/en-US.po | 64 +++++++ tuxbot/cogs/Network/locales/fr-FR.po | 64 +++++++ tuxbot/cogs/Network/locales/messages.pot | 63 +++++++ tuxbot/cogs/Network/models/__init__.py | 0 tuxbot/cogs/Network/network.py | 199 ++++++++++++++++++++ tuxbot/cogs/Polls/locales/messages.pot | 2 +- tuxbot/cogs/Utils/locales/messages.pot | 4 +- tuxbot/core/utils/functions/extra.py | 5 +- tuxbot/core/utils/functions/utils.py | 22 +++ 17 files changed, 504 insertions(+), 7 deletions(-) create mode 100644 tuxbot/cogs/Network/__init__.py create mode 100644 tuxbot/cogs/Network/config.py create mode 100644 tuxbot/cogs/Network/functions/converters.py create mode 100644 tuxbot/cogs/Network/functions/exceptions.py create mode 100644 tuxbot/cogs/Network/locales/en-US.po create mode 100644 tuxbot/cogs/Network/locales/fr-FR.po create mode 100644 tuxbot/cogs/Network/locales/messages.pot create mode 100644 tuxbot/cogs/Network/models/__init__.py create mode 100644 tuxbot/cogs/Network/network.py diff --git a/.idea/dictionaries/romain.xml b/.idea/dictionaries/romain.xml index 3108911..b0a64d1 100644 --- a/.idea/dictionaries/romain.xml +++ b/.idea/dictionaries/romain.xml @@ -17,6 +17,7 @@ gnous ipinfo iplocalise + ipwhois jishaku langue levelname diff --git a/setup.cfg b/setup.cfg index 223b283..f5f62b5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,7 @@ install_requires = discord.py @ git+https://github.com/Rapptz/discord.py discord_flags>=2.1.1 humanize>=2.6.0 + ipwhois>=1.2.0 jishaku>=1.19.1.200 psutil>=5.7.2 requests>=2.25.1 diff --git a/tuxbot/cogs/Admin/locales/messages.pot b/tuxbot/cogs/Admin/locales/messages.pot index e43ef9c..c254abd 100644 --- a/tuxbot/cogs/Admin/locales/messages.pot +++ b/tuxbot/cogs/Admin/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Tuxbot-bot\n" "Report-Msgid-Bugs-To: rick@gnous.eu\n" -"POT-Creation-Date: 2021-01-25 14:36+0100\n" +"POT-Creation-Date: 2021-01-25 16:09+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/tuxbot/cogs/Custom/locales/messages.pot b/tuxbot/cogs/Custom/locales/messages.pot index f83bcd3..c5523a8 100644 --- a/tuxbot/cogs/Custom/locales/messages.pot +++ b/tuxbot/cogs/Custom/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Tuxbot-bot\n" "Report-Msgid-Bugs-To: rick@gnous.eu\n" -"POT-Creation-Date: 2021-01-25 14:36+0100\n" +"POT-Creation-Date: 2021-01-25 16:09+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/tuxbot/cogs/Network/__init__.py b/tuxbot/cogs/Network/__init__.py new file mode 100644 index 0000000..2846ead --- /dev/null +++ b/tuxbot/cogs/Network/__init__.py @@ -0,0 +1,19 @@ +from collections import namedtuple + +from tuxbot.core.bot import Tux +from .network import Network +from .config import NetworkConfig, HAS_MODELS + +VersionInfo = namedtuple("VersionInfo", "major minor micro release_level") +version_info = VersionInfo(major=1, minor=0, micro=0, release_level="alpha") + +__version__ = "v{}.{}.{}-{}".format( + version_info.major, + version_info.minor, + version_info.micro, + version_info.release_level, +).replace("\n", "") + + +def setup(bot: Tux): + bot.add_cog(Network(bot)) diff --git a/tuxbot/cogs/Network/config.py b/tuxbot/cogs/Network/config.py new file mode 100644 index 0000000..51276d6 --- /dev/null +++ b/tuxbot/cogs/Network/config.py @@ -0,0 +1,15 @@ +from structured_config import Structure, StrField + +HAS_MODELS = False + + +class NetworkConfig(Structure): + ipinfoKey: str = StrField("") + + +extra = { + "ipinfoKey": { + "type": str, + "description": "API Key for ipinfo.io (.iplocalise command)", + }, +} diff --git a/tuxbot/cogs/Network/functions/converters.py b/tuxbot/cogs/Network/functions/converters.py new file mode 100644 index 0000000..8d02e17 --- /dev/null +++ b/tuxbot/cogs/Network/functions/converters.py @@ -0,0 +1,39 @@ +import re + +from discord.ext import commands + +from tuxbot.cogs.Network.functions.exceptions import InvalidIp + + +def _(x): + return x + + +DOMAIN_PATTERN = "^([A-Za-z0-9]\.|[A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9]\.){1,3}[A-Za-z]{2,6}$" +IP_PATTERN = "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$" + + +class IPConverter(commands.Converter): + async def convert(self, ctx, argument): + argument = argument.replace("http://", "").replace("https://", "") + + check_domain = re.match(DOMAIN_PATTERN, argument) + check_ip = re.match(IP_PATTERN, argument) + + if check_domain or check_ip: + return argument + + raise InvalidIp(_("Invalid ip or domain")) + + +class IPVersionConverter(commands.Converter): + async def convert(self, ctx, argument): + if not argument: + return argument + + argument = argument.replace("-", "").replace("p", "").replace("v", "") + + if argument not in ["4", "6"]: + raise InvalidIp(_("Invalid ip version")) + + return argument diff --git a/tuxbot/cogs/Network/functions/exceptions.py b/tuxbot/cogs/Network/functions/exceptions.py new file mode 100644 index 0000000..9786340 --- /dev/null +++ b/tuxbot/cogs/Network/functions/exceptions.py @@ -0,0 +1,9 @@ +from discord.ext import commands + + +class RFC18(commands.UserNotFound): + pass + + +class InvalidIp(commands.BadArgument): + pass diff --git a/tuxbot/cogs/Network/locales/en-US.po b/tuxbot/cogs/Network/locales/en-US.po new file mode 100644 index 0000000..1b37c06 --- /dev/null +++ b/tuxbot/cogs/Network/locales/en-US.po @@ -0,0 +1,64 @@ +# French translations for Tuxbot-bot package +# Traductions françaises du paquet Tuxbot-bot. +# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER +# This file is distributed under the same license as the Tuxbot-bot package. +# Automatically generated, 2020. +# +msgid "" +msgstr "" +"Project-Id-Version: Tuxbot-bot\n" +"Report-Msgid-Bugs-To: rick@gnous.eu\n" +"POT-Creation-Date: 2021-01-19 14:39+0100\n" +"PO-Revision-Date: 2021-01-19 14:39+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: tuxbot/cogs/Network/functions/converters.py:22 +msgid "Invalid ip or domain" +msgstr "" + +#: tuxbot/cogs/Network/functions/converters.py:35 +msgid "Invalid ip version" +msgstr "" + +#: tuxbot/cogs/Network/network.py:49 tuxbot/cogs/Network/network.py:52 +#, python-brace-format +msgid "in v{v}" +msgstr "" + +#: tuxbot/cogs/Network/network.py:61 +#, python-brace-format +msgid "Impossible to collect information on this ip {version}" +msgstr "" + +#: tuxbot/cogs/Network/network.py:89 +#, python-brace-format +msgid "IP address {ip_address} is already defined as Private-Use Networks via RFC 1918." +msgstr "" + +#: tuxbot/cogs/Network/network.py:109 +msgid "*Retrieving information...*" +msgstr "" + +#: tuxbot/cogs/Network/network.py:123 +#, python-brace-format +msgid "Information for ``{ip} ({ip_address})``" +msgstr "" + +#: tuxbot/cogs/Network/network.py:135 tuxbot/cogs/Network/network.py:156 +msgid "Belongs to:" +msgstr "" + +#: tuxbot/cogs/Network/network.py:140 tuxbot/cogs/Network/network.py:161 +msgid "Region:" +msgstr "" + +#: tuxbot/cogs/Network/network.py:174 +#, python-brace-format +msgid "Hostname: {hostname}" +msgstr "" diff --git a/tuxbot/cogs/Network/locales/fr-FR.po b/tuxbot/cogs/Network/locales/fr-FR.po new file mode 100644 index 0000000..61b432a --- /dev/null +++ b/tuxbot/cogs/Network/locales/fr-FR.po @@ -0,0 +1,64 @@ +# French translations for Tuxbot-bot package +# Traductions françaises du paquet Tuxbot-bot. +# Copyright (C) 2020 THE Tuxbot-bot'S COPYRIGHT HOLDER +# This file is distributed under the same license as the Tuxbot-bot package. +# Automatically generated, 2020. +# +msgid "" +msgstr "" +"Project-Id-Version: Tuxbot-bot\n" +"Report-Msgid-Bugs-To: rick@gnous.eu\n" +"POT-Creation-Date: 2021-01-19 14:39+0100\n" +"PO-Revision-Date: 2021-01-19 14:39+0100\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: tuxbot/cogs/Network/functions/converters.py:22 +msgid "Invalid ip or domain" +msgstr "Nome de domaine ou adresse IP invalide" + +#: tuxbot/cogs/Network/functions/converters.py:35 +msgid "Invalid ip version" +msgstr "Version d'adresse IP invalide" + +#: tuxbot/cogs/Network/network.py:49 tuxbot/cogs/Network/network.py:52 +#, python-brace-format +msgid "in v{v}" +msgstr "en v{v}" + +#: tuxbot/cogs/Network/network.py:61 +#, python-brace-format +msgid "Impossible to collect information on this ip {version}" +msgstr "Impossible de collecter des informations pour cette IP {version}" + +#: tuxbot/cogs/Network/network.py:89 +#, python-brace-format +msgid "IP address {ip_address} is already defined as Private-Use Networks via RFC 1918." +msgstr "L'adresse ip {ip_address} est est reservée à un usage local selon la RFC 1918" + +#: tuxbot/cogs/Network/network.py:109 +msgid "*Retrieving information...*" +msgstr "*Récupération des informations...*" + +#: tuxbot/cogs/Network/network.py:123 +#, python-brace-format +msgid "Information for ``{ip} ({ip_address})``" +msgstr "Informations pour ``{ip} ({ip_address})``" + +#: tuxbot/cogs/Network/network.py:135 tuxbot/cogs/Network/network.py:156 +msgid "Belongs to:" +msgstr "Appartient à :" + +#: tuxbot/cogs/Network/network.py:140 tuxbot/cogs/Network/network.py:161 +msgid "Region:" +msgstr "Région :" + +#: tuxbot/cogs/Network/network.py:174 +#, python-brace-format +msgid "Hostname: {hostname}" +msgstr "Nom d'hôte : {hostname}" diff --git a/tuxbot/cogs/Network/locales/messages.pot b/tuxbot/cogs/Network/locales/messages.pot new file mode 100644 index 0000000..3c60d0a --- /dev/null +++ b/tuxbot/cogs/Network/locales/messages.pot @@ -0,0 +1,63 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the Tuxbot-bot package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Tuxbot-bot\n" +"Report-Msgid-Bugs-To: rick@gnous.eu\n" +"POT-Creation-Date: 2021-01-25 16:09+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: tuxbot/cogs/Network/functions/converters.py:22 +msgid "Invalid ip or domain" +msgstr "" + +#: tuxbot/cogs/Network/functions/converters.py:35 +msgid "Invalid ip version" +msgstr "" + +#: tuxbot/cogs/Network/network.py:49 tuxbot/cogs/Network/network.py:52 +#, python-brace-format +msgid "in v{v}" +msgstr "" + +#: tuxbot/cogs/Network/network.py:61 +#, python-brace-format +msgid "Impossible to collect information on this ip {version}" +msgstr "" + +#: tuxbot/cogs/Network/network.py:89 +#, python-brace-format +msgid "IP address {ip_address} is already defined as Private-Use Networks via RFC 1918." +msgstr "" + +#: tuxbot/cogs/Network/network.py:109 +msgid "*Retrieving information...*" +msgstr "" + +#: tuxbot/cogs/Network/network.py:123 +#, python-brace-format +msgid "Information for ``{ip} ({ip_address})``" +msgstr "" + +#: tuxbot/cogs/Network/network.py:135 tuxbot/cogs/Network/network.py:156 +msgid "Belongs to:" +msgstr "" + +#: tuxbot/cogs/Network/network.py:140 tuxbot/cogs/Network/network.py:161 +msgid "Region:" +msgstr "" + +#: tuxbot/cogs/Network/network.py:174 +#, python-brace-format +msgid "Hostname: {hostname}" +msgstr "" diff --git a/tuxbot/cogs/Network/models/__init__.py b/tuxbot/cogs/Network/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tuxbot/cogs/Network/network.py b/tuxbot/cogs/Network/network.py new file mode 100644 index 0000000..9455c5f --- /dev/null +++ b/tuxbot/cogs/Network/network.py @@ -0,0 +1,199 @@ +import functools +import logging +import socket +from typing import Union, NoReturn + +import discord +import ipinfo +import ipwhois +from discord.ext import commands +from ipinfo.exceptions import RequestQuotaExceededError +from ipwhois import Net +from ipwhois.asn import IPASN +from structured_config import ConfigFile + +from tuxbot.cogs.Network.functions.converters import ( + IPConverter, + IPVersionConverter, +) +from tuxbot.cogs.Network.functions.exceptions import RFC18, InvalidIp +from tuxbot.core.bot import Tux +from tuxbot.core.i18n import ( + Translator, +) +from tuxbot.core.utils.data_manager import cogs_data_path +from tuxbot.core.utils.functions.extra import ( + ContextPlus, + command_extra, +) +from .config import NetworkConfig + +log = logging.getLogger("tuxbot.cogs.Network") +_ = Translator("Network", __file__) + + +class Network(commands.Cog, name="Network"): + def __init__(self, bot: Tux): + self.bot = bot + self.config: NetworkConfig = ConfigFile( + str( + cogs_data_path(self.bot.instance_name, "Network") + / "config.yaml" + ), + NetworkConfig, + ).config + + async def cog_command_error(self, ctx, error): + if isinstance(error, (RequestQuotaExceededError, RFC18, InvalidIp)): + await ctx.send(_(str(error), ctx, self.bot.config)) + + # ========================================================================= + # ========================================================================= + + async def _get_ip(self, ctx: ContextPlus, ip: str, inet: str = "") -> str: + inet_text = "" + + if inet == "6": + inet = socket.AF_INET6 + inet_text = _("in v{v}", ctx, self.bot.config).format(v=inet) + elif inet == "4": + inet = socket.AF_INET + inet_text = _("in v{v}", ctx, self.bot.config).format(v=inet) + else: + inet = 0 + + try: + return socket.getaddrinfo(str(ip), None, inet)[1][4][0] + except socket.gaierror: + return await ctx.send( + _( + "Impossible to collect information on this ip {version}".format( + version=inet_text + ), + ctx, + self.bot.config, + ) + ) + + @staticmethod + def _get_hostname(ip: str) -> str: + try: + return socket.gethostbyaddr(ip)[0] + except socket.herror: + return "N/A" + + @staticmethod + def get_ipwhois_result(ip_address: str) -> Union[NoReturn, dict]: + try: + net = Net(ip_address) + obj = IPASN(net) + return obj.lookup() + except ipwhois.exceptions.ASNRegistryError: + return {} + except ipwhois.exceptions.IPDefinedError as e: + + def _(x): + return x + + raise RFC18( + _( + "IP address {ip_address} is already defined as Private-Use" + " Networks via RFC 1918." + ) + ) from e + + async def get_ipinfo_result( + self, ip_address: str + ) -> Union[NoReturn, dict]: + try: + handler = ipinfo.getHandlerAsync(self.config.ipinfoKey) + return (await handler.getDetails(ip_address)).all + except RequestQuotaExceededError: + return {} + + # ========================================================================= + # ========================================================================= + + @command_extra(name="iplocalise", aliases=["localiseip"], deletable=True) + async def _iplocalise( + self, + ctx: ContextPlus, + ip: IPConverter, + version: IPVersionConverter = "", + ): + tmp = await ctx.send( + _("*Retrieving information...*", ctx, self.bot.config), + deletable=False, + ) + + ip_address = await self._get_ip(ctx, str(ip), str(version)) + ip_hostname = self._get_hostname(ip_address) + + ipinfo_result = await self.get_ipinfo_result(ip_address) + ipwhois_result = await self.bot.loop.run_in_executor( + None, functools.partial(self.get_ipwhois_result, ip_address) + ) + + e = discord.Embed( + title=_( + "Information for ``{ip} ({ip_address})``", ctx, self.bot.config + ).format(ip=ip, ip_address=ip_address), + color=0x5858D7, + ) + + if ipinfo_result: + org = ipinfo_result.get("org", "") + asn = org.split()[0] + + e.add_field( + name=_("Belongs to:", ctx, self.bot.config), + value=f"[{org}](https://bgp.he.net/{asn})", + inline=True, + ) + + e.add_field( + name=_("Region:", ctx, self.bot.config), + value=f"{ipinfo_result.get('city', 'N/A')} - " + f"{ipinfo_result.get('region', 'N/A')} " + f"({ipinfo_result.get('country', 'N/A')})", + inline=False, + ) + + e.set_thumbnail( + url=f"https://www.countryflags.io/{ipinfo_result['country']}" + f"/shiny/64.png" + ) + elif ipwhois_result: + org = ipwhois_result.get("asn_description", "N/A") + asn = ipwhois_result.get("asn", "N/A") + asn_country = ipwhois_result.get("asn_country_code", "N/A") + + e.add_field( + name=_("Belongs to:", ctx, self.bot.config), + value=f"{org} ([AS{asn}](https://bgp.he.net/{asn}))", + inline=True, + ) + + e.add_field( + name=_("Region:", ctx, self.bot.config), + value=asn_country, + inline=False, + ) + + e.set_thumbnail( + url=f"https://www.countryflags.io/{asn_country}/shiny/64.png" + ) + + if ipwhois_result: + e.add_field( + name="RIR :", value=ipwhois_result["asn_registry"], inline=True + ) + + e.set_footer( + text=_("Hostname: {hostname}", ctx, self.bot.config).format( + hostname=ip_hostname + ), + ) + + await tmp.delete() + await ctx.send(embed=e) diff --git a/tuxbot/cogs/Polls/locales/messages.pot b/tuxbot/cogs/Polls/locales/messages.pot index 12e214d..1d21de4 100644 --- a/tuxbot/cogs/Polls/locales/messages.pot +++ b/tuxbot/cogs/Polls/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Tuxbot-bot\n" "Report-Msgid-Bugs-To: rick@gnous.eu\n" -"POT-Creation-Date: 2021-01-25 14:36+0100\n" +"POT-Creation-Date: 2021-01-25 16:09+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/tuxbot/cogs/Utils/locales/messages.pot b/tuxbot/cogs/Utils/locales/messages.pot index 58f747e..ff04ab9 100644 --- a/tuxbot/cogs/Utils/locales/messages.pot +++ b/tuxbot/cogs/Utils/locales/messages.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Tuxbot-bot\n" "Report-Msgid-Bugs-To: rick@gnous.eu\n" -"POT-Creation-Date: 2021-01-25 14:36+0100\n" +"POT-Creation-Date: 2021-01-25 16:09+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -29,7 +29,7 @@ msgstr "" msgid "" "**{}** physical memory\n" "**{}** virtual memory\n" -"**{}**% CPU" +"**{:.2f}**% CPU" msgstr "" #: tuxbot/cogs/Utils/utils.py:77 diff --git a/tuxbot/core/utils/functions/extra.py b/tuxbot/core/utils/functions/extra.py index aacca0d..05b8fe4 100644 --- a/tuxbot/core/utils/functions/extra.py +++ b/tuxbot/core/utils/functions/extra.py @@ -25,8 +25,9 @@ class ContextPlus(commands.Context): delete_after=None, nonce=None, allowed_mentions=None, - deletable=False + deletable=True ): # i know *args and **kwargs but, i prefer work with same values + if content: content = content.replace( self.bot.config.Core.token, TOKEN_REPLACEMENT @@ -47,7 +48,7 @@ class ContextPlus(commands.Context): if ( hasattr(self.command, "deletable") and self.command.deletable - ) or deletable: + ) and deletable: message = await super().send( content=content, tts=tts, diff --git a/tuxbot/core/utils/functions/utils.py b/tuxbot/core/utils/functions/utils.py index 6ed928b..5268612 100644 --- a/tuxbot/core/utils/functions/utils.py +++ b/tuxbot/core/utils/functions/utils.py @@ -1,2 +1,24 @@ +import functools + +from discord.ext import commands + +from tuxbot.core.utils.functions.extra import ContextPlus + + def upper_first(string: str) -> str: return "".join(string[0].upper() + string[1:]) + + +def typing(func): + @functools.wraps(func) + async def wrapped(*args, **kwargs): + context = ( + args[0] + if isinstance(args[0], (commands.Context, ContextPlus)) + else args[1] + ) + + async with context.typing(): + await func(*args, **kwargs) + + return wrapped