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
This commit is contained in:
Romain J 2021-01-25 17:28:59 +01:00
parent fa3069244d
commit 7962205d16
17 changed files with 504 additions and 7 deletions

View file

@ -17,6 +17,7 @@
<w>gnous</w>
<w>ipinfo</w>
<w>iplocalise</w>
<w>ipwhois</w>
<w>jishaku</w>
<w>langue</w>
<w>levelname</w>

View file

@ -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

View file

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -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))

View file

@ -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)",
},
}

View file

@ -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

View file

@ -0,0 +1,9 @@
from discord.ext import commands
class RFC18(commands.UserNotFound):
pass
class InvalidIp(commands.BadArgument):
pass

View file

@ -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 ""

View file

@ -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}"

View file

@ -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 <EMAIL@ADDRESS>, 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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\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 ""

View file

View file

@ -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)

View file

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View file

@ -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 <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@ -29,7 +29,7 @@ msgstr ""
msgid ""
"**{}** physical memory\n"
"**{}** virtual memory\n"
"**{}**% CPU"
"**{:.2f}**% CPU"
msgstr ""
#: tuxbot/cogs/Utils/utils.py:77

View file

@ -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,

View file

@ -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