Compare commits

...

55 commits

Author SHA1 Message Date
d173dd68a5
fix(workflow): nvm 2024-06-11 16:50:15 -04:00
24239425c5
fix(workflows): missing -e argument 2024-06-11 16:48:03 -04:00
e3e96e5b84
fix(workflow): changed meli token 2024-06-11 16:39:04 -04:00
28f814db56
fix(seautils): pylint fix 2024-06-04 12:16:09 -04:00
7354769962
fix(emojiinfo): pylint fix 2024-06-04 12:16:01 -04:00
2ac1dacd19
fix(backup): added another error type caught by backup import, in the case where you didn't reply to a message or upload a file 2024-06-03 01:10:00 -04:00
66b933569b
misc(seautils): soup 2024-06-01 15:03:23 -04:00
c06db07f08
misc(seautils): bunch of miscellaneous changes 2024-06-01 15:02:08 -04:00
46f189a297
fix(seautils): pylint fix 2024-05-30 11:02:22 -04:00
545106d496
fix(seautils): fixed a typo in a function name 2024-05-28 22:25:47 -04:00
037a26deb0
misc(seautils): switch to datatracker.ietf.org links for publicly facing urls 2024-05-28 22:20:51 -04:00
f51329524c
fix(seautils): oops lmao 2024-05-28 22:19:09 -04:00
5b23f2f0fb
feat(seautils): add the url of the rfc document being retrieved to the embed 2024-05-28 22:17:20 -04:00
8f492cd937
fix(seautils): hopefully actually fixed the docstring 2024-05-28 22:14:35 -04:00
dfabac55f5
misc(seautils): hopefully fixed [p]rfc's help from overflowing into a second page 2024-05-28 22:13:18 -04:00
4f25e3d0f3
fix(seautils): removed some text from the rfc docstring 2024-05-28 22:11:11 -04:00
7207cd3747
misc(seautils): improved the rfc docstring 2024-05-28 22:09:56 -04:00
069ea800db
feat(seautils): added a detailed docstring to the rfc command 2024-05-28 22:02:42 -04:00
58245c621c
misc(seautils): changed a function name 2024-05-28 21:58:20 -04:00
b9f0dbf98a
fix(seautils): removed big gap at the bottom of rfc embeds 2024-05-28 21:56:43 -04:00
2d895d16c9
fix(seautils): fixed body error 2024-05-28 21:54:48 -04:00
861a03719b
fix(seautils): added a bad solution to a stupid problem 2024-05-28 21:22:30 -04:00
ac5d4df36b
fix(seautils): fixed broken regex 2024-05-28 21:18:49 -04:00
ae8d0d5db4
fix(seautils): added missing () 2024-05-28 21:17:29 -04:00
0ea80075f6
fix(seautils): fixed a missing argument 2024-05-28 21:16:45 -04:00
8d3f5c1d5f
fix(seautils): fix table of contents in rfc documents 2024-05-28 21:16:10 -04:00
42e209b547
misc(seautils): include the number of the rfc document in the embed 2024-05-28 21:10:28 -04:00
0ed96babdb
fix(seautils): ACTUALLY fixed broken regex 2024-05-28 20:40:48 -04:00
29b6a2141a
fix(seautils): actually actually fixed incorrect regex 2024-05-28 20:39:19 -04:00
c4ef2a7d4b
fix(seautils): fixed some broken regex 2024-05-28 20:38:39 -04:00
99cd13ccf1
feat(seautils): add correct formatting for masked links 2024-05-28 20:35:25 -04:00
28246121a6
feat(seautils): use markdownify to convert rfc html documents to markdown 2024-05-28 20:29:39 -04:00
a641cae640
feat(seautils): add [p]rfc command 2024-05-28 20:20:21 -04:00
2886d5e80d
feat(seautils): use prolog syntax highlighting for dig results 2024-05-28 19:29:26 -04:00
50094b85fc
fix(seautils): fixed the wrong thing lmao 2024-05-28 19:26:07 -04:00
7f46d6accc
fix(seautils): fixed empty answer section 2024-05-28 19:25:01 -04:00
cb6ddabb4d
fix(seautils): prevent duplicates in dig 2024-05-28 19:10:15 -04:00
8608e6a34e
fix(seautils): fixed only the first A response being used 2024-05-28 19:06:11 -04:00
fb468ee63e
fix(seautils): retrieve A, AAAA, and CNAME records by default (& docstring changes) 2024-05-28 18:55:09 -04:00
b27a3ee778
fix(seautils): fall back to the embed description if answer_section is too long 2024-05-28 18:46:32 -04:00
54491cb9c9
fix(seautils): convert port number to a string 2024-05-28 18:44:01 -04:00
29bb64b35c
fix(seautils): query ANY instead of A records 2024-05-28 18:42:42 -04:00
5ffc42480a
fix(seautils): revert breaking dig 2024-05-28 18:40:43 -04:00
7d51814a28
misc(seautils): purposefully breaking dig so i can test nslookup fallback 2024-05-28 18:35:17 -04:00
25fdf7b402
feat(seautils): default to nslookup if dig is not present 2024-05-28 18:31:38 -04:00
091f4fe36d
fix(seautils): fixed maybe_send_message 2024-05-28 18:02:17 -04:00
d444242245
fix(seautils): catch the error that is raised if dig is not installed on the system 2024-05-28 18:01:35 -04:00
7a2ee0a655
misc(seautils): added an error symbol to the failed dns query result message 2024-05-28 17:57:10 -04:00
8b68cb7530
fix(seautils): don't use match and case 2024-05-28 17:54:46 -04:00
028cae9e99
feat(seautils): improve error code handling 2024-05-28 17:53:58 -04:00
aa7e347a95
fix(seautils): fix a keyerror 2024-05-28 17:48:00 -04:00
93f358cfad
fix(seautils): fixed timestamp 2024-05-28 17:46:14 -04:00
8867cc627f
feat(seautils): add an embed to the dig command 2024-05-28 16:33:15 -04:00
e2059eac77
Merge branch 'main' of https://www.coastalcommits.com/Seaswimmer/SeaCogs 2024-05-28 16:23:56 -04:00
e9c062afa9
feat(seautils): added dig command 2024-05-28 16:22:22 -04:00
8 changed files with 230 additions and 12 deletions

View file

@ -18,4 +18,5 @@
import-self,
relative-beyond-top-level,
too-many-instance-attributes,
duplicate-code
duplicate-code,
too-many-nested-blocks

View file

@ -58,7 +58,7 @@ jobs:
npx -p "@getmeli/cli" meli upload ./site \
--url "https://pages.coastalcommits.com" \
--site "${{ vars.MELI_SITE_ID }}" \
--token "${{ secrets.MELI_SITE_SECRET }}" \
--token "${{ secrets.MELI_SECRET }}" \
--release "$CI_ACTION_REF_NAME_SLUG/${{ env.GITHUB_SHA }}" \
--branch "$CI_ACTION_REF_NAME_SLUG"

View file

@ -100,7 +100,7 @@ class Backup(commands.Cog):
except (json.JSONDecodeError, IndexError):
try:
export = json.loads(await ctx.message.reference.resolved.attachments[0].read())
except (json.JSONDecodeError, IndexError):
except (json.JSONDecodeError, IndexError, AttributeError):
await ctx.send(error("Please provide a valid JSON export file."))
return

View file

@ -81,6 +81,7 @@ class PartialEmoji(discord.PartialEmoji):
with open(path, "r", encoding="UTF-8") as file:
emojis: dict = json.load(file)
emoji_aliases = []
emoji_group = None
for dict_name, group in emojis.items():
for k, v in group.items():
if v == value:

49
poetry.lock generated
View file

@ -228,6 +228,27 @@ files = [
[package.extras]
dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"]
[[package]]
name = "beautifulsoup4"
version = "4.12.3"
description = "Screen-scraping library"
optional = false
python-versions = ">=3.6.0"
files = [
{file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"},
{file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"},
]
[package.dependencies]
soupsieve = ">1.2"
[package.extras]
cchardet = ["cchardet"]
chardet = ["chardet"]
charset-normalizer = ["charset-normalizer"]
html5lib = ["html5lib"]
lxml = ["lxml"]
[[package]]
name = "brotli"
version = "1.1.0"
@ -890,6 +911,21 @@ profiling = ["gprof2dot"]
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
[[package]]
name = "markdownify"
version = "0.12.1"
description = "Convert HTML to markdown."
optional = false
python-versions = "*"
files = [
{file = "markdownify-0.12.1-py3-none-any.whl", hash = "sha256:a3805abd8166dbb7b27783c5599d91f54f10d79894b2621404d85b333c7ce561"},
{file = "markdownify-0.12.1.tar.gz", hash = "sha256:1fb08c618b30e0ee7a31a39b998f44a18fb28ab254f55f4af06b6d35a2179e27"},
]
[package.dependencies]
beautifulsoup4 = ">=4.9,<5"
six = ">=1.15,<2"
[[package]]
name = "markupsafe"
version = "2.1.5"
@ -2111,6 +2147,17 @@ files = [
{file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"},
]
[[package]]
name = "soupsieve"
version = "2.5"
description = "A modern CSS selector implementation for Beautiful Soup."
optional = false
python-versions = ">=3.8"
files = [
{file = "soupsieve-2.5-py3-none-any.whl", hash = "sha256:eaa337ff55a1579b6549dc679565eac1e3d000563bcb1c8ab0d0fefbc0c2cdc7"},
{file = "soupsieve-2.5.tar.gz", hash = "sha256:5663d5a7b3bfaeee0bc4372e7fc48f9cff4940b3eec54a6451cc5299f1097690"},
]
[[package]]
name = "tinycss2"
version = "1.2.1"
@ -2451,4 +2498,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = ">=3.11,<3.12"
content-hash = "0ac382e0399d9c23c5f89a0ffeb3aae056dc8b28e864b22f815c0e3eb34175bd"
content-hash = "229d7fd39618cf708f3cd5409dde2e6e25b822e4f936e14b3ade9800bf00daab"

View file

@ -15,6 +15,8 @@ websockets = "^12.0"
pillow = "^10.3.0"
numpy = "^1.26.4"
colorthief = "^0.2.1"
beautifulsoup4 = "^4.12.3"
markdownify = "^0.12.1"
[tool.poetry.group.dev]
optional = true

View file

@ -8,5 +8,6 @@
"hidden": true,
"disabled": false,
"min_bot_version": "3.5.0",
"min_python_version": [3, 8, 0]
"min_python_version": [3, 8, 0],
"requirements": ["beautifulsoup4", "markdownify"]
}

View file

@ -5,13 +5,20 @@
# ____) | __/ (_| \__ \\ V V /| | | | | | | | | | | | __/ |
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
import asyncio
import inspect
import operator
import re
from asyncio.subprocess import Process
from functools import partial, partialmethod
from typing import Any
from discord import Embed, app_commands
import aiohttp
import yaml
from bs4 import BeautifulSoup
from discord import Color, Embed, app_commands
from discord.utils import CachedSlotProperty, cached_property
from markdownify import MarkdownConverter
from redbot.core import commands
from redbot.core.bot import Red
from redbot.core.dev_commands import cleanup_code
@ -19,29 +26,38 @@ from redbot.core.utils import chat_formatting as cf
from redbot.core.utils.views import SimpleMenu
def md(soup: BeautifulSoup, **options) -> Any | str:
return MarkdownConverter(**options).convert_soup(soup=soup)
def format_rfc_text(text: str, number: int) -> str:
one: str = re.sub(r"\(\.\/rfc(\d+)", r"(https://www.rfc-editor.org/rfc/rfc\1.html", text)
two: str = re.sub(r"\((#(?:section|page)-\d+(?:.\d+)?)\)", f"(https://www.rfc-editor.org/rfc/rfc{number}.html\1)", one)
three: str = re.sub(r"\n{3,}", "\n\n", two)
return three
class SeaUtils(commands.Cog):
"""A collection of random utilities."""
__author__ = ["SeaswimmerTheFsh"]
__version__ = "1.0.0"
def __init__(self, bot: Red):
def __init__(self, bot: Red) -> None:
self.bot = bot
def format_help_for_context(self, ctx: commands.Context) -> str:
pre_processed = super().format_help_for_context(ctx) or ""
pre_processed = super().format_help_for_context(ctx=ctx) or ""
n = "\n" if "\n\n" not in pre_processed else ""
text = [
f"{pre_processed}{n}",
f"Cog Version: **{self.__version__}**",
f"Author: {cf.humanize_list(self.__author__)}"
f"Author: {cf.humanize_list(items=self.__author__)}"
]
return "\n".join(text)
def format_src(self, obj: Any) -> str:
"""A large portion of this code is repurposed from Zephyrkul's RTFS cog.
https://github.com/Zephyrkul/FluffyCogs/blob/master/rtfs/rtfs.py"""
obj = inspect.unwrap(obj)
obj = inspect.unwrap(func=obj)
src: Any = getattr(obj, "__func__", obj)
if isinstance(obj, (commands.Command, app_commands.Command)):
src = obj.callback
@ -51,11 +67,11 @@ class SeaUtils(commands.Cog):
src = obj.fget
elif isinstance(obj, (cached_property, CachedSlotProperty)):
src = obj.function
return inspect.getsource(src)
return inspect.getsource(object=src)
@commands.command(aliases=["source", "src", "code", "showsource"])
@commands.is_owner()
async def showcode(self, ctx: commands.Context, *, object: str): # pylint: disable=redefined-builtin
async def showcode(self, ctx: commands.Context, *, object: str) -> None: # pylint: disable=redefined-builtin
"""Show the code for a particular object."""
try:
if object.startswith("/") and (obj := ctx.bot.tree.get_command(object[1:])):
@ -64,6 +80,8 @@ class SeaUtils(commands.Cog):
text = self.format_src(type(obj))
elif obj := ctx.bot.get_command(object):
text = self.format_src(obj)
else:
raise AttributeError
temp_content = cf.pagify(
text=cleanup_code(text),
escape_mass_mentions=True,
@ -82,3 +100,151 @@ class SeaUtils(commands.Cog):
await ctx.send(embed=embed, reference=ctx.message.to_reference(fail_if_not_exists=False))
else:
await ctx.send(content="Object not found!", reference=ctx.message.to_reference(fail_if_not_exists=False))
@commands.command(name='dig', aliases=['dnslookup', 'nslookup'])
@commands.is_owner()
async def dig(self, ctx: commands.Context, name: str, record_type: str | None = None, server: str | None = None, port: int = 53) -> None:
"""Retrieve DNS information for a domain.
Uses `dig` to perform a DNS query. Will fall back to `nslookup` if `dig` is not installed on the system.
`nslookup` does not provide as much information as `dig`, so only the `name` parameter will be used if `nslookup` is used.
Will return the A, AAAA, and CNAME records for a domain by default. You can specify a different record type with the `type` parameter."""
command_opts: list[str | int] = ['dig']
query_types: list[str] = [record_type] if record_type else ['A', 'AAAA', 'CNAME']
if server:
command_opts.extend(['@', server])
for query_type in query_types:
command_opts.extend([name, query_type])
command_opts.extend(['-p', str(port), '+yaml'])
try:
process: Process = await asyncio.create_subprocess_exec(*command_opts, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
stdout, stderr = await process.communicate()
if stderr:
await ctx.maybe_send_embed(message="An error was encountered!\n" + cf.box(text=stderr.decode()))
else:
data = yaml.safe_load(stdout.decode())
message_data: dict = data[0]['message']
response_data: dict = message_data['response_message_data']
if ctx.embed_requested():
embed = Embed(
title="DNS Query Result",
color=await ctx.embed_color(),
timestamp=message_data['response_time']
)
embed.add_field(name="Response Address", value=message_data['response_address'], inline=True)
embed.add_field(name="Response Port", value=message_data['response_port'], inline=True)
embed.add_field(name="Query Address", value=message_data['query_address'], inline=True)
embed.add_field(name="Query Port", value=message_data['query_port'], inline=True)
embed.add_field(name="Status", value=response_data['status'], inline=True)
embed.add_field(name="Flags", value=response_data['flags'], inline=True)
if response_data.get('status') != 'NOERROR':
embed.colour = Color.red()
embed.description = cf.error("Dig query did not return `NOERROR` status.")
questions = []
answers = []
authorities = []
for m in data:
response = m['message']['response_message_data']
if 'QUESTION_SECTION' in response:
for question in response['QUESTION_SECTION']:
if question not in questions:
questions.append(question)
if 'ANSWER_SECTION' in response:
for answer in response['ANSWER_SECTION']:
if answer not in answers:
answers.append(answer)
if 'AUTHORITY_SECTION' in response:
for authority in response['AUTHORITY_SECTION']:
if authority not in authorities:
authorities.append(authority)
if questions:
question_section = "\n".join(questions)
embed.add_field(name="Question Section", value=f"{cf.box(text=question_section, lang='prolog')}", inline=False)
if answers:
answer_section = "\n".join(answers)
if len(answer_section) > 1024:
embed.description = cf.warning("Answer section is too long to fit within embed field, falling back to description.") + cf.box(answer_section)
else:
embed.add_field(name="Answer Section", value=f"{cf.box(text=answer_section, lang='prolog')}", inline=False)
if authorities:
authority_section = "\n".join(authorities)
embed.add_field(name="Authority Section", value=f"{cf.box(text=authority_section, lang='prolog')}", inline=False)
await ctx.send(embed=embed)
else:
await ctx.send(content=cf.box(text=stdout, lang='yaml'))
except (FileNotFoundError):
try:
ns_process = await asyncio.create_subprocess_exec('nslookup', name, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
ns_stdout, ns_stderr = await ns_process.communicate()
if ns_stderr:
await ctx.maybe_send_embed(message="An error was encountered!\n" + cf.box(text=ns_stderr.decode()))
else:
warning = cf.warning("`dig` is not installed! Defaulting to `nslookup`.\nThis command provides more information when `dig` is installed on the system.\n")
if await ctx.embed_requested():
embed = Embed(
title="DNS Query Result",
color=await ctx.embed_color(),
timestamp=ctx.message.created_at
)
embed.description = warning + cf.box(text=ns_stdout.decode())
await ctx.send(embed=embed)
else:
await ctx.send(content = warning + cf.box(text=ns_stdout.decode()))
except (FileNotFoundError):
await ctx.maybe_send_embed(message=cf.error("Neither `dig` nor `nslookup` are installed on the system. Unable to resolve DNS query."))
@commands.command()
async def rfc(self, ctx: commands.Context, number: int) -> None:
"""Retrieve the text of an RFC document.
This command uses the [RFC Editor website](https://www.rfc-editor.org/) to fetch the text of an RFC document.
A [Request for Comments (RFC)](https://en.wikipedia.org/wiki/Request_for_Comments) is a publication in a series from the principal technical development and standards-setting bodies for the [Internet](https://en.wikipedia.org/wiki/Internet), most prominently the [Internet Engineering Task Force](https://en.wikipedia.org/wiki/Internet_Engineering_Task_Force). An RFC is authored by individuals or groups of engineers and [computer scientists](https://en.wikipedia.org/wiki/Computer_scientist) in the form of a [memorandum](https://en.wikipedia.org/wiki/Memorandum) describing methods, behaviors, research, or innovations applicable to the working of the Internet and Internet-connected systems. It is submitted either for [peer review](https://en.wikipedia.org/wiki/Peer_review) or to convey new concepts, information, or, occasionally, engineering humor.""" # noqa: E501
url = f"https://www.rfc-editor.org/rfc/rfc{number}.html"
datatracker_url = f"https://datatracker.ietf.org/doc/rfc{number}"
async with aiohttp.ClientSession() as session:
async with session.get(url=url) as response:
if response.status == 200:
html = await response.text()
soup = BeautifulSoup(html, 'html.parser')
pre_tags = soup.find_all('pre')
content: list[Embed | str] = []
for pre_tag in pre_tags:
text = format_rfc_text(md(pre_tag), number)
if len(text) > 4096:
pagified_text = cf.pagify(text, delims=["\n\n"], page_length=4096)
for page in pagified_text:
if await ctx.embed_requested():
embed = Embed(
title=f"RFC Document {number}",
url=datatracker_url,
description=page,
color=await ctx.embed_color()
)
content.append(embed)
else:
content.append(page)
else:
if await ctx.embed_requested():
embed = Embed(
title=f"RFC Document {number}",
url=datatracker_url,
description=text,
color=await ctx.embed_color()
)
content.append(embed)
else:
content.append(text)
if await ctx.embed_requested():
for embed in content:
embed.set_footer(text=f"Page {content.index(embed) + 1}/{len(content)}")
await SimpleMenu(pages=content, disable_after_timeout=True, timeout=300).start(ctx)
else:
await ctx.maybe_send_embed(content=cf.error(f"An error occurred while fetching RFC {number}. Status code: {response.status}."))