WIP: Add TTS cog #18
42 changed files with 7152 additions and 1073 deletions
|
@ -10,7 +10,7 @@ Aurora is a fully-featured moderation system. It is heavily inspired by Galactic
|
|||
## Installation
|
||||
|
||||
```bash
|
||||
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
||||
[p]cog install seacogs aurora
|
||||
[p]cog load aurora
|
||||
```
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
# Backup
|
||||
|
||||
Backup allows you to export a JSON list of all of your installed repositories and cogs, then reimport them and automatically reinstall/reload the cogs.
|
||||
Backup allows you to export a JSON list of all of your installed repositories and cogs, then reimport them and automatically reinstall the cogs.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
||||
[p]cog install seacogs backup
|
||||
[p]cog load backup
|
||||
```
|
||||
|
||||
## Version Compatibility
|
||||
|
||||
As of commit [1edb08a](https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs/commit/1edb08a1271f12098ca0bed11a735f7162cedd14), the Backup cog no longer supports Red versions older than 3.5.6. If you want to use the cog on an earlier version (3.5.0 - 3.5.5), install the cog pinned to this commit: `43464db6a7c51bc69282b1ae3dc507a4aae851de`.
|
||||
As of commit [1edb08a](https://www.coastalcommits.com/cswimr/SeaCogs/commit/1edb08a1271f12098ca0bed11a735f7162cedd14), the Backup cog no longer supports Red versions older than 3.5.6. If you want to use the cog on an earlier version (3.5.0 - 3.5.5), install the cog pinned to this commit: `43464db6a7c51bc69282b1ae3dc507a4aae851de`.
|
||||
|
||||
```bash
|
||||
[p]cog installversion sea-cogs 43464db6a7c51bc69282b1ae3dc507a4aae851de backup
|
||||
|
|
|
@ -6,7 +6,7 @@ This cog does require an api key to work.
|
|||
## Installation
|
||||
|
||||
```bash
|
||||
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
||||
[p]cog install seacogs bible
|
||||
[p]cog load bible
|
||||
```
|
||||
|
|
19
.docs/emojiinfo.md
Normal file
19
.docs/emojiinfo.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
# EmojiInfo
|
||||
|
||||
EmojiInfo allows you to retrieve information about an emoji.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
||||
[p]cog install seacogs emojiinfo
|
||||
[p]cog load emojiinfo
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
### emoji
|
||||
|
||||
- Usage: `[p]emoji <emoji> [ephemeral]`
|
||||
|
||||
Retrieve information about the provided emoji. If `ephemeral` is provided and the command is used as a slash command, the response will be sent as an ephemeral message.
|
|
@ -5,7 +5,7 @@ Nerdify allows you to nerdify other people's text.
|
|||
## Installation
|
||||
|
||||
```bash
|
||||
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
||||
[p]cog install seacogs nerdify
|
||||
[p]cog load nerdify
|
||||
```
|
||||
|
|
|
@ -28,7 +28,7 @@ The Downloader cog allows you to add Git repositories to your bot in order to do
|
|||
Now, use Downloader to add my repository to your bot:
|
||||
|
||||
```
|
||||
[p]repo add sea-cogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||
[p]repo add sea-cogs https://www.coastalcommits.com/cswimr/SeaCogs
|
||||
```
|
||||
|
||||
Now, install the Pterodactyl cog:
|
||||
|
|
|
@ -10,7 +10,7 @@ Pterodactyl allows for connecting to a Pterodactyl server through websockets. It
|
|||
## Installation
|
||||
|
||||
```bash
|
||||
[p]repo add seacogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||
[p]repo add seacogs https://www.coastalcommits.com/cswimr/SeaCogs
|
||||
[p]cog install seacogs pterodactyl
|
||||
[p]cog load aurora
|
||||
```
|
||||
|
|
|
@ -64,7 +64,7 @@ Red is quite a large bot, so I'll focus on the specifics of getting the bot work
|
|||
```
|
||||
2. Add my repository to the bot
|
||||
```bash
|
||||
[p]repo add sea-cogs https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||
[p]repo add sea-cogs https://www.coastalcommits.com/cswimr/SeaCogs
|
||||
```
|
||||
3. Install and load the Pterodactyl cog
|
||||
```bash
|
||||
|
|
|
@ -13,7 +13,7 @@ body:
|
|||
attributes:
|
||||
label: Please confirm that;
|
||||
options:
|
||||
- label: I have checked that this bug does not already have an opened/closed [issue](https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs/issues) or [pull request](https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs/pulls) associated with it.
|
||||
- label: I have checked that this bug does not already have an opened/closed [issue](https://www.coastalcommits.com/cswimr/SeaCogs/issues) or [pull request](https://www.coastalcommits.com/cswimr/SeaCogs/pulls) associated with it.
|
||||
required: true
|
||||
- label: I have checked that I am on the latest version of [Red-DiscordBot](https://github.com/CogCreators/Red-DiscordBot), and SeaCogs.
|
||||
required: true
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
|
||||
<!-- Create a new issue, if it doesn't exist yet -->
|
||||
|
||||
- [ ] By submitting this pull request, I permit SeaswimmerTheFsh to license my work under
|
||||
the [Mozilla Public License Version 2.0](https://www.coastalcommits.com/SeaswimmerTheFsh/SeaCogs/src/branch/main/LICENSE).
|
||||
- [ ] By submitting this pull request, I permit cswimr to license my work under
|
||||
the [Mozilla Public License Version 2.0](https://www.coastalcommits.com/cswimr/SeaCogs/src/branch/main/LICENSE).
|
||||
|
|
|
@ -18,4 +18,5 @@
|
|||
import-self,
|
||||
relative-beyond-top-level,
|
||||
too-many-instance-attributes,
|
||||
duplicate-code
|
||||
duplicate-code,
|
||||
too-many-nested-blocks
|
||||
|
|
|
@ -8,7 +8,7 @@ on:
|
|||
jobs:
|
||||
Lint Code (Ruff & Pylint):
|
||||
runs-on: docker
|
||||
container: www.coastalcommits.com/seaswimmerthefsh/actionscontainers-seacogs:latest
|
||||
container: www.coastalcommits.com/cswimr/actions:seacogs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
@ -25,7 +25,7 @@ jobs:
|
|||
|
||||
Build Documentation (MkDocs):
|
||||
runs-on: docker
|
||||
container: www.coastalcommits.com/seaswimmerthefsh/actionscontainers-seacogs:latest
|
||||
container: www.coastalcommits.com/cswimr/actions:seacogs
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
@ -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"
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ My assorted cogs for Red-DiscordBot.
|
|||
To get started with a development environment, first clone this repository.
|
||||
|
||||
```sh
|
||||
git clone https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs.git
|
||||
git clone https://coastalcommits.com/cswimr/SeaCogs.git
|
||||
```
|
||||
|
||||
Then, install Poetry.
|
||||
|
|
|
@ -9,14 +9,15 @@ import discord
|
|||
from red_commons.logging import getLogger
|
||||
from redbot.core import commands
|
||||
from redbot.core.bot import Config, Red
|
||||
from redbot.core.utils.chat_formatting import humanize_list
|
||||
from redbot.core.utils.chat_formatting import bold, humanize_list
|
||||
|
||||
|
||||
class AntiPolls(commands.Cog):
|
||||
"""AntiPolls deletes messages that contain polls, with a configurable per-guild role and channel whitelist and support for default Discord permissions (Manage Messages)."""
|
||||
|
||||
__author__ = ["SeaswimmerTheFsh"]
|
||||
__version__ = "1.0.0"
|
||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
||||
__version__ = "1.0.1"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/antipolls/"
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
|
@ -38,9 +39,9 @@ class AntiPolls(commands.Cog):
|
|||
n = "\n" if "\n\n" not in pre_processed else ""
|
||||
text = [
|
||||
f"{pre_processed}{n}",
|
||||
f"Cog Version: **{self.__version__}**",
|
||||
f"Author: {humanize_list(self.__author__)}",
|
||||
f"Documentation: {self.__documentation__}",
|
||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
||||
f"{bold('Documentation:')} {self.__documentation__}",
|
||||
]
|
||||
return "\n".join(text)
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||
"install_msg" : "Thank you for installing AntiPolls!\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).",
|
||||
"author" : ["cswimr"],
|
||||
"install_msg" : "Thank you for installing AntiPolls!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
||||
"name" : "AntiPolls",
|
||||
"short" : "AntiPolls deletes messages that contain polls.",
|
||||
"description" : "AntiPolls deletes messages that contain polls, with a configurable per-guild role and channel whitelist and support for default Discord permissions (Manage Messages).",
|
||||
"end_user_data_statement" : "This cog does not store any user data.",
|
||||
"hidden": false,
|
||||
"hidden": true,
|
||||
"disabled": false,
|
||||
"min_bot_version": "3.5.0",
|
||||
"min_python_version": [3, 10, 0],
|
||||
|
|
|
@ -19,8 +19,7 @@ from redbot.core import app_commands, commands, data_manager
|
|||
from redbot.core.app_commands import Choice
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.commands.converter import parse_relativedelta, parse_timedelta
|
||||
from redbot.core.utils.chat_formatting import (box, error, humanize_list,
|
||||
humanize_timedelta, warning)
|
||||
from redbot.core.utils.chat_formatting import box, error, humanize_list, humanize_timedelta, warning
|
||||
|
||||
from aurora.importers.aurora import ImportAuroraView
|
||||
from aurora.importers.galacticbot import ImportGalacticBotView
|
||||
|
@ -29,19 +28,10 @@ from aurora.menus.guild import Guild
|
|||
from aurora.menus.immune import Immune
|
||||
from aurora.menus.overrides import Overrides
|
||||
from aurora.utilities.config import config, register_config
|
||||
from aurora.utilities.database import (connect, create_guild_table, fetch_case,
|
||||
mysql_log)
|
||||
from aurora.utilities.factory import (addrole_embed, case_factory,
|
||||
changes_factory, evidenceformat_factory,
|
||||
guild_embed, immune_embed,
|
||||
message_factory, overrides_embed)
|
||||
from aurora.utilities.database import connect, create_guild_table, fetch_case, mysql_log
|
||||
from aurora.utilities.factory import addrole_embed, case_factory, changes_factory, evidenceformat_factory, guild_embed, immune_embed, message_factory, overrides_embed
|
||||
from aurora.utilities.logger import logger
|
||||
from aurora.utilities.utils import (check_moddable, check_permissions,
|
||||
convert_timedelta_to_str,
|
||||
fetch_channel_dict, fetch_user_dict,
|
||||
generate_dict, get_footer_image, log,
|
||||
send_evidenceformat,
|
||||
timedelta_from_relativedelta)
|
||||
from aurora.utilities.utils import check_moddable, check_permissions, convert_timedelta_to_str, fetch_channel_dict, fetch_user_dict, generate_dict, get_footer_image, log, send_evidenceformat, timedelta_from_relativedelta
|
||||
|
||||
|
||||
class Aurora(commands.Cog):
|
||||
|
@ -49,8 +39,8 @@ class Aurora(commands.Cog):
|
|||
It is heavily inspired by GalacticBot, and is designed to be a more user-friendly alternative to Red's core Mod cogs.
|
||||
This cog stores all of its data in an SQLite database."""
|
||||
|
||||
__author__ = ["SeaswimmerTheFsh"]
|
||||
__version__ = "2.1.2"
|
||||
__author__ = ["cswimr"]
|
||||
__version__ = "2.1.3"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/aurora/"
|
||||
|
||||
async def red_delete_data_for_user(self, *, requester, user_id: int):
|
||||
|
|
|
@ -43,7 +43,7 @@ class ImportAuroraView(ui.View):
|
|||
file = await self.ctx.message.attachments[0].read()
|
||||
data: list[dict] = sorted(json.loads(file), key=lambda x: x["moderation_id"])
|
||||
|
||||
user_mod_types = ["NOTE", "WARN", "MUTE", "UNMUTE", "KICK", "BAN", "UNBAN"]
|
||||
user_mod_types = ["NOTE", "WARN", "ADDROLE", "REMOVEROLE", "MUTE", "UNMUTE", "KICK", "TEMPBAN", "BAN", "UNBAN"]
|
||||
|
||||
channel_mod_types = ["SLOWMODE", "LOCKDOWN"]
|
||||
|
||||
|
@ -58,6 +58,8 @@ class ImportAuroraView(ui.View):
|
|||
case["target_type"] = "USER"
|
||||
elif case["moderation_type"] in channel_mod_types:
|
||||
case["target_type"] = "CHANNEL"
|
||||
else:
|
||||
case["target_type"] = "USER"
|
||||
|
||||
if "role_id" not in case or not case["role_id"]:
|
||||
case["role_id"] = 0
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||
"install_msg" : "Thank you for installing Aurora!\nMost of this cog's functionality requires enabling slash commands.\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).",
|
||||
"author" : ["cswimr"],
|
||||
"install_msg" : "Thank you for installing Aurora!\nMost of this cog's functionality requires enabling slash commands.\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
||||
"name" : "Aurora",
|
||||
"short" : "A full replacement for Red's core Mod cogs.",
|
||||
"description" : "Aurora is a fully-featured moderation system. It is heavily inspired by GalacticBot, and is designed to be a more user-friendly alternative to Red's core Mod cogs. This cog stores all of its data in an SQLite database.",
|
||||
|
|
|
@ -381,6 +381,10 @@ async def evidenceformat_factory(interaction: Interaction, case_dict: dict) -> s
|
|||
|
||||
content = f"Case: {case_dict['moderation_id']:,} ({str.title(case_dict['moderation_type'])})\nTarget: {target_name} ({target_user['id']})\nModerator: {moderator_name} ({moderator_user['id']})"
|
||||
|
||||
if case_dict["role_id"] != "0":
|
||||
role = interaction.guild.get_role(int(case_dict["role_id"]))
|
||||
content += "\nRole: " + (role.name if role is not None else case_dict["role_id"])
|
||||
|
||||
if case_dict["duration"] != "NULL":
|
||||
hours, minutes, seconds = map(int, case_dict["duration"].split(":"))
|
||||
td = timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
||||
|
|
|
@ -14,15 +14,16 @@ from redbot.cogs.downloader import errors
|
|||
from redbot.cogs.downloader.converters import InstalledCog
|
||||
from redbot.core import commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.utils.chat_formatting import error, humanize_list, text_to_file
|
||||
from redbot.core.utils.chat_formatting import bold, error, humanize_list, text_to_file
|
||||
|
||||
|
||||
# pylint: disable=protected-access
|
||||
class Backup(commands.Cog):
|
||||
"""A utility to make reinstalling repositories and cogs after migrating the bot far easier."""
|
||||
|
||||
__author__ = ["SeaswimmerTheFsh"]
|
||||
__version__ = "1.1.0"
|
||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
||||
__version__ = "1.1.1"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/backup/"
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
|
@ -35,9 +36,9 @@ class Backup(commands.Cog):
|
|||
n = "\n" if "\n\n" not in pre_processed else ""
|
||||
text = [
|
||||
f"{pre_processed}{n}",
|
||||
f"Cog Version: **{self.__version__}**",
|
||||
f"Author: {humanize_list(self.__author__)}",
|
||||
f"Documentation: {self.__documentation__}",
|
||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
||||
f"{bold('Documentation:')} {self.__documentation__}",
|
||||
]
|
||||
return "\n".join(text)
|
||||
|
||||
|
@ -100,7 +101,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
|
||||
|
||||
|
@ -196,7 +197,7 @@ class Backup(commands.Cog):
|
|||
cog_modules = []
|
||||
for cog in cogs:
|
||||
# If you're forking this cog, make sure to change these strings!
|
||||
if cog["name"] == "backup" and "SeaswimmerTheFsh/SeaCogs" in url:
|
||||
if cog["name"] == "backup" and "cswimr/SeaCogs" in url:
|
||||
continue
|
||||
try:
|
||||
cog_module = await InstalledCog.convert(ctx, cog["name"])
|
||||
|
@ -232,7 +233,7 @@ class Backup(commands.Cog):
|
|||
commit = None
|
||||
|
||||
# If you're forking this cog, make sure to change these strings!
|
||||
if cog_name == "backup" and "SeaswimmerTheFsh/SeaCogs" in url:
|
||||
if cog_name == "backup" and "cswimr/SeaCogs" in url:
|
||||
continue
|
||||
|
||||
async with repository.checkout(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||
"install_msg" : "Thank you for installing Backup!\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).",
|
||||
"author" : ["cswimr"],
|
||||
"install_msg" : "Thank you for installing Backup!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
||||
"name" : "Backup",
|
||||
"short" : "A utility to make reinstalling repositories and cogs after migrating the bot far easier.",
|
||||
"description" : "A utility to make reinstalling repositories and cogs after migrating the bot far easier.",
|
||||
|
@ -8,7 +8,7 @@
|
|||
"hidden": false,
|
||||
"disabled": false,
|
||||
"min_bot_version": "3.5.6",
|
||||
"max_bot_version": "3.5.9",
|
||||
"max_bot_version": "3.5.13",
|
||||
"min_python_version": [3, 9, 0],
|
||||
"tags": [
|
||||
"utility",
|
||||
|
|
|
@ -15,7 +15,7 @@ from PIL import Image
|
|||
from red_commons.logging import getLogger
|
||||
from redbot.core import Config, commands, data_manager
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.utils.chat_formatting import error, humanize_list
|
||||
from redbot.core.utils.chat_formatting import bold, error, humanize_list
|
||||
|
||||
import bible.errors
|
||||
from bible.models import Version
|
||||
|
@ -24,9 +24,10 @@ from bible.models import Version
|
|||
class Bible(commands.Cog):
|
||||
"""Retrieve Bible verses from the API.bible API."""
|
||||
|
||||
__author__ = ["SeaswimmerTheFsh"]
|
||||
__version__ = "1.1.0"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/bible/"
|
||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
||||
__version__ = "1.1.1"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/pterodactyl/"
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
super().__init__()
|
||||
|
@ -44,12 +45,13 @@ class Bible(commands.Cog):
|
|||
n = "\n" if "\n\n" not in pre_processed else ""
|
||||
text = [
|
||||
f"{pre_processed}{n}",
|
||||
f"Cog Version: **{self.__version__}**",
|
||||
f"Author: {humanize_list(self.__author__)}",
|
||||
f"Documentation: {self.__documentation__}",
|
||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
||||
f"{bold('Documentation:')} {self.__documentation__}",
|
||||
]
|
||||
return "\n".join(text)
|
||||
|
||||
|
||||
def get_icon(self, color: Colour) -> File:
|
||||
"""Get the docs.api.bible favicon with a given color."""
|
||||
image_path = data_manager.bundled_data_path(self) / "api.bible-logo.png"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||
"install_msg" : "Thank you for installing Bible!\nThis cog requires setting an API key for API.Bible. Please read the [documentation](https://seacogs.coastalcommits.com/bible/#setup) for more information.\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).",
|
||||
"author" : ["cswimr"],
|
||||
"install_msg" : "Thank you for installing Bible!\nThis cog requires setting an API key for API.Bible. Please read the [documentation](https://seacogs.coastalcommits.com/bible/#setup) for more information.\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
||||
"name" : "Bible",
|
||||
"short" : "Retrieve Bible verses from API.Bible.",
|
||||
"description" : "Retrieve Bible verses from the API.Bible API. This cog requires an API.Bible api key.",
|
||||
|
|
5
emojiinfo/__init__.py
Normal file
5
emojiinfo/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from .emojiinfo import EmojiInfo
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(EmojiInfo(bot))
|
5309
emojiinfo/data/emojis.json
Normal file
5309
emojiinfo/data/emojis.json
Normal file
File diff suppressed because it is too large
Load diff
130
emojiinfo/emojiinfo.py
Normal file
130
emojiinfo/emojiinfo.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
import io
|
||||
|
||||
import aiohttp
|
||||
import discord
|
||||
from colorthief import ColorThief
|
||||
from red_commons.logging import RedTraceLogger, getLogger
|
||||
from redbot.core import app_commands, commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.utils.chat_formatting import bold, humanize_list
|
||||
|
||||
from .model import PartialEmoji
|
||||
|
||||
|
||||
class EmojiInfo(commands.Cog):
|
||||
"""Retrieve information about emojis."""
|
||||
|
||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
||||
__version__ = "1.0.1"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/emojiinfo/"
|
||||
|
||||
def __init__(self, bot: Red) -> None:
|
||||
super().__init__()
|
||||
self.bot: Red = bot
|
||||
self.logger: RedTraceLogger = getLogger(name="red.SeaCogs.Emoji")
|
||||
|
||||
def format_help_for_context(self, ctx: commands.Context) -> str:
|
||||
pre_processed = super().format_help_for_context(ctx) or ""
|
||||
n = "\n" if "\n\n" not in pre_processed else ""
|
||||
text = [
|
||||
f"{pre_processed}{n}",
|
||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
||||
f"{bold('Documentation:')} {self.__documentation__}",
|
||||
]
|
||||
return "\n".join(text)
|
||||
|
||||
|
||||
async def fetch_twemoji(self, unicode_emoji) -> str:
|
||||
base_url = "https://cdn.jsdelivr.net/gh/jdecked/twemoji@latest/assets/72x72/"
|
||||
emoji_codepoint = "-".join([hex(ord(char))[2:] for char in unicode_emoji])
|
||||
segments = emoji_codepoint.split("-")
|
||||
valid_segments = [seg for seg in segments if len(seg) >= 4]
|
||||
emoji_url = f"{base_url}{valid_segments[0]}.png"
|
||||
return emoji_url
|
||||
|
||||
async def fetch_primary_color(self, emoji_url: str) -> discord.Color | None:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(emoji_url) as response:
|
||||
if response.status != 200:
|
||||
return None
|
||||
image = await response.read()
|
||||
dominant_color = ColorThief(io.BytesIO(image)).get_color(quality=1)
|
||||
color = discord.Color.from_rgb(*dominant_color)
|
||||
return color
|
||||
|
||||
async def get_emoji_info(self, emoji: PartialEmoji) -> tuple[str, str]:
|
||||
if emoji.is_unicode_emoji():
|
||||
try:
|
||||
emoji_url = await self.fetch_twemoji(unicode_emoji=emoji.name)
|
||||
except IndexError as e:
|
||||
raise e
|
||||
else:
|
||||
emoji_url = emoji.url
|
||||
|
||||
if emoji.id is not None:
|
||||
emoji_id = f"{bold('ID:')} `{emoji.id}`\n"
|
||||
markdown = f"`<{'a' if emoji.animated else ''}:{emoji.name}:{emoji.id}>`"
|
||||
name = f"{bold('Name:')} {emoji.name}\n"
|
||||
aliases = ""
|
||||
group = ""
|
||||
else:
|
||||
emoji_id = ""
|
||||
markdown = f"`{emoji}`"
|
||||
name = f"{bold('Name:')} {emoji.aliases.pop(0)}\n"
|
||||
aliases = f"{bold('Aliases:')} {', '.join(emoji.aliases)}\n" if emoji.aliases else ""
|
||||
group = f"{bold('Group:')} {emoji.group}\n"
|
||||
|
||||
return (
|
||||
f"{name}"
|
||||
f"{emoji_id}"
|
||||
f"{bold('Native:')} {emoji.is_unicode_emoji()}\n"
|
||||
f"{group}"
|
||||
f"{aliases}"
|
||||
f"{bold('Animated:')} {emoji.animated}\n"
|
||||
f"{bold('Markdown:')} {markdown}\n"
|
||||
f"{bold('URL:')} [Click Here]({emoji_url})"
|
||||
), emoji_url
|
||||
|
||||
@app_commands.command(name="emoji")
|
||||
@app_commands.describe(
|
||||
emoji="What emoji would you like to get information on?",
|
||||
ephemeral="Would you like the response to be hidden?"
|
||||
)
|
||||
async def emoji_slash(self, interaction: discord.Interaction, emoji: str, ephemeral: bool = True) -> None:
|
||||
"""Retrieve information about an emoji."""
|
||||
await interaction.response.defer(ephemeral=ephemeral)
|
||||
|
||||
try:
|
||||
emoji: PartialEmoji = PartialEmoji.from_str(self, value=emoji)
|
||||
string, emoji_url, = await self.get_emoji_info(emoji)
|
||||
self.logger.verbose(f"Emoji:\n{string}")
|
||||
except (IndexError, UnboundLocalError):
|
||||
return await interaction.followup.send("Please provide a valid emoji!")
|
||||
|
||||
if await self.bot.embed_requested(channel=interaction.channel):
|
||||
embed = embed = discord.Embed(title="Emoji Information", description=string, color = await self.fetch_primary_color(emoji_url) or await self.bot.get_embed_color(interaction.channel))
|
||||
embed.set_thumbnail(url=emoji_url)
|
||||
|
||||
await interaction.followup.send(embed=embed)
|
||||
else:
|
||||
await interaction.followup.send(content=string)
|
||||
|
||||
@commands.command(name="emoji")
|
||||
async def emoji(self, ctx: commands.Context, *, emoji: str) -> None:
|
||||
"""Retrieve information about an emoji."""
|
||||
try:
|
||||
emoji: PartialEmoji = PartialEmoji.from_str(self, value=emoji)
|
||||
string, emoji_url, = await self.get_emoji_info(emoji)
|
||||
self.logger.verbose(f"Emoji:\n{string}")
|
||||
except (IndexError, UnboundLocalError):
|
||||
return await ctx.send("Please provide a valid emoji!")
|
||||
|
||||
if await ctx.embed_requested():
|
||||
embed = embed = discord.Embed(title="Emoji Information", description=string, color = await self.fetch_primary_color(emoji_url) or await ctx.embed_color)
|
||||
embed.set_thumbnail(url=emoji_url)
|
||||
|
||||
await ctx.send(embed=embed)
|
||||
else:
|
||||
await ctx.send(content=string)
|
16
emojiinfo/info.json
Normal file
16
emojiinfo/info.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"author" : ["cswimr"],
|
||||
"install_msg" : "Thank you for installing Emoji!",
|
||||
"name" : "Emoji",
|
||||
"short" : "Retrieve information about emojis.",
|
||||
"description" : "Retrieve information about emojis.",
|
||||
"end_user_data_statement" : "This cog does not store end user data.",
|
||||
"hidden": false,
|
||||
"disabled": false,
|
||||
"min_bot_version": "3.5.0",
|
||||
"min_python_version": [3, 10, 0],
|
||||
"requirements": ["colorthief"],
|
||||
"tags": [
|
||||
"utility"
|
||||
]
|
||||
}
|
91
emojiinfo/model.py
Normal file
91
emojiinfo/model.py
Normal file
|
@ -0,0 +1,91 @@
|
|||
import json
|
||||
|
||||
import discord
|
||||
from redbot.core import commands, data_manager
|
||||
|
||||
|
||||
class PartialEmoji(discord.PartialEmoji):
|
||||
"""Represents a "partial" emoji. Subclasses `discord.PartialEmoji`
|
||||
|
||||
.. container:: operations
|
||||
|
||||
.. describe:: x == y
|
||||
|
||||
Checks if two emoji are the same.
|
||||
|
||||
.. describe:: x != y
|
||||
|
||||
Checks if two emoji are not the same.
|
||||
|
||||
.. describe:: hash(x)
|
||||
|
||||
Return the emoji's hash.
|
||||
|
||||
.. describe:: str(x)
|
||||
|
||||
Returns the emoji rendered for discord.
|
||||
|
||||
Attributes
|
||||
-----------
|
||||
name: Optional[:class:`str`]
|
||||
The custom emoji name, if applicable, or the unicode codepoint
|
||||
of the non-custom emoji. This can be ``None`` if the emoji
|
||||
got deleted (e.g. removing a reaction with a deleted emoji).
|
||||
animated: :class:`bool`
|
||||
Whether the emoji is animated or not.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of the custom emoji, if applicable.
|
||||
group: Optional[:class:`str`]
|
||||
The group name of the emoji if it is a native emoji.
|
||||
"""
|
||||
|
||||
def __init__(self, *, name: str, animated: bool = False, id: int | None = None, group: str | None = None, aliases: list | None = None) -> None: # pylint: disable=redefined-builtin
|
||||
super().__init__(name=name, animated=animated, id=id)
|
||||
self.group = group
|
||||
self.aliases = aliases
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, coginstance: commands.Cog, value: str) -> "PartialEmoji":
|
||||
"""Converts a Discord string representation of an emoji to a :class:`PartialEmoji`.
|
||||
|
||||
The formats accepted are:
|
||||
|
||||
- ``a:name:id``
|
||||
- ``<a:name:id>``
|
||||
- ``name:id``
|
||||
- ``<:name:id>``
|
||||
|
||||
If the format does not match then it is assumed to be a unicode emoji.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
------------
|
||||
value: :class:`str`
|
||||
The string representation of an emoji.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`PartialEmoji`
|
||||
The partial emoji from this string.
|
||||
"""
|
||||
match = cls._CUSTOM_EMOJI_RE.match(value)
|
||||
if match is not None:
|
||||
groups = match.groupdict()
|
||||
animated = bool(groups['animated'])
|
||||
emoji_id = int(groups['id'])
|
||||
name = groups['name']
|
||||
return cls(name=name, animated=animated, id=emoji_id)
|
||||
|
||||
path: data_manager.Path = data_manager.bundled_data_path(coginstance) / "emojis.json"
|
||||
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:
|
||||
emoji_group = dict_name
|
||||
if k not in emoji_aliases:
|
||||
emoji_aliases.append(k)
|
||||
return cls(name=value, animated=False, id=None, group=emoji_group, aliases=emoji_aliases)
|
10
info.json
10
info.json
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"author": [
|
||||
"SeaswimmerTheFsh (seasw.)"
|
||||
"cswimr"
|
||||
],
|
||||
"install_msg": "Thanks for installing my repo!\n\nIf you have any issues with any of the cogs, please create an issue [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs/issues) or join my [Discord Server](https://discord.gg/eMUMe77Yb8 ).",
|
||||
"name": "SeaCogs",
|
||||
"short": "Various cogs for Red, by SeaswimmerTheFsh (seasw.)",
|
||||
"description": "Various cogs for Red, by SeaswimmerTheFsh (seasw.)"
|
||||
"install_msg": "Thanks for installing my repo!\n\nIf you have any issues with any of the cogs, please create an issue [here](https://coastalcommits.com/cswimr/SeaCogs/issues) or join my [Discord Server](https://discord.gg/eMUMe77Yb8 ).",
|
||||
"index_name": "sea-cogs",
|
||||
"short": "Various cogs for Red, by cswimr",
|
||||
"description": "Various cogs for Red, by cswimr"
|
||||
}
|
||||
|
|
11
mkdocs.yml
11
mkdocs.yml
|
@ -1,12 +1,12 @@
|
|||
site_name: SeaCogs Documentation
|
||||
site_url: !ENV [SITE_URL, 'https://seacogs.coastalcommits.com']
|
||||
repo_name: CoastalCommits
|
||||
repo_url: https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs
|
||||
repo_url: https://coastalcommits.com/cswimr/SeaCogs
|
||||
edit_uri: !ENV [EDIT_URI, 'src/branch/main/.docs']
|
||||
copyright: Copyright © 2023-2024, SeaswimmerTheFsh
|
||||
copyright: Copyright © 2023-2024, cswimr
|
||||
docs_dir: .docs
|
||||
|
||||
site_author: SeaswimmerTheFsh
|
||||
site_author: cswimr
|
||||
site_description: Documentation for my Red-DiscordBot Cogs.
|
||||
|
||||
nav:
|
||||
|
@ -18,6 +18,7 @@ nav:
|
|||
- Configuration: aurora/configuration.md
|
||||
- Bible: bible.md
|
||||
- Backup: backup.md
|
||||
- EmojiInfo: emojiinfo.md
|
||||
- Nerdify: nerdify.md
|
||||
- Pterodactyl:
|
||||
- pterodactyl/index.md
|
||||
|
@ -29,7 +30,7 @@ nav:
|
|||
plugins:
|
||||
- git-authors
|
||||
- search
|
||||
#- social
|
||||
- social
|
||||
- git-revision-date-localized:
|
||||
enable_creation_date: true
|
||||
type: timeago
|
||||
|
@ -112,3 +113,5 @@ watch:
|
|||
- ./bible
|
||||
- ./nerdify
|
||||
- ./pterodactyl
|
||||
- ./emojiinfo
|
||||
- ./antipolls
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||
"install_msg" : "Thank you for installing Nerdify!\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs). Based off of PhasecoreX's [UwU](<https://github.com/PhasecoreX/PCXCogs/tree/master/uwu>) cog.",
|
||||
"author" : ["cswimr"],
|
||||
"install_msg" : "Thank you for installing Nerdify!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs). Based off of PhasecoreX's [UwU](<https://github.com/PhasecoreX/PCXCogs/tree/master/uwu>) cog.",
|
||||
"name" : "Nerdify",
|
||||
"short" : "Nerdify your text!",
|
||||
"description" : "Nerdify your text!",
|
||||
|
|
|
@ -12,13 +12,15 @@ from typing import Any, Optional, Union
|
|||
import discord
|
||||
from redbot.core import commands
|
||||
from redbot.core.utils import chat_formatting, common_filters
|
||||
from redbot.core.utils.chat_formatting import bold, humanize_list
|
||||
|
||||
|
||||
class Nerdify(commands.Cog):
|
||||
"""Nerdify your text."""
|
||||
|
||||
__author__ = ["SeaswimmerTheFsh"]
|
||||
__version__ = "1.3.4"
|
||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
||||
__version__ = "1.3.5"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/nerdify/"
|
||||
|
||||
def __init__(self, bot):
|
||||
|
@ -29,12 +31,13 @@ class Nerdify(commands.Cog):
|
|||
n = "\n" if "\n\n" not in pre_processed else ""
|
||||
text = [
|
||||
f"{pre_processed}{n}",
|
||||
f"Cog Version: **{self.__version__}**",
|
||||
f"Author: {chat_formatting.humanize_list(self.__author__)}",
|
||||
f"Documentation: {self.__documentation__}"
|
||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
||||
f"{bold('Documentation:')} {self.__documentation__}",
|
||||
]
|
||||
return "\n".join(text)
|
||||
|
||||
|
||||
@commands.command(aliases=["nerd"])
|
||||
async def nerdify(
|
||||
self, ctx: commands.Context, *, text: Optional[str] = None
|
||||
|
|
2037
poetry.lock
generated
2037
poetry.lock
generated
File diff suppressed because it is too large
Load diff
BIN
pterodactyl/data/unknown.png
Normal file
BIN
pterodactyl/data/unknown.png
Normal file
Binary file not shown.
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"author" : ["SeaswimmerTheFsh (seasw.)"],
|
||||
"install_msg" : "Thank you for installing Pterodactyl!\nYou can find the source code of this cog [here](https://coastalcommits.com/SeaswimmerTheFsh/SeaCogs).\nDocumentation can be found [here](https://seacogs.coastalcommits.com/pterodactyl ).",
|
||||
"author" : ["cswimr"],
|
||||
"install_msg" : "Thank you for installing Pterodactyl!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).\nDocumentation can be found [here](https://seacogs.coastalcommits.com/pterodactyl ).",
|
||||
"name" : "Pterodactyl",
|
||||
"short" : "Interface with Pterodactyl through websockets.",
|
||||
"description" : "Interface with Pterodactyl through websockets.",
|
||||
|
@ -9,7 +9,7 @@
|
|||
"disabled": false,
|
||||
"min_bot_version": "3.5.0",
|
||||
"min_python_version": [3, 8, 0],
|
||||
"requirements": ["git+https://github.com/SeaswimmerTheFsh/pydactyl", "websockets"],
|
||||
"requirements": ["git+https://github.com/cswimr/pydactyl", "websockets"],
|
||||
"tags": [
|
||||
"pterodactyl",
|
||||
"minecraft",
|
||||
|
|
|
@ -9,7 +9,7 @@ from pydactyl import PterodactylClient
|
|||
from redbot.core import app_commands, commands
|
||||
from redbot.core.app_commands import Choice
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.utils.chat_formatting import box, error, humanize_list
|
||||
from redbot.core.utils.chat_formatting import bold, box, error, humanize_list
|
||||
from redbot.core.utils.views import ConfirmView
|
||||
|
||||
from pterodactyl import mcsrvstatus
|
||||
|
@ -20,8 +20,9 @@ from pterodactyl.logger import logger
|
|||
class Pterodactyl(commands.Cog):
|
||||
"""Pterodactyl allows you to manage your Pterodactyl Panel from Discord."""
|
||||
|
||||
__author__ = ["SeaswimmerTheFsh"]
|
||||
__version__ = "2.0.0"
|
||||
__author__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
||||
__version__ = "2.0.4"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/pterodactyl/"
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
|
@ -39,12 +40,29 @@ class Pterodactyl(commands.Cog):
|
|||
n = "\n" if "\n\n" not in pre_processed else ""
|
||||
text = [
|
||||
f"{pre_processed}{n}",
|
||||
f"Cog Version: **{self.__version__}**",
|
||||
f"Author: {humanize_list(self.__author__)}",
|
||||
f"Documentation: {self.__documentation__}",
|
||||
f"{bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
||||
f"{bold('Author:')} {humanize_list(self.__author__)}",
|
||||
f"{bold('Documentation:')} {self.__documentation__}",
|
||||
]
|
||||
return "\n".join(text)
|
||||
|
||||
async def cog_load(self) -> None:
|
||||
pterodactyl_keys = await self.bot.get_shared_api_tokens("pterodactyl")
|
||||
api_key = pterodactyl_keys.get("api_key")
|
||||
if api_key is None:
|
||||
self.task.cancel()
|
||||
raise ValueError("Pterodactyl API key not set. Please set it using `[p]set api`.")
|
||||
base_url = await config.base_url()
|
||||
if base_url is None:
|
||||
self.task.cancel()
|
||||
raise ValueError("Pterodactyl base URL not set. Please set it using `[p]pterodactyl config url`.")
|
||||
server_id = await config.server_id()
|
||||
if server_id is None:
|
||||
self.task.cancel()
|
||||
raise ValueError("Pterodactyl server ID not set. Please set it using `[p]pterodactyl config serverid`.")
|
||||
|
||||
self.client = PterodactylClient(base_url, api_key).client
|
||||
|
||||
async def cog_unload(self) -> None:
|
||||
self.update_topic.cancel()
|
||||
self.task.cancel()
|
||||
|
@ -175,65 +193,36 @@ class Pterodactyl(commands.Cog):
|
|||
|
||||
async def power(self, ctx: Union[discord.Interaction, commands.Context], action: str, action_ing: str, warning: str = '') -> None:
|
||||
if isinstance(ctx, discord.Interaction):
|
||||
author = ctx.user
|
||||
else:
|
||||
author = ctx.author
|
||||
ctx = await self.bot.get_context(ctx)
|
||||
|
||||
current_status = await config.current_status()
|
||||
|
||||
if current_status == action_ing:
|
||||
if isinstance(ctx, discord.Interaction):
|
||||
return await ctx.response.send_message(f"Server is already {action_ing}.", ephemeral=True)
|
||||
return await ctx.send(f"Server is already {action_ing}.")
|
||||
return await ctx.send(f"Server is already {action_ing}.", ephemeral=True)
|
||||
|
||||
if current_status in ["starting", "stopping"] and action != "kill":
|
||||
if isinstance(ctx, discord.Interaction):
|
||||
return await ctx.response.send_message("Another power action is already in progress.", ephemeral=True)
|
||||
return await ctx.send("Another power action is already in progress.")
|
||||
return await ctx.send("Another power action is already in progress.", ephemeral=True)
|
||||
|
||||
view = ConfirmView(author, disable_buttons=True)
|
||||
view = ConfirmView(ctx.author, disable_buttons=True)
|
||||
|
||||
if isinstance(ctx, discord.Interaction):
|
||||
await ctx.response.send_message(f"{warning}Are you sure you want to {action} the server?", view=view)
|
||||
else:
|
||||
message = await ctx.send(f"{warning}Are you sure you want to {action} the server?", view=view)
|
||||
|
||||
await view.wait()
|
||||
|
||||
if view.result is True:
|
||||
if isinstance(ctx, discord.Interaction):
|
||||
await ctx.edit_original_response(content=f"Sending websocket command to {action} server...", view=None)
|
||||
else:
|
||||
await message.edit(content=f"Sending websocket command to {action} server...", view=None)
|
||||
|
||||
await self.websocket.send(json.dumps({"event": "set state", "args": [action]}))
|
||||
|
||||
if isinstance(ctx, discord.Interaction):
|
||||
await ctx.edit_original_response(content=f"Server {action_ing}", view=None)
|
||||
else:
|
||||
await message.edit(content=f"Server {action_ing}", view=None)
|
||||
|
||||
else:
|
||||
if isinstance(ctx, discord.Interaction):
|
||||
await ctx.edit_original_response(content="Cancelled.", view=None)
|
||||
else:
|
||||
await message.edit(content="Cancelled.", view=None)
|
||||
|
||||
async def send_command(self, ctx: Union[discord.Interaction, commands.Context], command: str):
|
||||
channel = self.bot.get_channel(await config.console_channel())
|
||||
if isinstance(ctx, discord.Interaction):
|
||||
if channel:
|
||||
await channel.send(f"Received console command from {ctx.user.id}: {command[:1900]}", allowed_mentions=discord.AllowedMentions.none())
|
||||
try:
|
||||
await self.websocket.send(json.dumps({"event": "send command", "args": [command]}))
|
||||
await ctx.response.send_message(f"Command sent to server. {box(command, 'json')}", ephemeral=True)
|
||||
except websockets.exceptions.ConnectionClosed as e:
|
||||
logger.error("WebSocket connection closed: %s", e)
|
||||
await ctx.response.send_message(error("WebSocket connection closed."))
|
||||
self.task.cancel()
|
||||
self.retry_counter = 0
|
||||
self.task = self.get_task()
|
||||
else:
|
||||
ctx = await self.bot.get_context(ctx)
|
||||
if channel:
|
||||
await channel.send(f"Received console command from {ctx.author.id}: {command[:1900]}", allowed_mentions=discord.AllowedMentions.none())
|
||||
try:
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
# pylint: disable=cyclic-import
|
||||
import json
|
||||
import re
|
||||
from typing import Optional, Union
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple, Union
|
||||
|
||||
import aiohttp
|
||||
import discord
|
||||
import websockets
|
||||
from pydactyl import PterodactylClient
|
||||
from redbot.core.data_manager import bundled_data_path
|
||||
from redbot.core.utils.chat_formatting import bold, pagify
|
||||
|
||||
from pterodactyl.config import config
|
||||
|
@ -78,7 +80,12 @@ async def establish_websocket_connection(coginstance: Pterodactyl) -> None:
|
|||
if join_message:
|
||||
if chat_channel is not None:
|
||||
if coginstance.bot.embed_requested(chat_channel):
|
||||
await chat_channel.send(embed=await generate_join_leave_embed(join_message, True))
|
||||
embed, img = await generate_join_leave_embed(coginstance=coginstance, username=join_message,join=True)
|
||||
if img:
|
||||
with open(img, 'rb') as file:
|
||||
await chat_channel.send(embed=embed, file=file)
|
||||
else:
|
||||
await chat_channel.send(embed=embed)
|
||||
else:
|
||||
await chat_channel.send(f"{join_message} joined the game", allowed_mentions=discord.AllowedMentions.none())
|
||||
|
||||
|
@ -86,7 +93,12 @@ async def establish_websocket_connection(coginstance: Pterodactyl) -> None:
|
|||
if leave_message:
|
||||
if chat_channel is not None:
|
||||
if coginstance.bot.embed_requested(chat_channel):
|
||||
await chat_channel.send(embed=await generate_join_leave_embed(leave_message, False))
|
||||
embed, img = await generate_join_leave_embed(coginstance=coginstance, username=leave_message,join=False)
|
||||
if img:
|
||||
with open(img, 'rb') as file:
|
||||
await chat_channel.send(embed=embed, file=file)
|
||||
else:
|
||||
await chat_channel.send(embed=embed)
|
||||
else:
|
||||
await chat_channel.send(f"{leave_message} left the game", allowed_mentions=discord.AllowedMentions.none())
|
||||
|
||||
|
@ -94,7 +106,7 @@ async def establish_websocket_connection(coginstance: Pterodactyl) -> None:
|
|||
if achievement_message:
|
||||
if chat_channel is not None:
|
||||
if coginstance.bot.embed_requested(chat_channel):
|
||||
await chat_channel.send(embed=await generate_achievement_embed(achievement_message['username'], achievement_message['achievement'], achievement_message['challenge']))
|
||||
await chat_channel.send(embed=await generate_achievement_embed(coginstance, achievement_message['username'], achievement_message['achievement'], achievement_message['challenge']))
|
||||
else:
|
||||
await chat_channel.send(f"{achievement_message['username']} has {'completed the challenge' if achievement_message['challenge'] else 'made the advancement'} {achievement_message['achievement']}")
|
||||
|
||||
|
@ -153,7 +165,7 @@ async def check_if_server_message(text: str) -> Union[bool, str]:
|
|||
regex = await config.server_regex()
|
||||
match: Optional[re.Match[str]] = re.match(regex, text)
|
||||
if match:
|
||||
logger.debug("Message is a server message")
|
||||
logger.trace("Message is a server message")
|
||||
return match.group(1)
|
||||
return False
|
||||
|
||||
|
@ -162,7 +174,7 @@ async def check_if_chat_message(text: str) -> Union[bool, dict]:
|
|||
match: Optional[re.Match[str]] = re.match(regex, text)
|
||||
if match:
|
||||
groups = {"username": match.group(1), "message": match.group(2)}
|
||||
logger.debug("Message is a chat message\n%s", json.dumps(groups))
|
||||
logger.trace("Message is a chat message\n%s", json.dumps(groups))
|
||||
return groups
|
||||
return False
|
||||
|
||||
|
@ -170,7 +182,7 @@ async def check_if_join_message(text: str) -> Union[bool, str]:
|
|||
regex = await config.join_regex()
|
||||
match: Optional[re.Match[str]] = re.match(regex, text)
|
||||
if match:
|
||||
logger.debug("Message is a join message")
|
||||
logger.trace("Message is a join message")
|
||||
return match.group(1)
|
||||
return False
|
||||
|
||||
|
@ -178,7 +190,7 @@ async def check_if_leave_message(text: str) -> Union[bool, str]:
|
|||
regex = await config.leave_regex()
|
||||
match: Optional[re.Match[str]] = re.match(regex, text)
|
||||
if match:
|
||||
logger.debug("Message is a leave message")
|
||||
logger.trace("Message is a leave message")
|
||||
return match.group(1)
|
||||
return False
|
||||
|
||||
|
@ -191,23 +203,23 @@ async def check_if_achievement_message(text: str) -> Union[bool, dict]:
|
|||
groups["challenge"] = True
|
||||
else:
|
||||
groups["challenge"] = False
|
||||
logger.debug("Message is an achievement message")
|
||||
logger.trace("Message is an achievement message")
|
||||
return groups
|
||||
return False
|
||||
|
||||
async def get_info(username: str) -> Optional[dict]:
|
||||
logger.debug("Retrieving player info for %s", username)
|
||||
logger.verbose("Retrieving player info for %s", username)
|
||||
endpoint = await config.api_endpoint()
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(f"https://playerdb.co/api/player/{endpoint}/{username}") as response:
|
||||
if response.status == 200:
|
||||
logger.debug("Player info retrieved for %s", username)
|
||||
logger.verbose("Player info retrieved for %s", username)
|
||||
return await response.json()
|
||||
logger.error("Failed to retrieve player info for %s: %s", username, response.status)
|
||||
logger.warning("Failed to retrieve player info for %s: %s", username, response.status)
|
||||
return None
|
||||
|
||||
async def send_chat_discord(coginstance: Pterodactyl, username: str, message: str, avatar_url: str) -> None:
|
||||
logger.debug("Sending chat message to Discord")
|
||||
logger.trace("Sending chat message to Discord")
|
||||
channel = coginstance.bot.get_channel(await config.chat_channel())
|
||||
if channel is not None:
|
||||
webhooks = await channel.webhooks()
|
||||
|
@ -215,33 +227,37 @@ async def send_chat_discord(coginstance: Pterodactyl, username: str, message: st
|
|||
if webhook is None:
|
||||
webhook = await channel.create_webhook(name="Pterodactyl Chat")
|
||||
await webhook.send(content=message, username=username, avatar_url=avatar_url, allowed_mentions=discord.AllowedMentions(everyone=False, roles=False, users=True))
|
||||
logger.debug("Chat message sent to Discord")
|
||||
logger.trace("Chat message sent to Discord")
|
||||
else:
|
||||
logger.warning("Chat channel not set. Skipping sending chat message to Discord")
|
||||
|
||||
async def generate_join_leave_embed(username: str, join: bool) -> discord.Embed:
|
||||
async def generate_join_leave_embed(coginstance: Pterodactyl, username: str, join: bool) -> Tuple[discord.Embed, Optional[Union[str, Path]]]:
|
||||
embed = discord.Embed()
|
||||
embed.color = discord.Color.green() if join else discord.Color.red()
|
||||
embed.description = await config.join_msg() if join else await config.leave_msg()
|
||||
info = await get_info(username)
|
||||
if info:
|
||||
img = None
|
||||
embed.set_author(name=username, icon_url=info['data']['player']['avatar'])
|
||||
else:
|
||||
embed.set_author(name=username, icon_url='https://seafsh.cc/u/j3AzqQ.png')
|
||||
img = bundled_data_path(coginstance) / "unknown.png"
|
||||
embed.set_author(name=username, icon_url='attachment://unknown.png')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
return embed
|
||||
return embed, img
|
||||
|
||||
async def generate_achievement_embed(username: str, achievement: str, challenge: bool) -> discord.Embed:
|
||||
async def generate_achievement_embed(coginstance: Pterodactyl, username: str, achievement: str, challenge: bool) -> Tuple[discord.Embed, Optional[Union[str, Path]]]:
|
||||
embed = discord.Embed()
|
||||
embed.color = discord.Color.from_str('#a800a7') if challenge else discord.Color.from_str('#54fb54')
|
||||
embed.description = f"{bold(username)} has {'completed the challenge' if challenge else 'made the advancement'} {bold(achievement)}"
|
||||
info = await get_info(username)
|
||||
if info:
|
||||
img = None
|
||||
embed.set_author(name=username, icon_url=info['data']['player']['avatar'])
|
||||
else:
|
||||
embed.set_author(name=username, icon_url='https://seafsh.cc/u/j3AzqQ.png')
|
||||
img = bundled_data_path(coginstance) / "unknown.png"
|
||||
embed.set_author(name=username, icon_url='attachment://unknown.png')
|
||||
embed.timestamp = discord.utils.utcnow()
|
||||
return embed
|
||||
return embed, img
|
||||
|
||||
def mask_ip(string: str) -> str:
|
||||
def check(match: re.Match[str]):
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
name = "seacogs"
|
||||
version = "0.1.0"
|
||||
description = "My assorted cogs for Red-DiscordBot."
|
||||
authors = ["SeaswimmerTheFsh"]
|
||||
authors = ["cswimr"]
|
||||
license = "MPL 2"
|
||||
readme = "README.md"
|
||||
package-mode = false
|
||||
|
@ -14,6 +14,9 @@ py-dactyl = "^2.0.4"
|
|||
websockets = "^12.0"
|
||||
pillow = "^10.3.0"
|
||||
numpy = "^1.26.4"
|
||||
colorthief = "^0.2.1"
|
||||
beautifulsoup4 = "^4.12.3"
|
||||
markdownify = "^0.12.1"
|
||||
py-lav = {extras = ["all"], version = ">=1.14.3,<1.15"}
|
||||
|
||||
[tool.poetry.group.dev]
|
||||
|
@ -22,6 +25,7 @@ optional = true
|
|||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "^0.3.1"
|
||||
pylint = "^3.1.0"
|
||||
sqlite-web = "^0.6.4"
|
||||
|
||||
[tool.poetry.group.docs]
|
||||
optional = true
|
||||
|
|
5
seautils/__init__.py
Normal file
5
seautils/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from .seautils import SeaUtils
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
await bot.add_cog(SeaUtils(bot))
|
13
seautils/info.json
Normal file
13
seautils/info.json
Normal file
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"author" : ["cswimr"],
|
||||
"install_msg" : "Thank you for installing SeaUtils!\nYou can find the source code of this cog [here](https://coastalcommits.com/cswimr/SeaCogs).",
|
||||
"name" : "SeaUtils",
|
||||
"short" : "A collection of useful utilities.",
|
||||
"description" : "A collection of useful utilities.",
|
||||
"end_user_data_statement" : "This cog does not store end user data.",
|
||||
"hidden": true,
|
||||
"disabled": false,
|
||||
"min_bot_version": "3.5.0",
|
||||
"min_python_version": [3, 8, 0],
|
||||
"requirements": ["beautifulsoup4", "markdownify"]
|
||||
}
|
254
seautils/seautils.py
Normal file
254
seautils/seautils.py
Normal file
|
@ -0,0 +1,254 @@
|
|||
# _____ _
|
||||
# / ____| (_)
|
||||
# | (___ ___ __ _ _____ ___ _ __ ___ _ __ ___ ___ _ __
|
||||
# \___ \ / _ \/ _` / __\ \ /\ / / | '_ ` _ \| '_ ` _ \ / _ \ '__|
|
||||
# ____) | __/ (_| \__ \\ V V /| | | | | | | | | | | | __/ |
|
||||
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
|
||||
|
||||
import asyncio
|
||||
import inspect
|
||||
import operator
|
||||
import re
|
||||
from asyncio.subprocess import Process
|
||||
from functools import partial, partialmethod
|
||||
from typing import Any
|
||||
|
||||
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
|
||||
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__ = ["[cswimr](https://www.coastalcommits.com/cswimr)"]
|
||||
__git__ = "https://www.coastalcommits.com/cswimr/SeaCogs"
|
||||
__version__ = "1.0.1"
|
||||
__documentation__ = "https://seacogs.coastalcommits.com/seautils/"
|
||||
|
||||
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 ""
|
||||
n = "\n" if "\n\n" not in pre_processed else ""
|
||||
text = [
|
||||
f"{pre_processed}{n}",
|
||||
f"{cf.bold('Cog Version:')} [{self.__version__}]({self.__git__})",
|
||||
f"{cf.bold('Author:')} {cf.humanize_list(self.__author__)}",
|
||||
f"{cf.bold('Documentation:')} {self.__documentation__}",
|
||||
]
|
||||
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(func=obj)
|
||||
src: Any = getattr(obj, "__func__", obj)
|
||||
if isinstance(obj, (commands.Command, app_commands.Command)):
|
||||
src = obj.callback
|
||||
elif isinstance(obj, (partial, partialmethod)):
|
||||
src = obj.func
|
||||
elif isinstance(obj, property):
|
||||
src = obj.fget
|
||||
elif isinstance(obj, (cached_property, CachedSlotProperty)):
|
||||
src = obj.function
|
||||
return inspect.getsource(object=src)
|
||||
|
||||
@commands.command(aliases=["source", "src", "code", "showsource"])
|
||||
@commands.is_owner()
|
||||
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:])):
|
||||
text = self.format_src(obj)
|
||||
elif obj := ctx.bot.get_cog(object):
|
||||
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,
|
||||
page_length = 1977
|
||||
)
|
||||
content = []
|
||||
max_i = operator.length_hint(temp_content)
|
||||
i = 1
|
||||
for page in temp_content:
|
||||
content.append(f"**Page {i}/{max_i}**\n{cf.box(page, lang='py')}")
|
||||
i += 1
|
||||
await SimpleMenu(pages=content, disable_after_timeout=True, timeout=180).start(ctx)
|
||||
except (OSError, AttributeError, UnboundLocalError):
|
||||
if ctx.embed_requested():
|
||||
embed = Embed(title="Object not found!", color=await ctx.embed_color())
|
||||
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(message=cf.error(f"An error occurred while fetching RFC {number}. Status code: {response.status}."))
|
Loading…
Reference in a new issue