diff --git a/.gitignore b/.gitignore
index 26437f8..b95a218 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,4 +45,5 @@ build
!.envs/.local/
-data/settings/
\ No newline at end of file
+data/settings/
+dump.rdb
\ No newline at end of file
diff --git a/.idea/dictionaries/romain.xml b/.idea/dictionaries/romain.xml
index 9778327..4d91983 100644
--- a/.idea/dictionaries/romain.xml
+++ b/.idea/dictionaries/romain.xml
@@ -34,6 +34,7 @@
outoutxyz
outouxyz
pacman
+ peeringdb
perso
postgre
postgresql
diff --git a/setup.cfg b/setup.cfg
index 35e9ec7..90b394e 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -15,6 +15,7 @@ platforms = linux
packages = find_namespace:
python_requires = >=3.9
install_requires =
+ asyncstdlib>=3.9.1
asyncpg>=0.21.0
Babel>=2.8.0
discord.py @ git+https://github.com/Rapptz/discord.py
diff --git a/tuxbot/cogs/Network/functions/converters.py b/tuxbot/cogs/Network/functions/converters.py
index 566a956..fdd5857 100644
--- a/tuxbot/cogs/Network/functions/converters.py
+++ b/tuxbot/cogs/Network/functions/converters.py
@@ -1,4 +1,5 @@
from discord.ext import commands
+from discord.ext.commands import Context
def _(x):
@@ -6,7 +7,7 @@ def _(x):
class IPConverter(commands.Converter):
- async def convert(self, ctx, argument): # skipcq: PYL-W0613
+ async def convert(self, ctx: Context, argument: str): # skipcq: PYL-W0613
argument = argument.replace("http://", "").replace("https://", "")
argument = argument.rstrip("/")
@@ -17,7 +18,7 @@ class IPConverter(commands.Converter):
class DomainConverter(commands.Converter):
- async def convert(self, ctx, argument): # skipcq: PYL-W0613
+ async def convert(self, ctx: Context, argument: str): # skipcq: PYL-W0613
if not argument.startswith("http"):
return f"http://{argument}"
@@ -25,13 +26,18 @@ class DomainConverter(commands.Converter):
class QueryTypeConverter(commands.Converter):
- async def convert(self, ctx, argument): # skipcq: PYL-W0613
+ async def convert(self, ctx: Context, argument: str): # skipcq: PYL-W0613
return argument.lower()
class IPVersionConverter(commands.Converter):
- async def convert(self, ctx, argument): # skipcq: PYL-W0613
+ async def convert(self, ctx: Context, argument: str): # skipcq: PYL-W0613
if not argument:
return argument
return argument.replace("-", "").replace("ip", "").replace("v", "")
+
+
+class ASConverter(commands.Converter):
+ async def convert(self, ctx: Context, argument: str): # skipcq: PYL-W0613
+ return argument.lower().lstrip("as")
diff --git a/tuxbot/cogs/Network/functions/exceptions.py b/tuxbot/cogs/Network/functions/exceptions.py
index 94e950f..895d455 100644
--- a/tuxbot/cogs/Network/functions/exceptions.py
+++ b/tuxbot/cogs/Network/functions/exceptions.py
@@ -23,3 +23,7 @@ class InvalidQueryType(NetworkException):
class VersionNotFound(NetworkException):
pass
+
+
+class InvalidAsn(NetworkException):
+ pass
diff --git a/tuxbot/cogs/Network/functions/utils.py b/tuxbot/cogs/Network/functions/utils.py
index d9272a5..e97280f 100644
--- a/tuxbot/cogs/Network/functions/utils.py
+++ b/tuxbot/cogs/Network/functions/utils.py
@@ -13,11 +13,15 @@ from ipinfo.exceptions import RequestQuotaExceededError
from ipwhois import Net
from ipwhois.asn import IPASN
+from aiocache import cached
+from aiocache.serializers import PickleSerializer
+
from tuxbot.cogs.Network.functions.exceptions import (
VersionNotFound,
RFC18,
InvalidIp,
InvalidQueryType,
+ InvalidAsn,
)
@@ -25,7 +29,8 @@ def _(x):
return x
-def get_ip(ip: str, inet: str = "") -> str:
+@cached(ttl=15 * 60, serializer=PickleSerializer())
+async def get_ip(loop, ip: str, inet: str = "") -> str:
_inet: socket.AddressFamily | int = 0 # pylint: disable=no-member
if inet == "6":
@@ -33,17 +38,21 @@ def get_ip(ip: str, inet: str = "") -> str:
elif inet == "4":
_inet = socket.AF_INET
- try:
- return socket.getaddrinfo(str(ip), None, _inet)[1][4][0]
- except socket.gaierror as e:
- raise VersionNotFound(
- _(
- "Unable to collect information on this in the given "
- "version",
- )
- ) from e
+ def _get_ip(_ip: str):
+ try:
+ return socket.getaddrinfo(_ip, None, _inet)[1][4][0]
+ except socket.gaierror as e:
+ raise VersionNotFound(
+ _(
+ "Unable to collect information on this in the given "
+ "version",
+ )
+ ) from e
+
+ return await loop.run_in_executor(None, _get_ip, str(ip))
+@cached(ttl=15 * 60, serializer=PickleSerializer())
async def get_hostname(loop, ip: str) -> str:
def _get_hostname(_ip: str):
try:
@@ -62,6 +71,7 @@ async def get_hostname(loop, ip: str) -> str:
return "N/A"
+@cached(ttl=15 * 60, serializer=PickleSerializer())
async def get_ipwhois_result(loop, ip_address: str) -> NoReturn | dict:
def _get_ipwhois_result(_ip_address: str) -> NoReturn | dict:
try:
@@ -87,6 +97,7 @@ async def get_ipwhois_result(loop, ip_address: str) -> NoReturn | dict:
return {}
+@cached(ttl=15 * 60, serializer=PickleSerializer())
async def get_ipinfo_result(apikey: str, ip_address: str) -> dict:
try:
handler = ipinfo.getHandlerAsync(
@@ -97,6 +108,7 @@ async def get_ipinfo_result(apikey: str, ip_address: str) -> dict:
return {}
+@cached(ttl=15 * 60, serializer=PickleSerializer())
async def get_crimeflare_result(
session: aiohttp.ClientSession, ip_address: str
) -> Optional[str]:
@@ -149,20 +161,74 @@ def merge_ipinfo_ipwhois(ipinfo_result: dict, ipwhois_result: dict) -> dict:
return output
+@cached(ttl=15 * 60, serializer=PickleSerializer())
async def get_pydig_result(
- domain: str, query_type: str, dnssec: str | bool
+ loop, domain: str, query_type: str, dnssec: str | bool
) -> list:
additional_args = [] if dnssec is False else ["+dnssec"]
- resolver = pydig.Resolver(
- nameservers=[
- "80.67.169.40",
- "80.67.169.12",
- ],
- additional_args=additional_args,
- )
+ def _get_pydig_result(_domain: str) -> NoReturn | dict:
+ resolver = pydig.Resolver(
+ nameservers=[
+ "80.67.169.40",
+ "80.67.169.12",
+ ],
+ additional_args=additional_args,
+ )
- return resolver.query(domain, query_type)
+ return resolver.query(_domain, query_type)
+
+ try:
+ return await asyncio.wait_for(
+ loop.run_in_executor(None, _get_pydig_result, str(domain)),
+ timeout=0.500,
+ )
+ except asyncio.exceptions.TimeoutError:
+ return []
+
+
+@cached(ttl=15 * 60, serializer=PickleSerializer())
+async def get_peeringdb_as_set_result(
+ session: aiohttp.ClientSession, asn: str
+) -> Optional[dict]:
+ try:
+ async with session.get(
+ f"https://www.peeringdb.com/api/as_set/{asn}",
+ timeout=aiohttp.ClientTimeout(total=5),
+ ) as s:
+ return await s.json()
+ except (
+ aiohttp.ClientError,
+ aiohttp.ContentTypeError,
+ asyncio.exceptions.TimeoutError,
+ ):
+ pass
+
+ return None
+
+
+@cached(ttl=15 * 60, serializer=PickleSerializer())
+async def get_peeringdb_net_irr_as_set_result(
+ session: aiohttp.ClientSession, asn: str
+) -> Optional[dict]:
+ try:
+ async with session.get(
+ f"https://www.peeringdb.com/api/net?irr_as_set={asn}",
+ timeout=aiohttp.ClientTimeout(total=10),
+ ) as s:
+ json = await s.json()
+
+ for data in json:
+ if data["asn"] == int(asn):
+ return data
+ except (
+ aiohttp.ClientError,
+ aiohttp.ContentTypeError,
+ asyncio.exceptions.TimeoutError,
+ ):
+ pass
+
+ return None
def check_ip_version_or_raise(version: str) -> bool | NoReturn:
@@ -194,3 +260,10 @@ def check_query_type_or_raise(query_type: str) -> bool | NoReturn:
"Supported queries : A, AAAA, CNAME, NS, DS, DNSKEY, SOA, TXT, PTR, MX"
)
)
+
+
+def check_asn_or_raise(asn: str) -> bool | NoReturn:
+ if asn.isdigit() and int(asn) < 4_294_967_295:
+ return True
+
+ raise InvalidAsn(_("Invalid ASN provided"))
diff --git a/tuxbot/cogs/Network/network.py b/tuxbot/cogs/Network/network.py
index 697727c..5f117b2 100644
--- a/tuxbot/cogs/Network/network.py
+++ b/tuxbot/cogs/Network/network.py
@@ -15,6 +15,7 @@ from tuxbot.cogs.Network.functions.converters import (
IPVersionConverter,
DomainConverter,
QueryTypeConverter,
+ ASConverter,
)
from tuxbot.cogs.Network.functions.exceptions import (
RFC18,
@@ -22,6 +23,7 @@ from tuxbot.cogs.Network.functions.exceptions import (
VersionNotFound,
InvalidDomain,
InvalidQueryType,
+ InvalidAsn,
)
from tuxbot.core.bot import Tux
from tuxbot.core.i18n import (
@@ -37,13 +39,16 @@ from .config import NetworkConfig
from .functions.utils import (
get_ip,
get_hostname,
+ get_crimeflare_result,
get_ipinfo_result,
get_ipwhois_result,
- merge_ipinfo_ipwhois,
get_pydig_result,
+ # get_peeringdb_as_set_result,
+ # get_peeringdb_net_irr_as_set_result,
+ merge_ipinfo_ipwhois,
check_query_type_or_raise,
check_ip_version_or_raise,
- get_crimeflare_result,
+ check_asn_or_raise,
)
log = logging.getLogger("tuxbot.cogs.Network")
@@ -68,6 +73,7 @@ class Network(commands.Cog):
InvalidDomain,
InvalidQueryType,
VersionNotFound,
+ InvalidAsn,
),
):
await ctx.send(_(str(error), ctx, self.bot.config))
@@ -87,9 +93,8 @@ class Network(commands.Cog):
):
check_ip_version_or_raise(str(version))
- ip_address = await self.bot.loop.run_in_executor(
- None, get_ip, str(ip), str(version)
- )
+ ip_address = await get_ip(self.bot.loop, str(ip), str(version))
+
ip_hostname = await get_hostname(self.bot.loop, str(ip_address))
ipinfo_result = await get_ipinfo_result(
@@ -222,7 +227,7 @@ class Network(commands.Cog):
check_query_type_or_raise(str(query_type))
pydig_result = await get_pydig_result(
- str(domain), str(query_type), dnssec
+ self.bot.loop, str(domain), str(query_type), dnssec
)
e = discord.Embed(title=f"DIG {domain} {query_type}", color=0x5858D7)
@@ -285,3 +290,26 @@ class Network(commands.Cog):
domain
)
)
+
+ @command_extra(
+ name="peeringdb", aliases=["peer", "peering"], deletable=True
+ )
+ async def _peeringdb(self, ctx: ContextPlus, asn: ASConverter):
+ check_asn_or_raise(str(asn))
+
+ return await ctx.send("Not implemented yet")
+
+ # peeringdb_as_set_result = await get_peeringdb_as_set_result(
+ # self.bot.session, str(asn)
+ # )
+ # peeringdb_net_irr_as_set_result = (
+ # await get_peeringdb_net_irr_as_set_result(
+ # self.bot.session, peeringdb_as_set_result["data"][0][asn]
+ # )
+ # )["data"]
+ #
+ # data = peeringdb_net_irr_as_set_result
+ #
+ # self.bot.console.log(data)
+ #
+ # await ctx.send("done")
diff --git a/tuxbot/core/bot.py b/tuxbot/core/bot.py
index 307d3d2..fd48168 100644
--- a/tuxbot/core/bot.py
+++ b/tuxbot/core/bot.py
@@ -90,8 +90,6 @@ class Tux(commands.AutoShardedBot):
self._app_owners_fetched = False # to prevent abusive API calls
self.loop = asyncio.get_event_loop()
- self.before_invoke(self._typing)
-
super().__init__(
*args,
intents=discord.Intents.all(),
@@ -121,10 +119,6 @@ class Tux(commands.AutoShardedBot):
return False
- @staticmethod
- async def _typing(ctx: ContextPlus) -> None:
- await ctx.trigger_typing()
-
async def load_packages(self):
if packages:
with Progress() as progress: