Compare commits

..

1 commit

Author SHA1 Message Date
a62b6d5593
feat(welcomer): added the welcomer cog 2024-03-26 03:01:06 -04:00
28 changed files with 1367 additions and 1363 deletions

View file

@ -10,14 +10,6 @@ Backup allows you to export a JSON list of all of your installed repositories an
[p]cog load 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`.
```bash
[p]cog installversion sea-cogs 43464db6a7c51bc69282b1ae3dc507a4aae851de backup
```
## Commands ## Commands
### backup export ### backup export

1
.gitignore vendored
View file

@ -1,4 +1,3 @@
.cache .cache
.vscode .vscode
site site
.venv

View file

@ -1,5 +0,0 @@
from .antipolls import AntiPolls
async def setup(bot):
await bot.add_cog(AntiPolls(bot))

View file

@ -1,180 +0,0 @@
# _____ _
# / ____| (_)
# | (___ ___ __ _ _____ ___ _ __ ___ _ __ ___ ___ _ __
# \___ \ / _ \/ _` / __\ \ /\ / / | '_ ` _ \| '_ ` _ \ / _ \ '__|
# ____) | __/ (_| \__ \\ V V /| | | | | | | | | | | | __/ |
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
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
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"
__documentation__ = "https://seacogs.coastalcommits.com/antipolls/"
def __init__(self, bot: Red):
super().__init__()
self.bot = bot
self.logger = getLogger("red.SeaCogs.AntiPolls")
self.config = Config.get_conf(self, identifier=23517395243, force_registration=True)
self.config.register_guild(
role_whitelist=[],
channel_whitelist=[],
manage_messages=True,
)
if not self.bot.intents.message_content:
self.logger.error("Message Content intent is not enabled, cog will not load.")
raise RuntimeError("This cog requires the Message Content intent to function. To prevent potentially destructive behavior, the cog will not load without the intent enabled.")
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"Cog Version: **{self.__version__}**",
f"Author: {humanize_list(self.__author__)}",
f"Documentation: {self.__documentation__}",
]
return "\n".join(text)
async def red_delete_data_for_user(self, **kwargs): # pylint: disable=unused-argument
"""Nothing to delete."""
return
@commands.Cog.listener('on_message')
async def polls_listener(self, message: discord.Message) -> None:
if message.guild is None:
return self.logger.verbose("Message in direct messages ignored")
if message.author.bot:
return self.logger.verbose("Message from bot ignored")
if await self.bot.cog_disabled_in_guild(self, message.guild):
return self.logger.verbose("Message ignored, cog is disabled in guild %s", message.guild.id)
guild_config = await self.config.guild(message.guild).all()
if guild_config['manage_messages'] is True and message.author.guild_permissions.manage_messages:
return self.logger.verbose("Message from user with Manage Messages permission ignored")
if message.channel.id in guild_config['channel_whitelist']:
return self.logger.verbose("Message in whitelisted channel %s ignored", message.channel.id)
if any(role.id in guild_config['role_whitelist'] for role in message.author.roles):
return self.logger.verbose("Message from whitelisted role %s ignored", message.author.roles)
if not message.content and not message.embeds and not message.attachments and not message.stickers:
self.logger.trace("Message %s is a poll, attempting to delete", message.id)
try:
await message.delete()
except discord.HTTPException as e:
return self.logger.error("Failed to delete message: %s", e)
return self.logger.trace("Deleted poll message %s", message.id)
self.logger.verbose("Message %s is not a poll, ignoring", message.id)
@commands.group(name="antipolls", aliases=["ap"])
@commands.guild_only()
@commands.admin_or_permissions(manage_guild=True)
async def antipolls(self, ctx: commands.Context) -> None:
"""Manage AntiPolls settings."""
@antipolls.group(name="roles")
async def antipolls_roles(self, ctx: commands.Context) -> None:
"""Manage role whitelist."""
@antipolls_roles.command(name="add")
async def antipolls_roles_add(self, ctx: commands.Context, *roles: discord.Role) -> None:
"""Add roles to the whitelist."""
async with self.config.guild(ctx.guild).role_whitelist() as role_whitelist:
role_whitelist: list
failed: list[discord.Role] = []
for role in roles:
if role.id in role_whitelist:
failed.append(role)
continue
role_whitelist.append(role.id)
await ctx.tick()
if failed:
await ctx.send(f"The following roles were already in the whitelist: {humanize_list([role.mention for role in failed])}", delete_after=10)
@antipolls_roles.command(name="remove")
async def antipolls_roles_remove(self, ctx: commands.Context, *roles: discord.Role) -> None:
"""Remove roles from the whitelist."""
async with self.config.guild(ctx.guild).role_whitelist() as role_whitelist:
role_whitelist: list
failed: list[discord.Role] = []
for role in roles:
if role.id not in role_whitelist:
failed.append(role)
continue
role_whitelist.remove(role.id)
await ctx.tick()
if failed:
await ctx.send(f"The following roles were not in the whitelist: {humanize_list([role.mention for role in failed])}", delete_after=10)
@antipolls_roles.command(name="list")
async def antipolls_roles_list(self, ctx: commands.Context) -> None:
"""List roles in the whitelist."""
role_whitelist = await self.config.guild(ctx.guild).role_whitelist()
if not role_whitelist:
return await ctx.send("No roles in the whitelist.")
roles = [ctx.guild.get_role(role) for role in role_whitelist]
await ctx.send(humanize_list([role.mention for role in roles]))
@antipolls.group(name="channels")
async def antipolls_channels(self, ctx: commands.Context) -> None:
"""Manage channel whitelist."""
@antipolls_channels.command(name="add")
async def antipolls_channels_add(self, ctx: commands.Context, *channels: discord.TextChannel) -> None:
"""Add channels to the whitelist."""
async with self.config.guild(ctx.guild).channel_whitelist() as channel_whitelist:
channel_whitelist: list
failed: list[discord.TextChannel] = []
for channel in channels:
if channel.id in channel_whitelist:
failed.append(channel)
continue
channel_whitelist.append(channel.id)
await ctx.tick()
if failed:
await ctx.send(f"The following channels were already in the whitelist: {humanize_list([channel.mention for channel in failed])}", delete_after=10)
@antipolls_channels.command(name="remove")
async def antipolls_channels_remove(self, ctx: commands.Context, *channels: discord.TextChannel) -> None:
"""Remove channels from the whitelist."""
async with self.config.guild(ctx.guild).channel_whitelist() as channel_whitelist:
channel_whitelist: list
failed: list[discord.TextChannel] = []
for channel in channels:
if channel.id not in channel_whitelist:
failed.append(channel)
continue
channel_whitelist.remove(channel.id)
await ctx.tick()
if failed:
await ctx.send(f"The following channels were not in the whitelist: {humanize_list([channel.mention for channel in failed])}", delete_after=10)
@antipolls_channels.command(name="list")
async def antipolls_channels_list(self, ctx: commands.Context) -> None:
"""List channels in the whitelist."""
channel_whitelist = await self.config.guild(ctx.guild).channel_whitelist()
if not channel_whitelist:
return await ctx.send("No channels in the whitelist.")
channels = [ctx.guild.get_channel(channel) for channel in channel_whitelist]
await ctx.send(humanize_list([channel.mention for channel in channels]))
@antipolls.command(name="managemessages")
async def antipolls_managemessages(self, ctx: commands.Context, enabled: bool) -> None:
"""Toggle Manage Messages permission check."""
await self.config.guild(ctx.guild).manage_messages.set(enabled)
await ctx.tick()

View file

@ -1,17 +0,0 @@
{
"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).",
"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,
"disabled": false,
"min_bot_version": "3.5.0",
"min_python_version": [3, 10, 0],
"tags": [
"automod",
"automoderation",
"polls"
]
}

View file

@ -39,8 +39,7 @@ class Aurora(commands.Cog):
This cog stores all of its data in an SQLite database.""" This cog stores all of its data in an SQLite database."""
__author__ = ["SeaswimmerTheFsh"] __author__ = ["SeaswimmerTheFsh"]
__version__ = "2.1.2" __version__ = "2.1.0"
__documentation__ = "https://seacogs.coastalcommits.com/aurora/"
async def red_delete_data_for_user(self, *, requester, user_id: int): async def red_delete_data_for_user(self, *, requester, user_id: int):
if requester == "discord_deleted_user": if requester == "discord_deleted_user":
@ -85,7 +84,6 @@ class Aurora(commands.Cog):
f"{pre_processed}{n}", f"{pre_processed}{n}",
f"Cog Version: **{self.__version__}**", f"Cog Version: **{self.__version__}**",
f"Author: {humanize_list(self.__author__)}", f"Author: {humanize_list(self.__author__)}",
f"Documentation: {self.__documentation__}",
] ]
return "\n".join(text) return "\n".join(text)
@ -1486,7 +1484,6 @@ class Aurora(commands.Cog):
@tasks.loop(minutes=1) @tasks.loop(minutes=1)
async def handle_expiry(self): async def handle_expiry(self):
await self.bot.wait_until_red_ready()
current_time = time.time() current_time = time.time()
database = connect() database = connect()
cursor = database.cursor() cursor = database.cursor()

View file

@ -17,13 +17,12 @@ class Addrole(ui.View):
await interaction.response.send_message(error("You must have the manage guild permission to add roles to the addrole whitelist."), ephemeral=True) await interaction.response.send_message(error("You must have the manage guild permission to add roles to the addrole whitelist."), ephemeral=True)
return return
await interaction.response.defer() await interaction.response.defer()
async with config.guild(self.ctx.guild).addrole_whitelist() as addrole_whitelist: addrole_whitelist: list = await config.guild(self.ctx.guild).addrole_whitelist()
addrole_whitelist: list # type hint if select.values[0].id in addrole_whitelist:
for value in select.values: addrole_whitelist.remove(select.values[0].id)
if value.id in addrole_whitelist:
addrole_whitelist.remove(value.id)
else: else:
addrole_whitelist.append(value.id) addrole_whitelist.append(select.values[0].id)
await config.guild(self.ctx.guild).addrole_whitelist.set(addrole_whitelist)
await interaction.message.edit(embed=await addrole_embed(self.ctx)) await interaction.message.edit(embed=await addrole_embed(self.ctx))
@ui.button(label="Clear", style=ButtonStyle.red, row=1) @ui.button(label="Clear", style=ButtonStyle.red, row=1)

View file

@ -31,16 +31,6 @@ class Guild(ui.View):
await config.guild(interaction.guild).use_discord_permissions.set(not current_setting) await config.guild(interaction.guild).use_discord_permissions.set(not current_setting)
await interaction.message.edit(embed=await guild_embed(self.ctx)) await interaction.message.edit(embed=await guild_embed(self.ctx))
@ui.button(label="Respect Hierarchy", style=ButtonStyle.green, row=0)
async def respect_heirarchy(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
if not interaction.user.guild_permissions.manage_guild and not interaction.user.guild_permissions.administrator:
await interaction.response.send_message("You must have the manage guild permission to change this setting.", ephemeral=True)
return
await interaction.response.defer()
current_setting = await config.guild(interaction.guild).respect_hierarchy()
await config.guild(interaction.guild).respect_hierarchy.set(not current_setting)
await interaction.message.edit(embed=await guild_embed(self.ctx))
@ui.button(label="Ignore Modlog", style=ButtonStyle.green, row=0) @ui.button(label="Ignore Modlog", style=ButtonStyle.green, row=0)
async def ignore_modlog(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument async def ignore_modlog(self, interaction: Interaction, button: ui.Button): # pylint: disable=unused-argument
if not interaction.user.guild_permissions.manage_guild and not interaction.user.guild_permissions.administrator: if not interaction.user.guild_permissions.manage_guild and not interaction.user.guild_permissions.administrator:

View file

@ -17,13 +17,13 @@ class Immune(ui.View):
await interaction.response.send_message(error("You must have the manage guild permission to add immune roles."), ephemeral=True) await interaction.response.send_message(error("You must have the manage guild permission to add immune roles."), ephemeral=True)
return return
await interaction.response.defer() await interaction.response.defer()
async with config.guild(self.ctx.guild).immune_roles() as immune_roles: immune_roles: list = await config.guild(self.ctx.guild).immune_roles()
immune_roles: list # type hint for role in select.values:
for value in select.values: if role.id in immune_roles:
if value.id in immune_roles: immune_roles.remove(role.id)
immune_roles.remove(value.id)
else: else:
immune_roles.append(value.id) immune_roles.append(role.id)
await config.guild(self.ctx.guild).immune_roles.set(immune_roles)
await interaction.message.edit(embed=await immune_embed(self.ctx)) await interaction.message.edit(embed=await immune_embed(self.ctx))
@ui.button(label="Clear", style=ButtonStyle.red, row=1) @ui.button(label="Clear", style=ButtonStyle.red, row=1)

View file

@ -7,7 +7,6 @@ def register_config(config_obj: Config):
config_obj.register_guild( config_obj.register_guild(
show_moderator=True, show_moderator=True,
use_discord_permissions=True, use_discord_permissions=True,
respect_hierarchy=True,
ignore_modlog=True, ignore_modlog=True,
ignore_other_bots=True, ignore_other_bots=True,
dm_users=True, dm_users=True,

View file

@ -2,16 +2,12 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Union from typing import Union
from discord import (Color, Embed, Guild, Interaction, InteractionMessage, from discord import Color, Embed, Guild, Interaction, InteractionMessage, Member, Role, User
Member, Role, User)
from redbot.core import commands from redbot.core import commands
from redbot.core.utils.chat_formatting import (bold, box, error, from redbot.core.utils.chat_formatting import bold, box, error, humanize_timedelta, warning
humanize_timedelta, warning)
from aurora.utilities.config import config from aurora.utilities.config import config
from aurora.utilities.utils import (fetch_channel_dict, fetch_user_dict, from aurora.utilities.utils import fetch_channel_dict, fetch_user_dict, get_bool_emoji, get_next_case_number, get_pagesize_str
get_bool_emoji, get_next_case_number,
get_pagesize_str)
async def message_factory( async def message_factory(
@ -455,7 +451,6 @@ async def guild_embed(ctx: commands.Context) -> Embed:
ctx.guild ctx.guild
).history_inline_pagesize(), ).history_inline_pagesize(),
"auto_evidenceformat": await config.guild(ctx.guild).auto_evidenceformat(), "auto_evidenceformat": await config.guild(ctx.guild).auto_evidenceformat(),
"respect_hierarchy": await config.guild(ctx.guild).respect_hierarchy(),
} }
channel = ctx.guild.get_channel(guild_settings["log_channel"]) channel = ctx.guild.get_channel(guild_settings["log_channel"])
@ -472,9 +467,6 @@ async def guild_embed(ctx: commands.Context) -> Embed:
+ bold("Use Discord Permissions: ") + bold("Use Discord Permissions: ")
+ get_bool_emoji(guild_settings["use_discord_permissions"]), + get_bool_emoji(guild_settings["use_discord_permissions"]),
"- " "- "
+ bold("Respect Hierarchy: ")
+ get_bool_emoji(guild_settings["respect_hierarchy"]),
"- "
+ bold("Ignore Modlog: ") + bold("Ignore Modlog: ")
+ get_bool_emoji(guild_settings["ignore_modlog"]), + get_bool_emoji(guild_settings["ignore_modlog"]),
"- " "- "
@ -514,29 +506,15 @@ async def guild_embed(ctx: commands.Context) -> Embed:
async def addrole_embed(ctx: commands.Context) -> Embed: async def addrole_embed(ctx: commands.Context) -> Embed:
"""Generates a configuration menu field value for a guild's addrole whitelist.""" """Generates a configuration menu field value for a guild's addrole whitelist."""
roles = [] whitelist = await config.guild(ctx.guild).addrole_whitelist()
async with config.guild(ctx.guild).addrole_whitelist() as whitelist: if whitelist:
for role in whitelist: whitelist = [
evalulated_role = ctx.guild.get_role(role) or error(f"`{role}` (Not Found)") ctx.guild.get_role(role).mention or error(f"`{role}` (Not Found)")
if isinstance(evalulated_role, Role): for role in whitelist
roles.append({ ]
"id": evalulated_role.id, whitelist = "\n".join(whitelist)
"mention": evalulated_role.mention,
"position": evalulated_role.position
})
else: else:
roles.append({ whitelist = warning("No roles are on the addrole whitelist!")
"id": role,
"mention": error(f"`{role}` (Not Found)"),
"position": 0
})
if roles:
roles = sorted(roles, key=lambda x: x["position"], reverse=True)
roles = [role["mention"] for role in roles]
whitelist_str = "\n".join(roles)
else:
whitelist_str = warning("No roles are on the addrole whitelist!")
e = await _config(ctx) e = await _config(ctx)
e.title += ": Addrole Whitelist" e.title += ": Addrole Whitelist"
@ -544,8 +522,8 @@ async def addrole_embed(ctx: commands.Context) -> Embed:
"Use the select menu below to manage this guild's addrole whitelist." "Use the select menu below to manage this guild's addrole whitelist."
) )
if len(whitelist_str) > 4000 and len(whitelist_str) < 5000: if len(whitelist) > 4000 and len(whitelist) < 5000:
lines = whitelist_str.split("\n") lines = whitelist.split("\n")
chunks = [] chunks = []
chunk = "" chunk = ""
for line in lines: for line in lines:
@ -559,35 +537,21 @@ async def addrole_embed(ctx: commands.Context) -> Embed:
for chunk in chunks: for chunk in chunks:
e.add_field(name="", value=chunk) e.add_field(name="", value=chunk)
else: else:
e.description += "\n\n" + whitelist_str e.description += "\n\n" + whitelist
return e return e
async def immune_embed(ctx: commands.Context) -> Embed: async def immune_embed(ctx: commands.Context) -> Embed:
"""Generates a configuration menu embed for a guild's immune roles.""" """Generates a configuration menu field value for a guild's immune roles."""
roles = [] immune_roles = await config.guild(ctx.guild).immune_roles()
async with config.guild(ctx.guild).immune_roles() as immune_roles: if immune_roles:
for role in immune_roles: immune_str = [
evalulated_role = ctx.guild.get_role(role) or error(f"`{role}` (Not Found)") ctx.guild.get_role(role).mention or error(f"`{role}` (Not Found)")
if isinstance(evalulated_role, Role): for role in immune_roles
roles.append({ ]
"id": evalulated_role.id, immune_str = "\n".join(immune_str)
"mention": evalulated_role.mention,
"position": evalulated_role.position
})
else:
roles.append({
"id": role,
"mention": error(f"`{role}` (Not Found)"),
"position": 0
})
if roles:
roles = sorted(roles, key=lambda x: x["position"], reverse=True)
roles = [role["mention"] for role in roles]
immune_str = "\n".join(roles)
else: else:
immune_str = warning("No roles are set as immune roles!") immune_str = warning("No roles are set as immune roles!")

View file

@ -1,3 +1,3 @@
from red_commons.logging import getLogger from red_commons.logging import getLogger
logger = getLogger("red.SeaCogs.Aurora") logger = getLogger("red.seacogs.aurora")

View file

@ -2,7 +2,7 @@
import json import json
from datetime import datetime from datetime import datetime
from datetime import timedelta as td from datetime import timedelta as td
from typing import Optional, Union from typing import Union
from dateutil.relativedelta import relativedelta as rd from dateutil.relativedelta import relativedelta as rd
from discord import Guild, Interaction, Member, SelectOption, User from discord import Guild, Interaction, Member, SelectOption, User
@ -77,7 +77,7 @@ async def check_moddable(
return False return False
if isinstance(target, Member): if isinstance(target, Member):
if interaction.user.top_role <= target.top_role and await config.guild(interaction.guild).respect_hierarchy() is True: if interaction.user.top_role <= target.top_role:
await interaction.response.send_message( await interaction.response.send_message(
content=error( content=error(
"You cannot moderate members with a higher role than you!" "You cannot moderate members with a higher role than you!"
@ -250,7 +250,7 @@ def convert_timedelta_to_str(timedelta: td) -> str:
return f"{hours}:{minutes}:{seconds}" return f"{hours}:{minutes}:{seconds}"
def get_bool_emoji(value: Optional[bool]) -> str: def get_bool_emoji(value: bool) -> str:
"""Returns a unicode emoji based on a boolean value.""" """Returns a unicode emoji based on a boolean value."""
if value is True: if value is True:
return "\N{WHITE HEAVY CHECK MARK}" return "\N{WHITE HEAVY CHECK MARK}"

View file

@ -14,7 +14,8 @@ from redbot.cogs.downloader import errors
from redbot.cogs.downloader.converters import InstalledCog from redbot.cogs.downloader.converters import InstalledCog
from redbot.core import commands from redbot.core import commands
from redbot.core.bot import Red 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 (error, humanize_list,
text_to_file)
# pylint: disable=protected-access # pylint: disable=protected-access
@ -22,13 +23,12 @@ class Backup(commands.Cog):
"""A utility to make reinstalling repositories and cogs after migrating the bot far easier.""" """A utility to make reinstalling repositories and cogs after migrating the bot far easier."""
__author__ = ["SeaswimmerTheFsh"] __author__ = ["SeaswimmerTheFsh"]
__version__ = "1.1.0" __version__ = "1.0.1"
__documentation__ = "https://seacogs.coastalcommits.com/backup/"
def __init__(self, bot: Red): def __init__(self, bot: Red):
super().__init__() super().__init__()
self.bot = bot self.bot = bot
self.logger = getLogger("red.SeaCogs.Backup") self.logger = getLogger("red.seacogs.backup")
def format_help_for_context(self, ctx: commands.Context) -> str: 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) or ""
@ -37,7 +37,6 @@ class Backup(commands.Cog):
f"{pre_processed}{n}", f"{pre_processed}{n}",
f"Cog Version: **{self.__version__}**", f"Cog Version: **{self.__version__}**",
f"Author: {humanize_list(self.__author__)}", f"Author: {humanize_list(self.__author__)}",
f"Documentation: {self.__documentation__}",
] ]
return "\n".join(text) return "\n".join(text)
@ -95,19 +94,12 @@ class Backup(commands.Cog):
@commands.is_owner() @commands.is_owner()
async def backup_import(self, ctx: commands.Context): async def backup_import(self, ctx: commands.Context):
"""Import your installed repositories and cogs from an export file.""" """Import your installed repositories and cogs from an export file."""
export = None
if ctx.message.attachments:
try: try:
export = json.loads(await ctx.message.attachments[0].read()) export = json.loads(await ctx.message.attachments[0].read())
except json.JSONDecodeError: except (json.JSONDecodeError, IndexError):
await ctx.send(error("Invalid JSON in message attachments."))
elif ctx.message.reference and hasattr(ctx.message.reference, 'resolved'):
if ctx.message.reference.resolved.attachments:
try: try:
export = json.loads(await ctx.message.reference.resolved.attachments[0].read()) export = json.loads(await ctx.message.reference.resolved.attachments[0].read())
except json.JSONDecodeError: except (json.JSONDecodeError, IndexError):
await ctx.send(error("Invalid JSON in referenced message attachments."))
if export is None:
await ctx.send(error("Please provide a valid JSON export file.")) await ctx.send(error("Please provide a valid JSON export file."))
return return
@ -120,23 +112,210 @@ class Backup(commands.Cog):
) )
return return
all_repos = list(downloader._repo_manager.repos) repo_s = []
uninstall_s = []
install_s = []
repo_e = []
uninstall_e = []
install_e = []
async with ctx.typing():
for repo in export: for repo in export:
if repo["name"] not in [r.name for r in all_repos]: # Most of this code is from the Downloader cog.
try: name = repo["name"]
await downloader._repo_manager.add_repo( branch = repo["branch"]
repo["url"], repo["name"], repo["branch"] url = repo["url"]
) cogs = repo["cogs"]
except errors.ExistingGitRepo:
pass
for cog in repo["cogs"]: if "PyLav/Red-Cogs" in url:
try: repo_e.append("PyLav cogs are not supported.")
await downloader._cog_install_interface.install_cog( continue
cog["name"], cog["commit"] if name.startswith(".") or name.endswith("."):
repo_e.append(
f"Invalid repository name: {name}\nRepository names cannot start or end with a dot."
)
continue
if re.match(r"^[a-zA-Z0-9_\-\.]+$", name) is None:
repo_e.append(
f"Invalid repository name: {name}\nRepository names may only contain letters, numbers, underscores, hyphens, and dots."
)
continue
try:
repository = await downloader._repo_manager.add_repo(
url, name, branch
)
repo_s.append(
f"Added repository {name} from {url} on branch {branch}."
)
self.logger.debug(
"Added repository %s from %s on branch %s", name, url, branch
)
except errors.ExistingGitRepo:
repo_e.append(f"Repository {name} already exists.")
repository = downloader._repo_manager.get_repo(
name
)
self.logger.debug("Repository %s already exists", name)
# This is commented out because errors.AuthenticationError is not yet implemented in Red 3.5.5's Downloader cog.
# Rather, it is only in the development version and will be added in version 3.5.6 (or whatever the next version is).
# except errors.AuthenticationError as err:
# repo_e.append(f"Authentication error while adding repository {name}. See logs for more information.")
# self.logger.exception(
# "Something went wrong whilst cloning %s (to revision %s)",
# url,
# branch,
# exc_info=err,
# )
# continue
except errors.CloningError as err:
repo_e.append(
f"Cloning error while adding repository {name}. See logs for more information."
)
self.logger.exception(
"Something went wrong whilst cloning %s (to revision %s)",
url,
branch,
exc_info=err,
)
continue
except OSError:
repo_e.append(
f"OS error while adding repository {name}. See logs for more information."
)
self.logger.exception(
"Something went wrong trying to add repo %s under name %s",
url,
name,
)
continue
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:
continue
try:
cog_module = await InstalledCog.convert(ctx, cog["name"])
except commands.BadArgument:
uninstall_e.append(f"Failed to uninstall {cog['name']}")
continue
cog_modules.append(cog_module)
for cog in set(cog.name for cog in cog_modules):
poss_installed_path = (await downloader.cog_install_path()) / cog
if poss_installed_path.exists():
with contextlib.suppress(commands.ExtensionNotLoaded):
await ctx.bot.unload_extension(cog)
await ctx.bot.remove_loaded_package(cog)
await downloader._delete_cog(
poss_installed_path
)
uninstall_s.append(f"Uninstalled {cog}")
self.logger.debug("Uninstalled %s", cog)
else:
uninstall_e.append(f"Failed to uninstall {cog}")
self.logger.warning("Failed to uninstall %s", cog)
await downloader._remove_from_installed(
cog_modules
)
for cog in cogs:
cog_name = cog["name"]
cog_pinned = cog["pinned"]
if cog_pinned:
commit = cog["commit"]
else:
commit = None
# If you're forking this cog, make sure to change these strings!
if cog_name == "backup" and "SeaswimmerTheFsh/SeaCogs" in url:
continue
async with repository.checkout(
commit, exit_to_rev=repository.branch
):
cogs_c, message = (
await downloader._filter_incorrect_cogs_by_names(
repository, [cog_name]
)
)
if not cogs_c:
install_e.append(message)
self.logger.error(message)
continue
failed_reqs = await downloader._install_requirements(
cogs_c
)
if failed_reqs:
install_e.append(
f"Failed to install {cog_name} due to missing requirements: {failed_reqs}"
)
self.logger.error(
"Failed to install %s due to missing requirements: %s",
cog_name,
failed_reqs,
)
continue
installed_cogs, failed_cogs = await downloader._install_cogs(
cogs_c
)
if repository.available_libraries:
installed_libs, failed_libs = (
await repository.install_libraries(
target_dir=downloader.SHAREDLIB_PATH,
req_target_dir=downloader.LIB_PATH,
)
)
else:
installed_libs = None
failed_libs = None
if cog_pinned:
for cog in installed_cogs:
cog.pinned = True
await downloader._save_to_installed(
installed_cogs + installed_libs
if installed_libs
else installed_cogs
)
if installed_cogs:
installed_cog_name = installed_cogs[0].name
install_s.append(f"Installed {installed_cog_name}")
self.logger.debug("Installed %s", installed_cog_name)
if installed_libs:
for lib in installed_libs:
install_s.append(
f"Installed {lib.name} required for {cog_name}"
)
self.logger.debug(
"Installed %s required for %s", lib.name, cog_name
)
if failed_cogs:
failed_cog_name = failed_cogs[0].name
install_e.append(f"Failed to install {failed_cog_name}")
self.logger.error("Failed to install %s", failed_cog_name)
if failed_libs:
for lib in failed_libs:
install_e.append(
f"Failed to install {lib.name} required for {cog_name}"
)
self.logger.error(
"Failed to install %s required for %s",
lib.name,
cog_name,
)
await ctx.send(
"Import complete!",
file=text_to_file(
f"Repositories:\n{repo_s}\n\nRepository Errors:\n{repo_e}\n\nUninstalled Cogs:\n{uninstall_s}\n\nUninstalled Cogs Errors:\n{uninstall_e}\n\nInstalled Cogs:\n{install_s}\n\nInstalled Cogs Errors:\n{install_e}",
"backup.log",
),
) )
except errors.CogNotFoundError:
pass
except errors.DownloaderError:
pass

View file

@ -7,8 +7,8 @@
"end_user_data_statement" : "This cog does not store end user data.", "end_user_data_statement" : "This cog does not store end user data.",
"hidden": false, "hidden": false,
"disabled": false, "disabled": false,
"min_bot_version": "3.5.6", "min_bot_version": "3.5.0",
"max_bot_version": "3.5.9", "max_bot_version": "3.5.5",
"min_python_version": [3, 9, 0], "min_python_version": [3, 9, 0],
"tags": [ "tags": [
"utility", "utility",

View file

@ -6,14 +6,11 @@
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_| # |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
import random import random
from io import BytesIO
import aiohttp import aiohttp
import numpy as np from discord import Embed
from discord import Colour, Embed, File
from PIL import Image
from red_commons.logging import getLogger from red_commons.logging import getLogger
from redbot.core import Config, commands, data_manager from redbot.core import Config, commands
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import error, humanize_list from redbot.core.utils.chat_formatting import error, humanize_list
@ -25,8 +22,7 @@ class Bible(commands.Cog):
"""Retrieve Bible verses from the API.bible API.""" """Retrieve Bible verses from the API.bible API."""
__author__ = ["SeaswimmerTheFsh"] __author__ = ["SeaswimmerTheFsh"]
__version__ = "1.1.0" __version__ = "1.0.1"
__documentation__ = "https://seacogs.coastalcommits.com/bible/"
def __init__(self, bot: Red): def __init__(self, bot: Red):
super().__init__() super().__init__()
@ -35,7 +31,7 @@ class Bible(commands.Cog):
self.config = Config.get_conf( self.config = Config.get_conf(
self, identifier=481923957134912, force_registration=True self, identifier=481923957134912, force_registration=True
) )
self.logger = getLogger("red.SeaCogs.Bible") self.logger = getLogger("red.seacogs.bible")
self.config.register_global(bible="de4e12af7f28f599-02") self.config.register_global(bible="de4e12af7f28f599-02")
self.config.register_user(bible=None) self.config.register_user(bible=None)
@ -46,26 +42,9 @@ class Bible(commands.Cog):
f"{pre_processed}{n}", f"{pre_processed}{n}",
f"Cog Version: **{self.__version__}**", f"Cog Version: **{self.__version__}**",
f"Author: {humanize_list(self.__author__)}", f"Author: {humanize_list(self.__author__)}",
f"Documentation: {self.__documentation__}",
] ]
return "\n".join(text) 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"
image = Image.open(image_path)
image = image.convert("RGBA")
data = np.array(image)
red, green, blue, alpha = data.T # pylint: disable=unused-variable
white_areas = (red == 255) & (blue == 255) & (green == 255)
data[..., :-1][white_areas.T] = color.to_rgb()
image = Image.fromarray(data)
with BytesIO() as image_binary:
image.save(image_binary, "PNG")
image_binary.seek(0)
return File(image_binary, filename="icon.png", description="API.Bible Icon")
async def translate_book_name(self, bible_id: str, book_name: str) -> str: async def translate_book_name(self, bible_id: str, book_name: str) -> str:
"""Translate a book name to a book ID.""" """Translate a book name to a book ID."""
book_name_list = [ book_name_list = [
@ -267,17 +246,15 @@ class Bible(commands.Cog):
return return
if await ctx.embed_requested(): if await ctx.embed_requested():
icon = self.get_icon(await ctx.embed_color())
embed = Embed( embed = Embed(
title=f"{passage['reference']}", title=f"{passage['reference']}",
description=passage["content"].replace("", ""), description=passage["content"].replace("", ""),
color=await ctx.embed_color(), color=await self.bot.get_embed_color(ctx.channel),
) )
embed.set_footer( embed.set_footer(
text=f"{ctx.prefix}bible passage - Powered by API.Bible - {version.abbreviationLocal} ({version.languageLocal}, {version.descriptionLocal})", text=f"{ctx.prefix}bible passage - Powered by API.Bible - {version.abbreviationLocal} ({version.languageLocal}, {version.descriptionLocal})"
icon_url="attachment://icon.png"
) )
await ctx.send(embed=embed, file=icon) await ctx.send(embed=embed)
else: else:
await ctx.send(f"## {passage['reference']}\n{passage['content']}") await ctx.send(f"## {passage['reference']}\n{passage['content']}")
@ -309,16 +286,14 @@ class Bible(commands.Cog):
return return
if await ctx.embed_requested(): if await ctx.embed_requested():
icon = self.get_icon(await ctx.embed_color())
embed = Embed( embed = Embed(
title=f"{passage['reference']}", title=f"{passage['reference']}",
description=passage["content"].replace("", ""), description=passage["content"].replace("", ""),
color=await ctx.embed_color(), color=await self.bot.get_embed_color(ctx.channel),
) )
embed.set_footer( embed.set_footer(
text=f"{ctx.prefix}bible random - Powered by API.Bible - {version.abbreviationLocal} ({version.languageLocal}, {version.descriptionLocal})", text=f"{ctx.prefix}bible random - Powered by API.Bible - {version.abbreviationLocal} ({version.languageLocal}, {version.descriptionLocal})"
icon_url="attachment://icon.png"
) )
await ctx.send(embed=embed, file=icon) await ctx.send(embed=embed)
else: else:
await ctx.send(f"## {passage['reference']}\n{passage['content']}") await ctx.send(f"## {passage['reference']}\n{passage['content']}")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View file

@ -9,7 +9,6 @@
"disabled": false, "disabled": false,
"min_bot_version": "3.5.0", "min_bot_version": "3.5.0",
"min_python_version": [3, 10, 0], "min_python_version": [3, 10, 0],
"requirements": ["numpy", "pillow"],
"tags": [ "tags": [
"fun", "fun",
"utility", "utility",

View file

@ -29,7 +29,7 @@ nav:
plugins: plugins:
- git-authors - git-authors
- search - search
#- social - social
- git-revision-date-localized: - git-revision-date-localized:
enable_creation_date: true enable_creation_date: true
type: timeago type: timeago

View file

@ -18,8 +18,7 @@ class Nerdify(commands.Cog):
"""Nerdify your text.""" """Nerdify your text."""
__author__ = ["SeaswimmerTheFsh"] __author__ = ["SeaswimmerTheFsh"]
__version__ = "1.3.4" __version__ = "1.3.3"
__documentation__ = "https://seacogs.coastalcommits.com/nerdify/"
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@ -31,7 +30,6 @@ class Nerdify(commands.Cog):
f"{pre_processed}{n}", f"{pre_processed}{n}",
f"Cog Version: **{self.__version__}**", f"Cog Version: **{self.__version__}**",
f"Author: {chat_formatting.humanize_list(self.__author__)}", f"Author: {chat_formatting.humanize_list(self.__author__)}",
f"Documentation: {self.__documentation__}"
] ]
return "\n".join(text) return "\n".join(text)

1986
poetry.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
from red_commons import logging from red_commons import logging
from red_commons.logging import getLogger from red_commons.logging import getLogger
logger = getLogger('red.SeaCogs.Pterodactyl') logger = getLogger('red.seacogs.pterodactyl')
websocket_logger = getLogger('red.SeaCogs.Pterodactyl.websocket') websocket_logger = getLogger('red.seacogs.pterodactyl.websocket')
if logger.level >= logging.VERBOSE: if logger.level >= logging.VERBOSE:
websocket_logger.setLevel(logging.logging.INFO) websocket_logger.setLevel(logging.logging.INFO)
elif logger.level < logging.VERBOSE: elif logger.level < logging.VERBOSE:

View file

@ -9,7 +9,7 @@ from pydactyl import PterodactylClient
from redbot.core import app_commands, commands from redbot.core import app_commands, commands
from redbot.core.app_commands import Choice from redbot.core.app_commands import Choice
from redbot.core.bot import Red from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import box, error, humanize_list from redbot.core.utils.chat_formatting import box, error
from redbot.core.utils.views import ConfirmView from redbot.core.utils.views import ConfirmView
from pterodactyl import mcsrvstatus from pterodactyl import mcsrvstatus
@ -20,10 +20,6 @@ from pterodactyl.logger import logger
class Pterodactyl(commands.Cog): class Pterodactyl(commands.Cog):
"""Pterodactyl allows you to manage your Pterodactyl Panel from Discord.""" """Pterodactyl allows you to manage your Pterodactyl Panel from Discord."""
__author__ = ["SeaswimmerTheFsh"]
__version__ = "2.0.0"
__documentation__ = "https://seacogs.coastalcommits.com/pterodactyl/"
def __init__(self, bot: Red): def __init__(self, bot: Red):
self.bot = bot self.bot = bot
self.client: Optional[PterodactylClient] = None self.client: Optional[PterodactylClient] = None
@ -34,17 +30,6 @@ class Pterodactyl(commands.Cog):
self.task = self.get_task() self.task = self.get_task()
self.update_topic.start() self.update_topic.start()
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"Cog Version: **{self.__version__}**",
f"Author: {humanize_list(self.__author__)}",
f"Documentation: {self.__documentation__}",
]
return "\n".join(text)
async def cog_unload(self) -> None: async def cog_unload(self) -> None:
self.update_topic.cancel() self.update_topic.cancel()
self.task.cancel() self.task.cancel()
@ -74,7 +59,6 @@ class Pterodactyl(commands.Cog):
@tasks.loop(minutes=6) @tasks.loop(minutes=6)
async def update_topic(self): async def update_topic(self):
await self.bot.wait_until_red_ready()
topic = await self.get_topic() topic = await self.get_topic()
console = self.bot.get_channel(await config.console_channel()) console = self.bot.get_channel(await config.console_channel())
chat = self.bot.get_channel(await config.chat_channel()) chat = self.bot.get_channel(await config.chat_channel())
@ -148,7 +132,7 @@ class Pterodactyl(commands.Cog):
"C": str(message.author.color), "C": str(message.author.color),
"D": message.author.discriminator, "D": message.author.discriminator,
"I": str(message.author.id), "I": str(message.author.id),
"M": message.content.replace('"','').replace("\n", " "), "M": message.content.replace('"',''),
"N": message.author.display_name, "N": message.author.display_name,
"U": message.author.name, "U": message.author.name,
"V": await config.invite() or "use [p]pterodactyl config invite to change me", "V": await config.invite() or "use [p]pterodactyl config invite to change me",
@ -161,7 +145,9 @@ class Pterodactyl(commands.Cog):
if await config.api_endpoint() == "minecraft": if await config.api_endpoint() == "minecraft":
status, response = await mcsrvstatus.get_status(await config.topic_hostname(), await config.topic_port()) status, response = await mcsrvstatus.get_status(await config.topic_hostname(), await config.topic_port())
if status and 'list' in response['players']: if status and 'list' in response['players']:
output_str = '\n'.join(response['players']['list']) output_str = ''
for player in response['players']['list']:
output_str += f"{player}\n"
return output_str, response['players']['list'] return output_str, response['players']['list']
return None return None
@ -545,7 +531,7 @@ class Pterodactyl(commands.Cog):
await view.wait() await view.wait()
if view.result is True: if view.result is True:
blacklist.update({name: regex}) blacklist.update({name: regex})
await msg.edit(content=f"Updated `{name}` in the regex blacklist.\n{box(regex, 're')}") await msg.edit(f"Updated `{name}` in the regex blacklist.\n{box(regex, 're')}")
else: else:
await msg.edit(content="Cancelled.") await msg.edit(content="Cancelled.")
@ -560,7 +546,7 @@ class Pterodactyl(commands.Cog):
await view.wait() await view.wait()
if view.result is True: if view.result is True:
del blacklist[name] del blacklist[name]
await msg.edit(content=f"Removed `{name}` from the regex blacklist.") await msg.edit(content="Removed `{name}` from the regex blacklist.")
else: else:
await msg.edit(content="Cancelled.") await msg.edit(content="Cancelled.")
else: else:

View file

@ -15,7 +15,6 @@ from pterodactyl.pterodactyl import Pterodactyl
async def establish_websocket_connection(coginstance: Pterodactyl) -> None: async def establish_websocket_connection(coginstance: Pterodactyl) -> None:
await coginstance.bot.wait_until_red_ready()
base_url = await config.base_url() base_url = await config.base_url()
base_url = base_url[:-1] if base_url.endswith('/') else base_url base_url = base_url[:-1] if base_url.endswith('/') else base_url

View file

@ -5,15 +5,12 @@ description = "My assorted cogs for Red-DiscordBot."
authors = ["SeaswimmerTheFsh"] authors = ["SeaswimmerTheFsh"]
license = "MPL 2" license = "MPL 2"
readme = "README.md" readme = "README.md"
package-mode = false
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.11,<3.12" python = ">=3.11,<3.12"
Red-DiscordBot = "^3.5.5" Red-DiscordBot = "^3.5.5"
py-dactyl = "^2.0.4" py-dactyl = "^2.0.4"
websockets = "^12.0" websockets = "^12.0"
pillow = "^10.3.0"
numpy = "^1.26.4"
[tool.poetry.group.dev] [tool.poetry.group.dev]
optional = true optional = true

5
welcomer/__init__.py Normal file
View file

@ -0,0 +1,5 @@
from .welcomer import Welcomer
async def setup(bot):
await bot.add_cog(Welcomer(bot))

17
welcomer/info.json Normal file
View file

@ -0,0 +1,17 @@
{
"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.",
"name" : "Nerdify",
"short" : "Nerdify your text!",
"description" : "Nerdify your text!",
"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, 8, 0],
"tags": [
"fun",
"text",
"meme"
]
}

39
welcomer/welcomer.py Normal file
View file

@ -0,0 +1,39 @@
# _____ _
# / ____| (_)
# | (___ ___ __ _ _____ ___ _ __ ___ _ __ ___ ___ _ __
# \___ \ / _ \/ _` / __\ \ /\ / / | '_ ` _ \| '_ ` _ \ / _ \ '__|
# ____) | __/ (_| \__ \\ V V /| | | | | | | | | | | | __/ |
# |_____/ \___|\__,_|___/ \_/\_/ |_|_| |_| |_|_| |_| |_|\___|_|
from redbot.core import commands, Config
from redbot.core.utils import chat_formatting as cf
class Welcomer(commands.Cog):
"""Manage welcome messages for new members and leave messages for former members."""
__author__ = ["SeaswimmerTheFsh"]
__version__ = "0.1.0"
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=1234567890, force_registration=True)
self.config.register_guild(
welcome_message=None,
leave_message=None,
channel=None,
enabled=False,
)
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"Cog Version: **{self.__version__}**",
f"Author: {cf.humanize_list(self.__author__)}",
]
return "\n".join(text)
def placeholders(self, )