This commit is contained in:
Valerie 2025-04-02 17:02:06 -04:00
parent e0787e48d1
commit 787a367d13
254 changed files with 41944 additions and 185 deletions

View file

@ -0,0 +1,13 @@
import json
import pathlib
from redbot.core.bot import Red
from .advanced_invite import AdvancedInvite
with open(pathlib.Path(__file__).parent / "info.json") as fp:
__red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"]
async def setup(bot: Red):
await bot.add_cog(AdvancedInvite(bot))

View file

@ -0,0 +1,451 @@
# Copyright (c) 2021 - Jojo#7791
# Licensed under MIT
import asyncio
import datetime
import logging
from typing import Any, Dict, Final, List, Optional, Union, Tuple, TypeVar, TYPE_CHECKING
import aiohttp
import discord
from redbot.core import Config, commands
from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import humanize_list, humanize_number
from .utils import *
log = logging.getLogger("red.JojoCogs.advanced_invite")
if TYPE_CHECKING:
NoneStrict = NoneConverter
else:
class NoneStrict(NoneConverter):
strict = True
async def can_invite(ctx: commands.Context) -> bool:
return await ctx.bot.is_owner(ctx.author) or await ctx.bot.is_invite_url_public()
_config_structure: Final[Dict[str, Any]] = {
"custom_url": None,
"image_url": None,
"custom_message": "Thanks for choosing {bot_name}!",
"send_in_channel": False,
"embeds": True,
"title": "Invite {bot_name}",
"support_server": None,
"footer": None,
"extra_link": False,
"support_server_emoji": {},
"invite_emoji": {},
}
class AdvancedInvite(commands.Cog):
"""An advanced invite for [botname]
To configure the invite command, check out `[p]invite set`.
"""
__authors__: Final[List[str]] = ["Jojo#7791"]
__version__: Final[str] = "4.0.0"
def __init__(self, bot: Red):
self.bot = bot
self._invite_command: Optional[commands.Command] = self.bot.remove_command("invite")
self.config = Config.get_conf(self, 544974305445019651, True)
self.config.register_global(**_config_structure)
self._supported_images: Tuple[str, ...] = ("jpg", "jpeg", "png", "gif")
async def cog_unload(self) -> None:
self.bot.remove_command("invite"), self.bot.add_command( # type:ignore
self._invite_command
) if self._invite_command else None
@staticmethod
def _humanize_list(data: List[str]) -> str:
return humanize_list([f"`{i}`" for i in data])
def format_help_for_context(self, ctx: commands.Context) -> str:
plural = "" if len(self.__authors__) == 1 else "s"
return (
f"{super().format_help_for_context(ctx)}\n"
f"**Author{plural}:** {self._humanize_list(self.__authors__)}\n"
f"**Version:** `{self.__version__}`"
)
async def red_delete_data_for_user(self, *args, **kwargs) -> None:
"""Nothing to delete"""
return
@commands.command(hidden=True)
async def inviteversion(self, ctx: commands.Context):
"""Get the version of Advanced Invite"""
msg = (
"**Advanced Invite**\n"
f"Version: `{self.__version__}`\n\n"
'"This cog was created for a friend of mine, and as such is close to my heart.\n'
'Thanks for being awesome and using my stuff!" - Jojo (the author of this cog)\n\n'
"Created with ❤"
)
await ctx.maybe_send_embed(msg)
@commands.group(name="invite", usage="", invoke_without_command=True)
@commands.check(can_invite)
async def invite(self, ctx: commands.Context, send_in_channel: Optional[bool] = False):
"""Invite [botname] to your server!"""
if TYPE_CHECKING:
channel: discord.TextChannel
else:
channel = (
await self._get_channel(ctx)
if not send_in_channel and await self.bot.is_owner(ctx.author)
else ctx.channel
)
settings = await self.config.all()
title, message = self._get_items(settings, ["title", "custom_message"], ctx)
url = await self.bot.get_invite_url()
time = datetime.datetime.now(tz=datetime.timezone.utc)
support_msg = (
f"Join the support server <{support}>\n"
if (support := settings.get("support_server")) else ""
)
invite_emoji, support_emoji = (
Emoji.from_data(settings.get(x)) for x in ("invite_emoji", "support_emoji")
)
footer = settings.get("footer")
if footer:
footer = footer.replace("{bot_name}", ctx.me.name).replace(
"{guild_count}", humanize_number(len(ctx.bot.guilds))
).replace(
"{user_count}",
humanize_number(len(self.bot.users))
)
kwargs: Dict[str, Any] = {
"content": (
f"**{title}**\n\n{message}\n<{url}>\n{support_msg}\n\n{footer}"
),
}
if await self._embed_requested(ctx, channel):
message = f"{message}\n{url}" if settings.get("extra_link") else f"[{message}]({url})"
embed = discord.Embed(
title=title,
description=message,
colour=await ctx.embed_colour(),
timestamp=time,
)
if support:
embed.add_field(name="Join the support server", value=support)
if curl := settings.get("custom_url"):
embed.set_thumbnail(url=curl)
if iurl := settings.get("image_url"):
embed.set_image(url=iurl)
if footer:
embed.set_footer(text=footer)
kwargs = {"embed": embed}
view = discord.ui.View()
view.add_item(self._make_button(url, f"Invite {ctx.me.name}", invite_emoji))
if support:
view.add_item(self._make_button(support, "Join the support server", support_emoji))
kwargs["view"] = view
try:
await channel.send(**kwargs)
except discord.Forbidden: # Couldn't dm
if channel == ctx.author.dm_channel:
return await ctx.send("I could not dm you!")
await ctx.send(
"I'm sorry, something went wrong when trying to send the invite."
"Please let my owner know if this problem continues."
)
except discord.HTTPException:
await ctx.send(
"I'm sorry, something went wrong when trying to send the invite."
"Please let my owner know if this problem continues."
)
@staticmethod
def _make_button(url: str, label: str, emoji: Optional[Emoji]) -> discord.ui.Button:
emoji_data = emoji.as_emoji() if emoji else None
return discord.ui.Button(style=discord.ButtonStyle.url, label=label, url=url, emoji=emoji_data)
@staticmethod
def _get_items(settings: dict, keys: List[str], ctx: commands.Context) -> list:
return [
settings.get(key, _config_structure[key]).replace(
"{bot_name}", ctx.me.name,
) for key in keys
]
@invite.group(name="settings", aliases=("set",))
@commands.is_owner()
async def invite_settings(self, ctx: commands.Context):
"""Manage the settings for the invite command.
You can set the title, message, support server invite.
If you have embeds enabled you can also set the thumbnail url, and the footer.
"""
pass
@invite_settings.command(name="support")
async def invite_support(self, ctx: commands.Context, invite: InviteNoneConverter):
"""Set the support server invite.
Type `None` to reset it
**Arguments**
- `invite` The invite url for your support server. Type `None` to remove it.
"""
invite = getattr(invite, "url", invite)
set_reset = f"set as <{invite}>." if invite else "reset."
await ctx.send(f"The support server has been {set_reset}")
await self.config.support_server.set(invite)
@invite_settings.command(name="embed")
async def invite_embed(self, ctx: commands.Context, toggle: bool):
"""Set whether the invite command should use embeds.
**Arguments**
- `toggle` Whether the invite command should be embedded or not.
"""
toggled = "enabled" if toggle else "disabled"
await ctx.send(f"Embeds are now {toggled} for the invite command.")
await self.config.embeds.set(toggle)
@invite_settings.command(name="message")
async def invite_message(self, ctx: commands.Context, *, message: NoneStrict):
"""Set the message for the invite command.
Type `None` to reset it.
You can use `{bot_name}` in your message to display [botname] in the message.
**Arguments**
- `message` The message for the invite command. Type `None` to reset it.
"""
reset = False
if message is None:
reset = True
message = _config_structure["custom_message"]
elif len(message) > 1500:
return await ctx.send("The message's length cannot be more than 1500 characters.")
set_reset = "reset" if reset else "set"
await ctx.send(f"The message has been {set_reset}.")
await self.config.custom_message.set(message)
@invite_settings.command(name="title")
async def invite_title(self, ctx: commands.Context, *, title: NoneStrict):
"""Set the title for the invite command.
Type `None` to reset it.
You can use `{bot_name}` to display [botname] in the title.
**Arguments**
- `title` The title for the invite command. Type `None` to reset it.
"""
reset = False
if title is None:
reset = True
title = _config_structure["title"]
set_reset = "reset" if reset else "set"
await ctx.send(f"The title has been {set_reset}")
await self.config.title.set(title)
@invite_settings.command(name="footer")
async def invite_footer(self, ctx: commands.Context, *, footer: NoneConverter):
"""Set the footer for the invite command
**Variables**
- `{bot_name}` Displays [botname] in the footer
- `{guild_count}` Displays in how many guilds is the bot in
- `{user_count}` Displays how many users in total
**Arguments**
- `footer` The footer for the invite command.
"""
if not footer:
await self.config.footer.set(None)
return await ctx.send("The footer has been reset.")
if len(footer) > 100:
return await ctx.send("The footer's length cannot be over 100 characters long.")
await self.config.footer.set(footer)
await ctx.send("The footer has been set.")
@invite_settings.command(name="public")
async def invite_send_in_channel(self, ctx: commands.Context, toggle: bool):
"""Set whether the invite command should send in the channel it was invoked in
**Arguments**
- `toggle` Whether or not the invite command should be sent in the channel it was used in.
"""
await self.config.send_in_channel.set(toggle)
now_no_longer = "now" if toggle else "no longer"
await ctx.send(
f"The invite command will {now_no_longer} send the message in the channel it was invoked in"
)
@invite_settings.command(name="supportserveremoji", aliases=["ssemoji"])
async def support_server_emoji(self, ctx: commands.Context, emoji: EmojiConverter):
"""Set the emoji for the support server invite button.
Type "None" to remove it.
**Arguments**
- `emoji` The emoji for the support server button. Type "none" to remove it.
"""
if not emoji:
await self.config.support_server_emoji.clear()
return await ctx.send("I have reset the support server emoji.")
await self.config.support_server_emoji.set(emoji.to_dict())
await ctx.send(f"Set the support server emoji to {emoji.as_emoji()}")
@invite_settings.command(name="inviteemoji", aliases=["iemoji"])
async def invite_emoji(self, ctx: commands.Context, emoji: EmojiConverter):
"""Set the emoji for the invite button.
Type "None" to remove it.
**Arguments**
- `emoji` The emoji for the invite button. Type "none" to remove it.
"""
if not emoji:
await self.config.invite_emoji.clear()
return await ctx.send("I have reset the invite emoji.")
await self.config.invite_emoji.set(emoji.to_dict())
await ctx.send(f"Set the invite emoji to {emoji.as_emoji()}")
@invite_settings.command(name="thumbnailurl")
async def invite_custom_url(self, ctx: commands.Context, url: str = None):
"""Set the thumbnail url for the invite command's embed
This setting only applies for embeds.
**Arguments**
- `url` The thumbnail url for embed command. This can also be a file (upload the image when you run the command)
Type `none` to reset the url.
"""
if len(ctx.message.attachments) == 0 and url is None:
return await ctx.send_help()
if len(ctx.message.attachments) > 0:
if not (attach := ctx.message.attachments[0]).filename.endswith(
self._supported_images
):
return await ctx.send("That image is invalid.")
url = attach.url
elif url is not None:
if url.lower == "none":
await self.config.custom_url.clear()
return await ctx.send("I have reset the thumbnail url.")
if url.startswith("<") and url.endswith(">"):
url = url[1:-1]
if not url.endswith(self._supported_images):
return await ctx.send(
f"That url does not point to a proper image type, ({', '.join(self._supported_images)})"
)
async with ctx.typing():
async with aiohttp.ClientSession() as sess:
try:
async with sess.get(url) as re:
await re.read()
except aiohttp.InvalidURL:
return await ctx.send("That is not a valid url.")
except aiohttp.ClientError:
return await ctx.send(
"Something went wrong while trying to get the image."
)
await self.config.custom_url.set(url)
await ctx.send("Done. I have set the thumbnail url.")
@invite_settings.command(name="imageurl", usage="<url or image>")
async def invite_image_url(self, ctx: commands.Context, url: str = None):
"""Set the image url for the invite command.
This setting only applies for embeds.
**Arguments**
- `url` The url for the embed's image. Type `none` to clear it.
You can also upload an image instead of providing this argument
"""
if len(ctx.message.attachments) > 0:
# Attachments take priority
if not (attach := ctx.message.attachments[0]).filename.endswith(
self._supported_images
):
return await ctx.send("That image is invalid.")
url = attach.url
elif url is not None:
if url == "none":
await self.config.image_url.clear()
return await ctx.send("Reset the image url.")
async with ctx.typing():
try:
async with aiohttp.request("GET", url) as re:
await re.read()
except aiohttp.InvalidURL:
return await ctx.send("That url is invalid.")
except aiohttp.ClientError:
return await ctx.send("Something went wrong when trying to validate that url.")
else:
# as much as I hate else blocks this is hard to bypass
return await ctx.send_help()
await self.config.image_url.set(url)
await ctx.send("Done. I have set the image url.")
@invite_settings.command(name="extralink")
async def invite_extra_links(self, ctx: commands.Context, toggle: bool):
"""Toggle whether the invite command's embed should have extra links showing the invite url
**Arguments**
- `toggle` Whether the invite command's embed should have extra links.
"""
await self.config.extra_link.set(toggle)
now_no_longer = "now" if toggle else "no longer"
await ctx.send(f"Extra links are {now_no_longer} enabled.")
@invite_settings.command(name="showsettings")
async def invite_show_settings(self, ctx: commands.Context):
"""Show the settings for the invite command"""
_data: dict = {}
settings = await self.config.all()
for key, value in settings.items():
if key == "mobile_check":
continue
key = key.replace("_", " ").replace("custom ", "")
key = " ".join(x.capitalize() for x in key.split())
if key.lower() == "url":
key = "Embed Thumbnail Url"
_data[key] = value
msg = "**Invite settings**\n\n" + "\n".join(
f"**{key}:** {value}" for key, value in _data.items()
)
kwargs: dict = {"content": msg}
if await ctx.embed_requested():
embed = discord.Embed(
title="Invite settings",
colour=await ctx.embed_colour(),
timestamp=datetime.datetime.now(tz=datetime.timezone.utc),
)
[embed.add_field(name=key, value=value, inline=False) for key, value in _data.items()]
kwargs = {"embed": embed}
await ctx.send(**kwargs)
async def _get_channel(self, ctx: commands.Context) -> Union[discord.TextChannel, discord.DMChannel]:
if await self.config.send_in_channel():
return ctx.channel
if ret := ctx.author.dm_channel:
return ret
return await ctx.author.create_dm()
async def _embed_requested(self, ctx: commands.Context, channel: discord.TextChannel) -> bool:
if not await self.config.embeds():
return False
if isinstance(channel, discord.DMChannel):
return True
return channel.permissions_for(ctx.me).embed_links

17
advancedinvite/info.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "advancedinvite",
"short": "Embed the invite command",
"description": "Embeds the invite command if possible.",
"end_user_data_statement": "This cog does not store end user data.",
"install_msg": "Thanks for installing Jojo's AdvancedInvite cog!\nThis cog was requested by DSC#6238 :heart:\n\nTake a look at `[p]help invite` for setting commands.",
"author": [
"Jojo#7791"
],
"required_cogs": {},
"requirements": ["emoji"],
"tags": ["Utility"],
"min_bot_version": "3.5.0.dev0",
"hidden": false,
"disabled": false,
"type": "COG"
}

135
advancedinvite/utils.py Normal file
View file

@ -0,0 +1,135 @@
# Copyright (c) 2021 - Jojo#7791
# Licensed under MIT
import logging
from datetime import datetime
from enum import Enum
from typing import TYPE_CHECKING, Any, Dict, List, Union, Optional
import discord
try:
from emoji.unicode_codes import UNICODE_EMOJI_ENGLISH
except ImportError:
from emoji import EMOJI_DATA as UNICODE_EMOJI_ENGLISH
from redbot.core import commands
log = logging.getLogger("red.jojocogs.advancedinvite.utils")
__all__ = [
"create_doc",
"TimestampFormats",
"timestamp_format",
"NoneConverter",
"InviteNoneConverter",
"Emoji",
"EmojiConverter",
]
NoneType = type(None)
def create_doc(default: Optional[str] = None, *, override: bool = False):
"""Create a docstring if you don't wanna"""
def inner(func):
doc = default or "No Documentation"
if not func.__doc__ or override:
func.__doc__ = doc
return func
return inner
@create_doc()
class TimestampFormats(Enum):
DEFAULT = "f"
LONG_DATE_TIME = "F"
SHORT_DATE = "d"
LONG_DATE = "D"
SHORT_TIME = "t"
LONG_TIME = "T"
RELATIVE_TIME = "R"
@create_doc()
def timestamp_format(dt: Optional[datetime] = None, *, dt_format: Optional[TimestampFormats] = None) -> str:
if not dt:
dt = datetime.now()
if not dt_format or dt_format == TimestampFormats.DEFAULT:
return f"<t:{int(dt.timestamp())}>"
else:
return f"<t:{int(dt.timestamp())}:{dt_format.value}>"
if TYPE_CHECKING:
NoneConverter = Optional[str]
else:
class NoneConverter(commands.Converter):
"""A simple converter for NoneType args for commands"""
def __init__(self, *, strict: bool = False):
self.strict = strict
async def convert(self, ctx: commands.Context, arg: str) -> Union[NoneType, str]:
args = ["none"]
if not self.strict:
args.extend(["no", "nothing"])
if arg.lower() in args:
return None
return arg
if TYPE_CHECKING:
InviteNoneConverter = Union[NoneType, discord.Invite]
else:
class InviteNoneConverter(NoneConverter):
def __init__(self):
self.strict = False
async def convert(
self, ctx: commands.Context, arg: str
) -> Union[NoneType, discord.Invite]:
ret = await super().convert(ctx, arg)
if ret is None:
return ret
return await commands.InviteConverter().convert(ctx, ret)
class Emoji:
def __init__(self, data: Dict[str, Any]):
self.name = data["name"]
self.id = data.get("id", None)
self.animated = data.get("animated", None)
self.custom = self.id is not None
@classmethod
def from_data(cls, data: Union[str, Dict[str, Any]]):
log.debug(data)
if not data:
return None
if isinstance(data, str):
return cls({"name": data})
return cls(data)
def to_dict(self) -> Dict[str, Any]:
return {"name": self.name, "id": self.id}
def as_emoji(self) -> str:
if not self.custom:
return self.name
an = "a" if self.animated else ""
return f"<{an}:{self.name}:{self.id}>"
if TYPE_CHECKING:
EmojiConverter = Union[Emoji, NoneType]
else:
class EmojiConverter(commands.PartialEmojiConverter):
async def convert(self, ctx: commands.Context, arg: str) -> Union[Emoji, NoneType]:
if arg.lower() == "none":
return None
arg = arg.strip()
data = arg if arg in UNICODE_EMOJI_ENGLISH.keys() else await super().convert(ctx, arg)
data = getattr(data, "to_dict", lambda: data)()
return Emoji.from_data(data)

64
autotraceback/README.rst Normal file
View file

@ -0,0 +1,64 @@
.. _autotraceback:
=============
AutoTraceback
=============
This is the cog guide for the ``AutoTraceback`` cog. This guide contains the collection of commands which you can use in the cog.
Through this guide, ``[p]`` will always represent your prefix. Replace ``[p]`` with your own prefix when you use these commands in Discord.
.. note::
Ensure that you are up to date by running ``[p]cog update autotraceback``.
If there is something missing, or something that needs improving in this documentation, feel free to create an issue `here <https://github.com/AAA3A-AAA3A/AAA3A-cogs/issues>`_.
This documentation is generated everytime this cog receives an update.
---------------
About this cog:
---------------
A cog to display the error traceback of a command automatically after the error!
---------
Commands:
---------
Here are all the commands included in this cog (1):
* ``[p]traceback [public=True] [index=0]``
Sends to the owner the last command exception that has occurred.
------------
Installation
------------
If you haven't added my repo before, lets add it first. We'll call it "AAA3A-cogs" here.
.. code-block:: ini
[p]repo add AAA3A-cogs https://github.com/AAA3A-AAA3A/AAA3A-cogs
Now, we can install AutoTraceback.
.. code-block:: ini
[p]cog install AAA3A-cogs autotraceback
Once it's installed, it is not loaded by default. Load it by running the following command:
.. code-block:: ini
[p]load autotraceback
----------------
Further Support:
----------------
Check out my docs `here <https://aaa3a-cogs.readthedocs.io/en/latest/>`_.
Mention me in the #support_other-cogs in the `cog support server <https://discord.gg/GET4DVk>`_ if you need any help.
Additionally, feel free to open an issue or pull request to this repo.
--------
Credits:
--------
Thanks to Kreusada for the Python code to automatically generate this documentation!

62
autotraceback/__init__.py Normal file
View file

@ -0,0 +1,62 @@
from redbot.core import errors # isort:skip
import importlib
import sys
try:
import AAA3A_utils
except ModuleNotFoundError:
raise errors.CogLoadError(
"The needed utils to run the cog were not found. Please execute the command `[p]pipinstall git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
)
modules = sorted(
[module for module in sys.modules if module.split(".")[0] == "AAA3A_utils"], reverse=True
)
for module in modules:
try:
importlib.reload(sys.modules[module])
except ModuleNotFoundError:
pass
del AAA3A_utils
# import AAA3A_utils
# import json
# import os
# __version__ = AAA3A_utils.__version__
# with open(os.path.join(os.path.dirname(__file__), "utils_version.json"), mode="r") as f:
# data = json.load(f)
# needed_utils_version = data["needed_utils_version"]
# if __version__ > needed_utils_version:
# raise errors.CogLoadError(
# "The needed utils to run the cog has a higher version than the one supported by this version of the cog. Please update the cogs of the `AAA3A-cogs` repo."
# )
# elif __version__ < needed_utils_version:
# raise errors.CogLoadError(
# "The needed utils to run the cog has a lower version than the one supported by this version of the cog. Please execute the command `[p]pipinstall --upgrade git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
# )
from redbot.core.bot import Red # isort:skip
import asyncio
from redbot.core.utils import get_end_user_data_statement
from .autotraceback import AutoTraceback
__red_end_user_data_statement__ = get_end_user_data_statement(file=__file__)
old_traceback = None
async def setup_after_ready(bot: Red) -> None:
global old_traceback
await bot.wait_until_red_ready()
cog = AutoTraceback(bot)
if old_traceback := bot.get_command("traceback"):
bot.remove_command(old_traceback.name)
await bot.add_cog(cog)
async def setup(bot: Red) -> None:
asyncio.create_task(setup_after_ready(bot))
def teardown(bot: Red) -> None:
bot.add_command(old_traceback)

View file

@ -0,0 +1,142 @@
from AAA3A_utils import Cog, CogsUtils, Menu # isort:skip
from redbot.core import commands # isort:skip
from redbot.core.bot import Red # isort:skip
from redbot.core.i18n import Translator, cog_i18n # isort:skip
import discord # isort:skip
import typing # isort:skip
import traceback
from redbot.core.utils.chat_formatting import box, pagify
from .dashboard_integration import DashboardIntegration
# Credits:
# General repo credits.
_: Translator = Translator("AutoTraceback", __file__)
IGNORED_ERRORS = (
commands.UserInputError,
commands.DisabledCommand,
commands.CommandNotFound,
commands.CheckFailure,
commands.NoPrivateMessage,
commands.CommandOnCooldown,
commands.MaxConcurrencyReached,
commands.BadArgument,
commands.BadBoolArgument,
)
@cog_i18n(_)
class AutoTraceback(DashboardIntegration, Cog):
"""A cog to display the error traceback of a command automatically after the error!"""
def __init__(self, bot: Red) -> None:
super().__init__(bot=bot)
self.tracebacks: typing.List[str] = []
@commands.is_owner()
@commands.hybrid_command()
async def traceback(
self, ctx: commands.Context, public: typing.Optional[bool] = True, index: int = 0
) -> None:
"""Sends to the owner the last command exception that has occurred.
If public (yes is specified), it will be sent to the chat instead.
Warning: Sending the traceback publicly can accidentally reveal sensitive information about your computer or configuration.
**Examples:**
- `[p]traceback` - Sends the traceback to your DMs.
- `[p]traceback True` - Sends the last traceback in the current context.
**Arguments:**
- `[public]` - Whether to send the traceback to the current context. Default is `True`.
- `[index]` - The error index. `0` is the last one.
"""
if not self.tracebacks and not ctx.bot._last_exception:
raise commands.UserFeedbackCheckFailure(_("No exception has occurred yet."))
if index == 0: # Last bot exception can be set directly by cogs.
_last_exception = ctx.bot._last_exception
else:
try:
_last_exception = self.tracebacks[-(index + 1)]
except IndexError:
_last_exception = ctx.bot._last_exception
_last_exception = _last_exception.split("\n")
_last_exception[0] = _last_exception[0] + (
"" if _last_exception[0].endswith(":") else ":\n"
)
_last_exception = "\n".join(_last_exception)
_last_exception = CogsUtils.replace_var_paths(_last_exception)
if public:
try:
await Menu(pages=_last_exception, timeout=180, lang="py").start(ctx)
except discord.HTTPException:
pass
else:
return
for page in pagify(_last_exception, shorten_by=15):
try:
await ctx.author.send(box(page, lang="py"))
except discord.HTTPException:
raise commands.UserFeedbackCheckFailure(
"I couldn't send the traceback message to you in DM. "
"Either you blocked me or you disabled DMs in this server."
)
@commands.Cog.listener()
async def on_command_error(
self, ctx: commands.Context, error: commands.CommandError, unhandled_by_cog: bool = False
) -> None:
if await self.bot.cog_disabled_in_guild(cog=self, guild=ctx.guild):
return
if isinstance(error, IGNORED_ERRORS):
return
traceback_error = "".join(
traceback.format_exception(type(error), error, error.__traceback__)
)
_traceback_error = traceback_error.split("\n")
_traceback_error[0] = _traceback_error[0] + (
"" if _traceback_error[0].endswith(":") else ":\n"
)
traceback_error = "\n".join(_traceback_error)
traceback_error = CogsUtils.replace_var_paths(traceback_error)
self.tracebacks.append(traceback_error)
if ctx.author.id not in ctx.bot.owner_ids:
return
pages = [box(page, lang="py") for page in pagify(traceback_error, shorten_by=10)]
try:
await Menu(pages=pages, timeout=180, delete_after_timeout=False).start(ctx)
except discord.HTTPException:
pass
@commands.Cog.listener()
async def on_assistant_cog_add(
self, assistant_cog: typing.Optional[commands.Cog] = None
) -> None: # Vert's Assistant integration/third party.
if assistant_cog is None:
return self.get_last_command_error_traceback_for_assistant
schema = {
"name": "get_last_command_error_traceback_for_assistant",
"description": "Get the traceback of the last command error occured on the bot.",
"parameters": {"type": "object", "properties": {}, "required": []},
}
await assistant_cog.register_function(cog_name=self.qualified_name, schema=schema)
async def get_last_command_error_traceback_for_assistant(
self, user: typing.Union[discord.Member, discord.User], *args, **kwargs
):
if user.id not in self.bot.owner_ids:
return "Only bot owners can view errors tracebacks."
if not self.bot._last_exception:
return "No last command error recorded."
last_traceback = self.bot._last_exception
last_traceback = CogsUtils.replace_var_paths(last_traceback)
data = {
"Last command error traceback": f"\n{last_traceback}",
}
return [f"{key}: {value}\n" for key, value in data.items() if value is not None]

View file

@ -0,0 +1,56 @@
from redbot.core import commands # isort:skip
from redbot.core.bot import Red # isort:skip
from redbot.core.i18n import Translator # isort:skip
import typing # isort:skip
_: Translator = Translator("AutoTraceback", __file__)
def dashboard_page(*args, **kwargs):
def decorator(func: typing.Callable):
func.__dashboard_decorator_params__ = (args, kwargs)
return func
return decorator
class DashboardIntegration:
bot: Red
tracebacks = []
@commands.Cog.listener()
async def on_dashboard_cog_add(self, dashboard_cog: commands.Cog) -> None:
if hasattr(self, "settings") and hasattr(self.settings, "commands_added"):
await self.settings.commands_added.wait()
dashboard_cog.rpc.third_parties_handler.add_third_party(self)
@dashboard_page(
name=None,
description="Display the traceback of the last occured exceptions.",
is_owner=True,
)
async def rpc_callback(self, **kwargs) -> typing.Dict[str, typing.Any]:
tracebacks = self.tracebacks.copy()
if not tracebacks:
return {"status": 0, "error_title": _("No exception has occurred yet.")}
source = """
{% for traceback in tracebacks %}
{{ traceback|highlight("python") }}
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
"""
return {
"status": 0,
"web_content": {
"source": source,
"tracebacks": kwargs["Pagination"].from_list(
tracebacks,
per_page=kwargs["extra_kwargs"].get("per_page"),
page=kwargs["extra_kwargs"].get("page"),
default_per_page=1,
default_page=len(tracebacks),
),
},
}

15
autotraceback/info.json Normal file
View file

@ -0,0 +1,15 @@
{
"author": ["AAA3A"],
"name": "AutoTraceback",
"install_msg": "Thank you for installing this cog!\nDo `[p]help CogName` to get the list of commands and their description. If you enjoy my work, please consider donating on [Buy Me a Coffee](<https://www.buymeacoffee.com/aaa3a>) or [Ko-Fi](<https://ko-fi.com/aaa3a>)!",
"short": "A cog to display the error traceback of a command automatically after the error!",
"description": "A cog to display the error traceback of a command automatically after the error! If you are developing cogs for Red for example, you don't need to use the traceback command every time you run a new command.",
"tags": [
"error",
"traceback",
"dev"
],
"requirements": ["git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git"],
"min_bot_version": "3.5.0",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: German\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: de\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: de_DE\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Ein Zahnrad, um den Fehler-Traceback eines Befehls automatisch nach dem Fehler anzuzeigen!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Es ist noch keine Ausnahme aufgetreten."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Greek\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: el\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: el_GR\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Ένα γρανάζι για την αυτόματη εμφάνιση της αναδρομής σφάλματος μιας εντολής μετά το σφάλμα!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Καμία εξαίρεση δεν έχει συμβεί ακόμη."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: es_ES\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Un engranaje para mostrar automáticamente la traza de error de un comando después del error!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Todavía no se ha producido ninguna excepción."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: fi\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: fi_FI\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Hammasratas, joka näyttää komennon virheen jäljityksen automaattisesti virheen jälkeen!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Poikkeusta ei ole vielä tapahtunut."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: fr_FR\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Un cog pour afficher le traceback de l'erreur d'une commande automatiquement après l'erreur !"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Aucune exception ne s'est encore produite."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: it\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: it_IT\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Una rotella per visualizzare automaticamente il traceback dell'errore di un comando dopo l'errore!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Non si è ancora verificata alcuna eccezione."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: ja_JP\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "コマンドのエラートレースバックをエラーの後に自動的に表示するためのコグです!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "まだ例外は発生していません。"

View file

@ -0,0 +1,23 @@
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-12-29 10:43+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid ""
"A cog to display the error traceback of a command automatically after the "
"error!"
msgstr ""
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr ""

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: nl\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: nl_NL\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Een tandwiel om de fouttrackback van een commando automatisch weer te geven na de fout!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Er heeft zich nog geen uitzondering voorgedaan."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: pl\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: pl_PL\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Trybik do wyświetlania śladu błędu polecenia automatycznie po wystąpieniu błędu!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Nie wystąpił jeszcze żaden wyjątek."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Portuguese, Brazilian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: pt-BR\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: pt_BR\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Uma engrenagem para mostrar o traço de erro de um comando automaticamente após o erro!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Ainda não ocorreu qualquer excepção."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: pt-PT\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: pt_PT\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Uma engrenagem para mostrar o traço de erro de um comando automaticamente após o erro!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Ainda não ocorreu qualquer excepção."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Romanian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: ro\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: ro_RO\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "O rotiță pentru a afișa automat urmărirea erorii unei comenzi după eroare!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Încă nu s-a produs nicio excepție."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: ru\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: ru_RU\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Шестеренка для автоматического отображения трассировки ошибки команды после ошибки!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Исключение пока не произошло."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-21 13:27\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: tr\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: tr_TR\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Bir komutun hata geri dönüşünü hatadan sonra otomatik olarak görüntülemek için bir cog!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Henüz hiçbir istisna gerçekleşmedi."

View file

@ -0,0 +1,28 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:17\n"
"Last-Translator: \n"
"Language-Team: Ukrainian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: uk\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/autotraceback/locales/messages.pot\n"
"X-Crowdin-File-ID: 74\n"
"Language: uk_UA\n"
#: autotraceback\autotraceback.py:34
#, docstring
msgid "A cog to display the error traceback of a command automatically after the error!"
msgstr "Гвинтик для автоматичного відображення трасування помилки команди після виникнення помилки!"
#: autotraceback\autotraceback.py:61 autotraceback\dashboard_integration.py:35
msgid "No exception has occurred yet."
msgstr "Винятків поки що не було."

View file

@ -0,0 +1 @@
{"needed_utils_version": 7.0}

20
avatar/__init__.py Normal file
View file

@ -0,0 +1,20 @@
from typing import Union
import discord
from redbot.core import app_commands
from redbot.core.bot import Red
@app_commands.context_menu(name="Avatar", extras={"red_force_enable": True})
@app_commands.user_install()
async def avatar(interaction: discord.Interaction[Red], user: Union[discord.Member, discord.User]):
await interaction.response.send_message(
embed=discord.Embed(title=f"{user.display_name} - {user.id}", color=user.color).set_image(
url=user.display_avatar.url
),
ephemeral=True,
)
async def setup(bot: Red):
bot.tree.add_command(avatar)

14
avatar/info.json Normal file
View file

@ -0,0 +1,14 @@
{
"author": [
"Zephyrkul (Zephyrkul#1089)"
],
"install_msg": "This cog requires your bot to be installed on your user account.",
"short": "Adds a simple Avatar context menu to your user-installed bot.",
"description": "Adds a simple Avatar context menu to your user-installed bot.",
"min_bot_version": "3.5.10",
"tags": [
"utility"
],
"hidden": false,
"end_user_data_statement": "This cog does not persistently store any data or metadata about users."
}

6
battleship/__init__.py Normal file
View file

@ -0,0 +1,6 @@
from .battleship import Battleship
__red_end_user_data_statement__ = 'This cog does not store user data.'
async def setup(bot):
await bot.add_cog(Battleship(bot))

154
battleship/ai.py Normal file
View file

@ -0,0 +1,154 @@
import random
class BattleshipAI():
"""
AI opponent for Battleship.
Params:
Optional[name] = str, The name for this AI.
"""
def __init__(self, name=None):
if name is None:
name = '[AI]'
self.display_name = name
self.mention = self.display_name
self.id = None
async def send(self, *args, **kwargs):
"""Absorbs attempts to DM what would normally be a human player."""
pass
def place(self, board, length):
"""Decides where to place ships."""
options = self._get_possible_ships(board, length)
if not options:
raise RuntimeError('There does not appear to be any valid location to place a ship.')
return random.choice(options)
def shoot(self, board, ship_status):
"""Picks an optimal place to shoot."""
options = []
min_len = [2, 3, 3, 4, 5][ship_status[::-1].index(None)]
max_len = [5, 4, 3, 3, 2][ship_status.index(None)]
#Replace all of the dead ship positions with misses to avoid attempting to finish the ship
for ship_num, cords in enumerate(ship_status):
if not cords:
continue
ship_len = [5, 4, 3, 3, 2][ship_num]
idx = cords[0] + (cords[1] * 10)
d = cords[2]
if d == 'r':
for n in range(ship_len):
if board[idx + n] != 2:
raise RuntimeError('Inconsistency in board and ship_status.')
board[idx + n] = 1
else:
for n in range(ship_len):
if board[idx + (n * 10)] != 2:
raise RuntimeError('Inconsistency in board and ship_status.')
board[idx + (n * 10)] = 1
#Get all of the possible ship positions with the remaining spaces
possible_ships = self._get_possible_ships(board, min_len)
#If there are any hits left, attempt to find the rest of the ship
if 2 in board:
#Try to move in a straight line with other hits
best = 0
for length in range(min_len, max_len + 1):
if length == 2: #2 length ships will not produce a line
continue
ships = self._get_possible_ships(board, length)
for cords in ships:
idx = self._cord_to_index(cords)
if cords[2] == 'r':
index = lambda i: idx + i
else:
index = lambda i: idx + (i * 10)
hits_in_ship = 0
for n in range(length):
if board[index(n)] == 2:
hits_in_ship += 1
if hits_in_ship > 1 and hits_in_ship != length:
if best == hits_in_ship:
options.append((idx, cords[2], length))
elif best < hits_in_ship:
best = hits_in_ship
options = [(idx, cords[2], length)]
if options:
break
if options:
maybe_ship = random.choice(options)
options = []
if maybe_ship[1] == 'r':
index = lambda i: maybe_ship[0] + i
else:
index = lambda i: maybe_ship[0] + (i * 10)
for n in range(maybe_ship[2]):
if board[index(n)] == 0:
options.append(self._index_to_cord(index(n)))
#If no lines exist (or existing lines do not allow for extension), attempt a random spot next to a hit.
else:
hit_indexes = []
for idx, n in enumerate(board):
if n == 2:
hit_indexes.append(idx)
for idx in hit_indexes:
if idx + 1 <= 99 and board[idx + 1] == 0:
options.append(self._index_to_cord(idx + 1))
if idx - 1 >= 0 and board[idx - 1] == 0:
options.append(self._index_to_cord(idx - 1))
if idx + 10 <= 99 and board[idx + 10] == 0:
options.append(self._index_to_cord(idx + 10))
if idx - 10 >= 0 and board[idx - 10] == 0:
options.append(self._index_to_cord(idx - 10))
#Otherwise, attack the best possible spot
else:
best = len(possible_ships)
for idx in range(100):
if board[idx] != 0:
continue
test_board = board[:] #copy the board
test_board[idx] = 1
num_remaining = len(self._get_possible_ships(test_board, min_len))
if best == num_remaining:
options.append(self._index_to_cord(idx))
elif best > num_remaining:
best = num_remaining
options = [self._index_to_cord(idx)]
if not options:
raise RuntimeError('There does not appear to be any valid location to shoot.')
return random.choice(options)
@staticmethod
def _index_to_cord(idx):
"""Converts a board index to its string representation."""
lets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
return lets[idx % 10] + str(idx // 10)
@staticmethod
def _cord_to_index(cord):
"""Converts a string cord to its board index."""
letnum = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7, 'i': 8, 'j': 9}
x = letnum[cord[0].lower()]
y = int(cord[1])
return (y * 10) + x
def _get_possible_ships(self, board, length):
"""Find all of the possible ship positions remaining for ships of a specific length."""
locations = []
for idx in range(100):
canR = True
canD = True
if 10 - length < idx % 10:
canR = False
for n in range(length):
if idx + n > 99 or board[idx + n] in (1, 3):
canR = False
if idx + (n * 10) > 99 or board[idx + (n * 10)] in (1, 3):
canD = False
cord = self._index_to_cord(idx)
if canR:
locations.append(cord + 'r')
if canD:
locations.append(cord + 'd')
return locations

215
battleship/battleship.py Normal file
View file

@ -0,0 +1,215 @@
import discord
from redbot.core import commands
from redbot.core import Config
from redbot.core import checks
import asyncio
from .game import BattleshipGame
from .ai import BattleshipAI
from .views import ConfirmView, GetPlayersView
class Battleship(commands.Cog):
"""Play battleship with one other person."""
def __init__(self, bot):
self.bot = bot
self.games = []
self.config = Config.get_conf(self, identifier=7345167901)
self.config.register_guild(
extraHit = True,
doMention = False,
doImage = True,
useThreads = False,
)
@commands.guild_only()
@commands.command()
async def battleship(self, ctx, opponent: discord.Member=None):
"""Start a game of battleship."""
if [game for game in self.games if game.channel == ctx.channel]:
return await ctx.send('A game is already running in this channel.')
if opponent is None:
view = GetPlayersView(ctx, 2)
initial_message = await ctx.send(view.generate_message(), view=view)
else:
view = ConfirmView(opponent)
initial_message = await ctx.send(f'{opponent.mention} You have been challenged to a game of Battleship by {ctx.author.display_name}!', view=view)
channel = ctx.channel
if (
await self.config.guild(ctx.guild).useThreads()
and ctx.channel.permissions_for(ctx.guild.me).create_public_threads
and ctx.channel.type is discord.ChannelType.text
):
try:
channel = await initial_message.create_thread(
name='Battleship',
reason='Automated thread for Battleship.',
)
except discord.HTTPException:
pass
await view.wait()
if opponent is None:
players = view.players
else:
if not view.result:
await channel.send(f'{opponent.display_name} does not want to play, shutting down.')
return
players = [ctx.author, opponent]
if len(players) < 2:
return await channel.send('Nobody else wants to play, shutting down.')
players = players[:2]
if [game for game in self.games if game.channel == channel]:
return await channel.send('Another game started in this channel while setting up.')
await channel.send(
'A game of battleship will be played between '
f'{" and ".join(p.display_name for p in players)}.'
)
game = BattleshipGame(ctx, channel, *players)
self.games.append(game)
@commands.guild_only()
@checks.guildowner()
@commands.command()
async def battleshipstop(self, ctx):
"""Stop the game of battleship in this channel."""
wasGame = False
for game in [g for g in self.games if g.channel == ctx.channel]:
game._task.cancel()
wasGame = True
if wasGame: #prevent multiple messages if more than one game exists for some reason
await ctx.send('The game was stopped successfully.')
else:
await ctx.send('There is no ongoing game in this channel.')
@commands.command()
async def battleshipboard(self, ctx, channel: discord.TextChannel=None):
"""
View your current board from an ongoing game in your DMs.
Specify the channel ID of the channel the game is in.
"""
if channel is None:
channel = ctx.channel
game = [game for game in self.games if game.channel.id == channel.id]
if not game:
return await ctx.send(
'There is no game in that channel or that channel does not exist.'
)
game = [g for g in game if ctx.author.id in [m.id for m in g.player]]
if not game:
return await ctx.send('You are not in that game.')
game = game[0]
p = [m.id for m in game.player].index(ctx.author.id)
await game.send_board(p, 1, ctx.author, '')
@commands.guild_only()
@checks.guildowner()
@commands.group(invoke_without_command=True)
async def battleshipset(self, ctx):
"""Config options for battleship."""
await ctx.send_help()
cfg = await self.config.guild(ctx.guild).all()
msg = (
'Extra shot on hit: {extraHit}\n'
'Mention on turn: {doMention}\n'
'Display the board using an image: {doImage}\n'
'Game contained to a thread: {useThreads}\n'
).format_map(cfg)
await ctx.send(f'```py\n{msg}```')
@battleshipset.command()
async def extra(self, ctx, value: bool=None):
"""
Set if an extra shot should be given after a hit.
Defaults to True.
This value is server specific.
"""
if value is None:
v = await self.config.guild(ctx.guild).extraHit()
if v:
await ctx.send('You are currently able to shoot again after a hit.')
else:
await ctx.send('You are currently not able to shoot again after a hit.')
else:
await self.config.guild(ctx.guild).extraHit.set(value)
if value:
await ctx.send('You will now be able to shoot again after a hit.')
else:
await ctx.send('You will no longer be able to shoot again after a hit.')
@battleshipset.command()
async def mention(self, ctx, value: bool=None):
"""
Set if players should be mentioned when their turn begins.
Defaults to False.
This value is server specific.
"""
if value is None:
v = await self.config.guild(ctx.guild).doMention()
if v:
await ctx.send('Players are being mentioned when their turn begins.')
else:
await ctx.send('Players are not being mentioned when their turn begins.')
else:
await self.config.guild(ctx.guild).doMention.set(value)
if value:
await ctx.send('Players will be mentioned when their turn begins.')
else:
await ctx.send('Players will not be mentioned when their turn begins.')
@battleshipset.command()
async def imgboard(self, ctx, value: bool=None):
"""
Set if the board should be displayed using an image.
Defaults to True.
This value is server specific.
"""
if value is None:
v = await self.config.guild(ctx.guild).doImage()
if v:
await ctx.send('The board is currently displayed using an image.')
else:
await ctx.send('The board is currently displayed using text.')
else:
await self.config.guild(ctx.guild).doImage.set(value)
if value:
await ctx.send('The board will now be displayed using an image.')
else:
await ctx.send('The board will now be displayed using text.')
@battleshipset.command()
async def thread(self, ctx, value: bool=None):
"""
Set if a thread should be created per-game to contain game messages.
Defaults to False.
This value is server specific.
"""
if value is None:
v = await self.config.guild(ctx.guild).useThreads()
if v:
await ctx.send('The game is currently run in a per-game thread.')
else:
await ctx.send('The game is not currently run in a thread.')
else:
await self.config.guild(ctx.guild).useThreads.set(value)
if value:
await ctx.send('The game will now be run in a per-game thread.')
else:
await ctx.send('The game will not be run in a thread.')
def cog_unload(self):
return [game._task.cancel() for game in self.games]
async def red_delete_data_for_user(self, **kwargs):
"""Nothing to delete."""
return

BIN
battleship/data/board.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

BIN
battleship/data/hit.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

BIN
battleship/data/len2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
battleship/data/len3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
battleship/data/len4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
battleship/data/len5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
battleship/data/miss.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 508 B

366
battleship/game.py Normal file
View file

@ -0,0 +1,366 @@
import discord
from PIL import Image
from redbot.core.data_manager import bundled_data_path
from io import BytesIO
import asyncio
import logging
from .ai import BattleshipAI
class BattleshipGame():
"""
A game of Battleship.
Params:
ctx = redbot.core.commands.context.Context, The context that created the game.
channel = discord.abc.GuildChannel, the channel where the game messages will be sent to.
p1 = discord.member.Member, The member object of player 1.
p2 = discord.member.Member, The member object of player 2.
"""
def __init__(self, ctx, channel, p1, p2):
self.ctx = ctx
self.channel = channel
self.bot = ctx.bot
self.cog = ctx.cog
self.player = [p1, p2]
self.name = [p1.display_name, p2.display_name]
self.p = 1
self.board = [[0] * 100, [0] * 100]
self.letnum = {
'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4,
'f': 5, 'g': 6, 'h': 7, 'i': 8, 'j': 9
}
self.pmsg = []
self.key = [[], []]
self.ship_pos = [[], []]
self.log = logging.getLogger('red.flamecogs.battleship')
self._task = asyncio.create_task(self.run())
self._task.add_done_callback(self.error_callback) #Thanks Sinbad <3
async def send_error(self):
"""Sends a message to the channel after an error."""
await self.channel.send(
'A fatal error has occurred, shutting down.\n'
'Please have the bot owner copy the error from console '
'and post it in the support channel of <https://discord.gg/bYqCjvu>.'
)
async def send_forbidden(self):
"""Sends a message to the channel warning that a player could not be DMed."""
await self.channel.send(
'I cannot send direct messages to one of the players. Please ensure '
'that the privacy setting "Allow direct messages from server members" '
'is enabled and that the bot is not blocked.'
)
def error_callback(self, fut):
"""Checks for errors in stopped games."""
try:
fut.result()
except asyncio.CancelledError:
pass
except discord.errors.Forbidden:
asyncio.create_task(self.send_forbidden())
self.log.warning('Canceled a game due to a discord.errors.Forbidden error.')
except Exception as exc:
asyncio.create_task(self.send_error())
msg = 'Error in Battleship.\n'
self.log.exception(msg)
self.bot.dispatch('flamecogs_game_error', self, exc)
try:
self.cog.games.remove(self)
except ValueError:
pass
def _gen_text(self, player, show_unhit):
"""
Creates a visualization of the board.
Returns a str of the board.
Params:
player = int, Which player's board to print.
show_unhit = int, Should unhit ships be shown.
"""
outputchars = [{0:'· ', 1:'O ', 2:'X ', 3:'· '}, {0:'· ', 1:'O ', 2:'X ', 3:'# '}]
output = ' ' + ' '.join(['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']) #header row
for y in range(10): #vertical positions
output += f'\n{y} '
for x in range(10): #horizontal positions
output += outputchars[show_unhit][self.board[player][(y*10)+x]]
return f'```\n{output}```'
def _gen_img(self, player, show_unhit):
"""
Creates a visualization of the board.
Returns a bytes image of the board.
Params:
player = int, Which player's board to print.
show_unhit = int, Should unhit ships be shown.
"""
path = bundled_data_path(self.cog)
img = Image.open(path / 'board.png')
hit = Image.open(path / 'hit.png')
miss = Image.open(path / 'miss.png')
ships = [
[
Image.open(path / 'len5.png'),
Image.open(path / 'len4.png'),
Image.open(path / 'len3.png'),
Image.open(path / 'len3.png'),
Image.open(path / 'len2.png')
], [
Image.open(path / 'len5destroyed.png'),
Image.open(path / 'len4destroyed.png'),
Image.open(path / 'len3destroyed.png'),
Image.open(path / 'len3destroyed.png'),
Image.open(path / 'len2destroyed.png')
]
]
#place ships
for index, pos in enumerate(self.ship_pos[player]):
x, y, d = pos
if show_unhit and not all(self.key[player][index].values()): #show a non-damaged ship
if d == 'd': #down
ships[0][index] = ships[0][index].rotate(90, expand=True)
img.paste(ships[0][index], box=((x*30)+32, (y*30)+32), mask=ships[0][index])
elif all(self.key[player][index].values()): #show a damaged ship
if d == 'd': #down
ships[1][index] = ships[1][index].rotate(90, expand=True)
img.paste(ships[1][index], box=((x*30)+32, (y*30)+32), mask=ships[1][index])
#place hit/miss markers
for y in range(10):
for x in range(10):
if self.board[player][((y)*10)+x] == 1: #miss
img.paste(miss, box=((x*30)+32, (y*30)+32), mask=miss)
elif self.board[player][((y)*10)+x] == 2: #hit
img.paste(hit, box=((x*30)+32, (y*30)+32), mask=hit)
temp = BytesIO()
temp.name = 'board.png'
img.save(temp)
temp.seek(0)
return temp
async def update_dm(self, player):
"""
Update the DM board for a specific player.
Only updates the board if the board is not an image.
Params:
player = int, Which player's board to print.
"""
if not await self.cog.config.guild(self.channel.guild).doImage():
if self.pmsg[player]:
content = self._gen_text(player, 1)
await self.pmsg[player].edit(content=content)
async def send_board(self, player, show_unhit, dest, msg):
"""
Send either an image of the board or a text representation of the board.
player = int, Which player's board to print.
show_unhit = int, Should unhit ships be shown.
dest = Union[discord.User, discord.abc.GuildChannel], Where to send to.
msg = str, Text to include with the board.
"""
if isinstance(dest, BattleshipAI):
return
if await self.cog.config.guild(self.channel.guild).doImage():
if isinstance(dest, (discord.User, discord.Member)):
filesize_limit = 8388608
attach_files = True
else:
filesize_limit = dest.guild.filesize_limit
attach_files = dest.permissions_for(dest.guild.me).attach_files
if attach_files:
img = self._gen_img(player, show_unhit)
file_size = img.tell()
img.seek(0)
if file_size <= filesize_limit:
file = discord.File(img, 'board.png')
await dest.send(file=file)
if msg:
await dest.send(msg)
return
content = self._gen_text(player, show_unhit)
m = await dest.send(f'{content}{msg}')
return m
async def _place(self, player, length, value):
"""
Add a ship to the board.
Returns True when the ship is successfully placed.
Returns False and sends a message when the ship cannot be placed.
Params:
player = int, Which player's board to place to.
length = int, Length of the ship to place.
value = str, The XYD to place ship at.
"""
hold = {}
try:
x = self.letnum[value[0]]
except (KeyError, IndexError):
await self.player[player].send('Invalid input, x cord must be a letter from A-J.')
return False
try:
y = int(value[1])
except (ValueError, IndexError):
await self.player[player].send('Invalid input, y cord must be a number from 0-9.')
return False
try:
d = value[2]
except IndexError:
await self.player[player].send('Invalid input, d cord must be a direction of d or r.')
return False
try:
if d == 'r': #right
if 10 - length < x: #ship would wrap over right edge
await self.player[player].send('Invalid input, too far to the right.')
return False
for z in range(length):
if self.board[player][(y*10)+x+z] != 0: #a spot taken by another ship
await self.player[player].send(
'Invalid input, another ship is in that range.'
)
return False
for z in range(length):
self.board[player][(y*10)+x+z] = 3
hold[(y*10)+x+z] = 0
elif d == 'd': #down
for z in range(length):
if self.board[player][((y+z)*10)+x] != 0: #a spot taken by another ship
await self.player[player].send(
'Invalid input, another ship is in that range.'
)
return False
for z in range(length):
self.board[player][((y+z)*10)+x] = 3
hold[((y+z)*10)+x] = 0
else:
await self.player[player].send(
'Invalid input, d cord must be a direction of d or r.'
)
return False
except IndexError:
await self.player[player].send('Invalid input, too far down.')
return False
self.key[player].append(hold)
self.ship_pos[player].append((x, y, d))
return True
async def run(self):
"""
Runs the actual game.
Should only be called by __init__.
"""
for x in range(2): #each player
await self.channel.send(f'Messaging {self.name[x]} for setup now.')
privateMessage = await self.player[x].send(
f'{self.name[x]}, it is your turn to set up your ships.\n'
'Place ships by entering the top left coordinate using the letter of the column '
'followed by the number of the row and the direction of (r)ight or (d)own '
'in ColumnRowDirection format (such as c2r).'
)
for ship_len in [5, 4, 3, 3, 2]: #each ship length
await self.send_board(x, 1, self.player[x], f'Place your {ship_len} length ship.')
while True:
if isinstance(self.player[x], BattleshipAI):
await asyncio.sleep(1)
cords = self.player[x].place(self.board[x], ship_len)
else:
try:
cords = await self.bot.wait_for(
'message',
timeout=120,
check=lambda m: (
m.channel == privateMessage.channel
and not m.author.bot
)
)
cords = cords.content
except asyncio.TimeoutError:
await self.channel.send(f'{self.name[x]} took too long, shutting down.')
return
if await self._place(x, ship_len, cords.lower()): #only break if _place succeeded
break
m = await self.send_board(x, 1, self.player[x], '')
self.pmsg.append(m) #save this message for editing later
pswap = {1:0, 0:1} #helper to swap player
while True:
self.p = pswap[self.p] #swap players
if await self.cog.config.guild(self.channel.guild).doMention(): #should player be mentioned
mention = self.player[self.p].mention
else:
mention = self.name[self.p]
await self.channel.send(f'{mention}\'s turn!')
await self.send_board(
pswap[self.p], 0, self.channel, f'{self.name[self.p]}, take your shot.'
)
while True:
if isinstance(self.player[self.p], BattleshipAI):
safe_board = [i if i != 3 else 0 for i in self.board[pswap[self.p]]]
ship_status = []
for idx, ship_dict in enumerate(self.key[pswap[self.p]]):
if all(ship_dict.values()):
ship_status.append(self.ship_pos[pswap[self.p]][idx])
else:
ship_status.append(None)
cords = self.player[self.p].shoot(safe_board, ship_status)
cords = cords.lower()
else:
try:
cords = await self.bot.wait_for(
'message',
timeout=120,
check=lambda m: (
m.author == self.player[self.p]
and m.channel == self.channel
and len(m.content) == 2
)
)
cords = cords.content.lower()
except asyncio.TimeoutError:
await self.channel.send('You took too long, shutting down.')
return
try: #makes sure input is valid
x = self.letnum[cords[0]]
y = int(cords[1])
except (ValueError, KeyError, IndexError):
continue
if self.board[pswap[self.p]][(y*10)+x] == 0:
self.board[pswap[self.p]][(y*10)+x] = 1
await self.update_dm(pswap[self.p])
await self.send_board(pswap[self.p], 0, self.channel, '**Miss!**')
break
elif self.board[pswap[self.p]][(y*10)+x] in [1, 2]:
await self.channel.send('You already shot there!')
elif self.board[pswap[self.p]][(y*10)+x] == 3:
self.board[pswap[self.p]][(y*10)+x] = 2
#DEAD SHIP
ship_dead = None
for a in range(5):
if (y*10)+x in self.key[pswap[self.p]][a]:
self.key[pswap[self.p]][a][(y*10)+x] = 1
if all(self.key[pswap[self.p]][a].values()): #if ship destroyed
ship_dead = [5, 4, 3, 3, 2][a]
await self.update_dm(pswap[self.p])
if ship_dead:
msg = (
f'**Hit!**\n**{self.name[pswap[self.p]]}\'s '
f'{ship_dead} length ship was destroyed!**'
)
await self.send_board(pswap[self.p], 0, self.channel, msg)
else:
await self.send_board(pswap[self.p], 0, self.channel, '**Hit!**')
#DEAD PLAYER
if 3 not in self.board[pswap[self.p]]:
await self.channel.send(f'**{self.name[self.p]} wins!**')
return
if await self.cog.config.guild(self.channel.guild).extraHit():
await self.channel.send('Take another shot.')
else:
break

12
battleship/info.json Normal file
View file

@ -0,0 +1,12 @@
{
"author" : ["Flame442"],
"install_msg" : "Thanks for installing battleship. Use `[p]battleship` to play.\nThis cog comes with bundled data.",
"name" : "Battleship",
"short" : "Play battleship with a friend.",
"requirements" : ["pillow"],
"description" : "Play battleship against a friend or an AI opponent. Generates an image to represent the board state. Run [p]battleship to play!",
"tags" : ["fun", "games", "battleship"],
"min_bot_version": "3.5.0.dev1",
"min_python_version": [3, 6, 0],
"end_user_data_statement": "This cog does not store user data."
}

106
battleship/views.py Normal file
View file

@ -0,0 +1,106 @@
import discord
from .ai import BattleshipAI
class ConfirmView(discord.ui.View):
def __init__(self, member: discord.Member):
super().__init__(timeout=60)
self.member = member
self.result = False
async def interaction_check(self, interaction):
if interaction.user.id != self.member.id:
await interaction.response.send_message(content='You are not allowed to interact with this button.', ephemeral=True)
return False
return True
@discord.ui.button(label='Accept', style=discord.ButtonStyle.green)
async def yes(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.edit_message(view=None)
self.result = True
self.stop()
@discord.ui.button(label='Deny', style=discord.ButtonStyle.red)
async def no(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.edit_message(view=None)
self.stop()
class GetPlayersView(discord.ui.View):
"""View to gather the players that will play in a game."""
def __init__(self, ctx, max_players):
super().__init__(timeout=60)
self.ctx = ctx
self.max_players = max_players
self.players = [ctx.author]
def generate_message(self):
"""Generates a message to show the players currently added to the game."""
msg = ""
for idx, player in enumerate(self.players, start=1):
msg += f"Player {idx} - {player.display_name}\n"
msg += (
f"\nClick the `Join Game` button to join. Up to {self.max_players} players can join. "
"To start with less than that many, use the `Start Game` button to begin."
)
return msg
async def interaction_check(self, interaction):
if len(self.players) >= self.max_players:
await interaction.response.send_message(content='The game is full.', ephemeral=True)
return False
if interaction.user.id != self.ctx.author.id and interaction.user in self.players:
await interaction.response.send_message(
content='You have already joined the game. Please wait for others to join or for the game to be started.',
ephemeral=True,
)
return False
return True
@discord.ui.button(label="Join Game", style=discord.ButtonStyle.blurple)
async def join(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Allows a user not currently added to join."""
if interaction.user.id == self.ctx.author.id:
await interaction.response.send_message(
content='You have already joined the game. You can add AI players or start the game early with the other two buttons.',
ephemeral=True,
)
return
self.players.append(interaction.user)
#self.start.disabled = False
if len(self.players) >= self.max_players:
view = None
self.stop()
else:
view = self
await interaction.response.edit_message(content=self.generate_message(), view=view)
@discord.ui.button(label="Play vs AI", style=discord.ButtonStyle.blurple)
async def ai(self, interaction: discord.Interaction, button: discord.ui.Button):
"""Fills the next player slot with an AI player."""
if interaction.user.id != self.ctx.author.id:
await interaction.response.send_message(
content='Only the host can use this button.',
ephemeral=True,
)
return
self.players.append(BattleshipAI(self.ctx.guild.me.display_name))
#self.start.disabled = False
if len(self.players) >= self.max_players:
view = None
self.stop()
else:
view = self
await interaction.response.edit_message(content=self.generate_message(), view=view)
#@discord.ui.button(label="Start Game", style=discord.ButtonStyle.green, disabled=True)
#async def start(self, interaction: discord.Interaction, button: discord.ui.Button):
# """Starts the game with less than max_players players."""
# if interaction.user.id != self.ctx.author.id:
# await interaction.response.send_message(
# content='Only the host can use this button.',
# ephemeral=True,
# )
# return
# await interaction.response.edit_message(view=None)
# self.stop()

37
cogcount/__init__.py Normal file
View file

@ -0,0 +1,37 @@
"""
MIT License
Copyright (c) 2021-present Kuro-Rui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import json
from pathlib import Path
from redbot.core.bot import Red
from .cogcount import CogCount
with open(Path(__file__).parent / "info.json") as fp:
__red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"]
async def setup(bot: Red):
await bot.add_cog(CogCount(bot))

83
cogcount/cogcount.py Normal file
View file

@ -0,0 +1,83 @@
"""
MIT License
Copyright (c) 2021-present Kuro-Rui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import discord
import kuroutils
from redbot.core import commands
from redbot.core.bot import Red
from redbot.core.commands.converter import CogConverter
class CogCount(kuroutils.Cog):
"""Count [botname]'s cogs and commands."""
__author__ = ["Kuro"]
__version__ = "0.0.1"
def __init__(self, bot: Red):
super().__init__(bot)
@commands.is_owner()
@commands.group()
async def count(self, ctx: commands.Context):
"""See how many cogs/commands [botname] has."""
pass
@commands.is_owner()
@count.command()
async def cogs(self, ctx: commands.Context):
"""See how many cogs [botname] has."""
total = len(set(await self.bot._cog_mgr.available_modules()))
loaded = len(set(self.bot.extensions.keys()))
unloaded = total - loaded
description = (
f"`Loaded :` **{loaded}** Cogs.\n"
f"`Unloaded :` **{unloaded}** Cogs.\n"
f"`Total :` **{total}** Cogs."
)
if not await ctx.embed_requested():
await ctx.send(f"**Cogs**\n\n{description}")
return
embed = discord.Embed(
title="Cogs Count", description=description, color=await ctx.embed_color()
)
await ctx.send(embed=embed)
@commands.is_owner()
@count.command()
async def commands(self, ctx: commands.Context, cog: CogConverter = None):
"""
See how many commands [botname] has.
You can also provide a cog name to see how many commands is in that cog.
The commands count includes subcommands.
"""
if cog:
commands = len(set(cog.walk_commands()))
await ctx.send(f"I have `{commands}` commands on that cog.")
return
commands = len(set(self.bot.walk_commands()))
await ctx.send(f"I have `{commands}` commands.")

13
cogcount/info.json Normal file
View file

@ -0,0 +1,13 @@
{
"author": ["Kuro"],
"description": "Have you ever wondered, how many commands/cogs does your bot has? Well this cog can help with that!",
"disabled": false,
"end_user_data_statement": "This cog does not store any end user data.",
"hidden": false,
"install_msg": "Thanks for installing `CogCount`! Get started with `[p]count`.\nThis cog has docs! Check it out at <https://kuro-cogs.readthedocs.io/en/latest/cogs/cogcount.html>.",
"name": "CogCount",
"requirements": ["git+https://github.com/Kuro-Rui/Kuro-Utils"],
"short": "A cog that shows how many commands/cogs the bot has.",
"tags": ["cogs", "commands", "count"],
"type": "COG"
}

71
cogpaths/README.rst Normal file
View file

@ -0,0 +1,71 @@
.. _cogpaths:
========
CogPaths
========
This is the cog guide for the 'CogPaths' cog. This guide
contains the collection of commands which you can use in the cog.
Through this guide, ``[p]`` will always represent your prefix. Replace
``[p]`` with your own prefix when you use these commands in Discord.
.. note::
This guide was last updated for version 1.1.0. Ensure
that you are up to date by running ``[p]cog update cogpaths``.
If there is something missing, or something that needs improving
in this documentation, feel free to create an issue `here <https://github.com/Kreusada/Kreusada-Cogs/issues>`_.
This documentation is auto-generated everytime this cog receives an update.
--------------
About this cog
--------------
Get information about a cog's paths.
--------
Commands
--------
Here are all the commands included in this cog (1):
+----------------+--------------------------+
| Command | Help |
+================+==========================+
| ``[p]cogpath`` | Get the paths for a cog. |
+----------------+--------------------------+
------------
Installation
------------
If you haven't added my repo before, lets add it first. We'll call it
"kreusada-cogs" here.
.. code-block::
[p]repo add kreusada-cogs https://github.com/Kreusada/Kreusada-Cogs
Now, we can install CogPaths.
.. code-block::
[p]cog install kreusada-cogs cogpaths
Once it's installed, it is not loaded by default. Load it by running the following
command:
.. code-block::
[p]load cogpaths
---------------
Further Support
---------------
For more support, head over to the `cog support server <https://discord.gg/GET4DVk>`_,
I have my own channel over there at #support_kreusada-cogs. Feel free to join my
`personal server <https://discord.gg/JmCFyq7>`_ whilst you're here.

10
cogpaths/__init__.py Normal file
View file

@ -0,0 +1,10 @@
from redbot.core.bot import Red
from redbot.core.utils import get_end_user_data_statement
from .cogpaths import CogPaths
__red_end_user_data_statement__ = get_end_user_data_statement(__file__)
async def setup(bot: Red):
await bot.add_cog(CogPaths(bot))

41
cogpaths/cogpaths.py Normal file
View file

@ -0,0 +1,41 @@
import inspect
import os
import pathlib
from redbot.core import commands, data_manager
from redbot.core.bot import Red
from redbot.core.commands import CogConverter
from redbot.core.config import Config
from redbot.core.utils.chat_formatting import box
class CogPaths(commands.Cog):
"""Get information about a cog's paths."""
__author__ = "Kreusada"
__version__ = "1.1.0"
def __init__(self, bot: Red):
self.bot = bot
def format_help_for_context(self, ctx: commands.Context) -> str:
context = super().format_help_for_context(ctx)
return f"{context}\n\nAuthor: {self.__author__}\nVersion: {self.__version__}"
async def red_delete_data_for_user(self, **kwargs):
return
@commands.is_owner()
@commands.command(aliases=["cogpaths"])
async def cogpath(self, ctx: commands.Context, cog: CogConverter):
"""Get the paths for a cog."""
cog_path = pathlib.Path(inspect.getfile(cog.__class__)).parent.resolve()
cog_data_path = pathlib.Path(data_manager.cog_data_path() / cog.qualified_name).resolve()
if not os.path.exists(cog_data_path):
cog_data_path = None
if not isinstance(getattr(cog, "config", None), Config):
reason = "This cog does not store any data, or does not use Red's Config API."
else:
reason = "This cog had its data directory removed."
message = "Cog path: {}\nData path: {}".format(cog_path, cog_data_path or reason)
await ctx.send(box(message, lang="yaml"))

16
cogpaths/info.json Normal file
View file

@ -0,0 +1,16 @@
{
"author": [
"Kreusada"
],
"description": "Get various paths for a cog.",
"install_msg": "Thanks for installing, have fun. Please refer to my docs if you need any help: https://kreusadacogs.readthedocs.io/en/latest/cog_cogpaths.html",
"short": "Get various paths for a cog.",
"name": "CogPaths",
"tags": [
"Paths",
"Cogs"
],
"requirements": [],
"type": "COG",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

674
consolelogs/LICENSE Normal file
View file

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

82
consolelogs/README.rst Normal file
View file

@ -0,0 +1,82 @@
.. _consolelogs:
===========
ConsoleLogs
===========
This is the cog guide for the ``ConsoleLogs`` cog. This guide contains the collection of commands which you can use in the cog.
Through this guide, ``[p]`` will always represent your prefix. Replace ``[p]`` with your own prefix when you use these commands in Discord.
.. note::
Ensure that you are up to date by running ``[p]cog update consolelogs``.
If there is something missing, or something that needs improving in this documentation, feel free to create an issue `here <https://github.com/AAA3A-AAA3A/AAA3A-cogs/issues>`_.
This documentation is generated everytime this cog receives an update.
---------------
About this cog:
---------------
A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!
---------
Commands:
---------
Here are all the commands included in this cog (7):
* ``[p]consolelogs [lines_break=2] ["critical"|"error"|"warning"|"info"|"debug"|"trace"|"node"|"criticals"|"errors"|"warnings"|"infos"|"debugs"|"traces"|"nodes"] [ids] [logger_name]``
View a console log, for a provided level/logger name.
* ``[p]consolelogs addchannel <channel> [global_errors=True] [prefixed_commands_errors=True] [slash_commands_errors=True] [dpy_ignored_exceptions=False] [full_console=False] [guild_invite=True] [ignored_cogs]``
Enable errors logging in a channel.
* ``[p]consolelogs getdebugloopsstatus``
Get an embed to check loops status.
* ``[p]consolelogs removechannel <channel>``
Disable errors logging in a channel.
* ``[p]consolelogs scroll [lines_break=2] ["critical"|"error"|"warning"|"info"|"debug"|"trace"|"node"|"criticals"|"errors"|"warnings"|"infos"|"debugs"|"traces"|"nodes"] [ids] [logger_name]``
Scroll the console logs, for all levels/loggers or provided level/logger name.
* ``[p]consolelogs stats``
Display the stats for the bot logs since the bot start.
* ``[p]consolelogs view [index=-1] ["critical"|"error"|"warning"|"info"|"debug"|"trace"|"node"|"criticals"|"errors"|"warnings"|"infos"|"debugs"|"traces"|"nodes"] [ids] [logger_name]``
View the console logs one by one, for all levels/loggers or provided level/logger name.
------------
Installation
------------
If you haven't added my repo before, lets add it first. We'll call it "AAA3A-cogs" here.
.. code-block:: ini
[p]repo add AAA3A-cogs https://github.com/AAA3A-AAA3A/AAA3A-cogs
Now, we can install ConsoleLogs.
.. code-block:: ini
[p]cog install AAA3A-cogs consolelogs
Once it's installed, it is not loaded by default. Load it by running the following command:
.. code-block:: ini
[p]load consolelogs
----------------
Further Support:
----------------
Check out my docs `here <https://aaa3a-cogs.readthedocs.io/en/latest/>`_.
Mention me in the #support_other-cogs in the `cog support server <https://discord.gg/GET4DVk>`_ if you need any help.
Additionally, feel free to open an issue or pull request to this repo.
--------
Credits:
--------
Thanks to Kreusada for the Python code to automatically generate this documentation!

47
consolelogs/__init__.py Normal file
View file

@ -0,0 +1,47 @@
from redbot.core import errors # isort:skip
import importlib
import sys
try:
import AAA3A_utils
except ModuleNotFoundError:
raise errors.CogLoadError(
"The needed utils to run the cog were not found. Please execute the command `[p]pipinstall git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
)
modules = sorted(
[module for module in sys.modules if module.split(".")[0] == "AAA3A_utils"], reverse=True
)
for module in modules:
try:
importlib.reload(sys.modules[module])
except ModuleNotFoundError:
pass
del AAA3A_utils
# import AAA3A_utils
# import json
# import os
# __version__ = AAA3A_utils.__version__
# with open(os.path.join(os.path.dirname(__file__), "utils_version.json"), mode="r") as f:
# data = json.load(f)
# needed_utils_version = data["needed_utils_version"]
# if __version__ > needed_utils_version:
# raise errors.CogLoadError(
# "The needed utils to run the cog has a higher version than the one supported by this version of the cog. Please update the cogs of the `AAA3A-cogs` repo."
# )
# elif __version__ < needed_utils_version:
# raise errors.CogLoadError(
# "The needed utils to run the cog has a lower version than the one supported by this version of the cog. Please execute the command `[p]pipinstall --upgrade git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
# )
from redbot.core.bot import Red # isort:skip
from redbot.core.utils import get_end_user_data_statement
from .consolelogs import ConsoleLogs
__red_end_user_data_statement__ = get_end_user_data_statement(file=__file__)
async def setup(bot: Red) -> None:
cog = ConsoleLogs(bot)
await bot.add_cog(cog)

798
consolelogs/consolelogs.py Normal file
View file

@ -0,0 +1,798 @@
from AAA3A_utils import Cog, CogsUtils, Menu, Loop # isort:skip
from redbot.core import commands, Config # isort:skip
from redbot.core.bot import Red # isort:skip
from redbot.core.i18n import Translator, cog_i18n # isort:skip
import discord # isort:skip
import typing # isort:skip
from redbot import __version__ as red_version
from redbot.core import data_manager
from redbot.core.utils.chat_formatting import box, humanize_list, pagify
try:
from redbot.core._events import INTRO
except ModuleNotFoundError: # Lemon's fork.
INTRO = ""
import asyncio
import datetime
import logging
import re
import traceback
from collections import Counter
from dataclasses import dataclass
from io import BytesIO, TextIOWrapper
from colorama import Fore
from rich import box as rich_box
from rich import print as rich_print
from rich.columns import Columns
from rich.panel import Panel
from rich.table import Table
from .dashboard_integration import DashboardIntegration
# Credits:
# General repo credits.
# Thanks to Tobotimus for the part to get logs files lines (https://github.com/Tobotimus/Tobo-Cogs/blob/V3/errorlogs/errorlogs.py)!
# Thanks to Trusty for the part to get the "message" content for slash commands (https://github.com/TrustyJAID/Trusty-cogs/blob/master/extendedmodlog/eventmixin.py#L222-L249!
_: Translator = Translator("ConsoleLogs", __file__)
LATEST_LOG_RE = re.compile(r"latest(?:-part(?P<part>\d+))?\.log")
CONSOLE_LOG_RE = re.compile(
r"^\[(?P<time_str>.*?)\] \[(?P<level>.*?)\] (?P<logger_name>.*?): (?P<message>.*)"
)
IGNORED_ERRORS = (
commands.UserInputError,
commands.DisabledCommand,
commands.CommandNotFound,
commands.CheckFailure,
commands.NoPrivateMessage,
commands.CommandOnCooldown,
commands.MaxConcurrencyReached,
commands.BadArgument,
commands.BadBoolArgument,
)
class IdConverter(commands.Converter):
async def convert(self, ctx: commands.Context, argument: str) -> int:
try:
return int(argument.lstrip("#"))
except ValueError:
raise commands.BadArgument()
@dataclass(frozen=False)
class ConsoleLog:
id: int
time: datetime.datetime
time_timestamp: int
time_str: str
level: typing.Literal["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "TRACE", "NODE"]
logger_name: str
message: str
exc_info: typing.Optional[str] = None
display_without_informations: bool = False
@property
def logger(self) -> logging.Logger:
return logging.getLogger(self.logger_name)
def __str__(self, with_ansi: bool = False, with_extra_break_line: bool = True) -> str:
if self.display_without_informations:
return self.message
BREAK_LINE = "\n"
if not with_ansi:
return f"#{self.id} [{self.time_str}] {self.level} [{self.logger_name}] {self.message}{BREAK_LINE if self.exc_info is not None else ''}{BREAK_LINE if with_extra_break_line and self.exc_info is not None else ''}{self.exc_info if self.exc_info is not None else ''}"
levels_colors = {
"CRITICAL": Fore.RED,
"ERROR": Fore.RED,
"WARNING": Fore.YELLOW,
"INFO": Fore.BLUE,
"DEBUG": Fore.GREEN,
"TRACE": Fore.CYAN,
"NODE": Fore.MAGENTA,
}
level_color = levels_colors.get(self.level, Fore.MAGENTA)
return f"{Fore.CYAN}#{self.id} {Fore.BLACK}[{self.time_str}] {level_color}{self.level} {Fore.WHITE}[{Fore.MAGENTA}{self.logger_name}{Fore.WHITE}] {Fore.WHITE}{self.message.split(BREAK_LINE)[0]}{Fore.RESET}{BREAK_LINE if self.exc_info is not None else ''}{BREAK_LINE if with_extra_break_line and self.exc_info is not None else ''}{self.exc_info if self.exc_info is not None else ''}"
@cog_i18n(_)
class ConsoleLogs(DashboardIntegration, Cog):
"""A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"""
__authors__: typing.List[str] = ["AAA3A", "Tobotimus"]
def __init__(self, bot: Red) -> None:
super().__init__(bot=bot)
self.config: Config = Config.get_conf(
self,
identifier=205192943327321000143939875896557571750,
force_registration=True,
)
self.config.register_channel(
enabled=False,
global_errors=True,
prefixed_commands_errors=True,
slash_commands_errors=True,
dpy_ignored_exceptions=False,
full_console=False,
guild_invite=True,
ignored_cogs=[],
)
self.RED_INTRO: str = None
self._last_console_log_sent_timestamp: int = None
async def cog_load(self) -> None:
await super().cog_load()
asyncio.create_task(self.load())
async def load(self) -> None:
await self.bot.wait_until_red_ready()
self.RED_INTRO: str = INTRO
guilds = len(self.bot.guilds)
users = len(set(list(self.bot.get_all_members())))
prefixes = getattr(self.bot._cli_flags, "prefix", None) or (
await self.bot._config.prefix()
)
lang = await self.bot._config.locale()
dpy_version = discord.__version__
table_general_info = Table(show_edge=False, show_header=False, box=rich_box.MINIMAL)
table_general_info.add_row("Prefixes", ", ".join(prefixes))
table_general_info.add_row("Language", lang)
table_general_info.add_row("Red version", red_version)
table_general_info.add_row("Discord.py version", dpy_version)
table_general_info.add_row("Storage type", data_manager.storage_type())
table_counts = Table(show_edge=False, show_header=False, box=rich_box.MINIMAL)
table_counts.add_row("Shards", str(self.bot.shard_count))
table_counts.add_row("Servers", str(guilds))
if self.bot.intents.members:
table_counts.add_row("Unique Users", str(users))
io_file = BytesIO()
with TextIOWrapper(io_file, encoding="utf-8") as text_wrapper:
rich_print(
Columns(
[
Panel(table_general_info, title=self.bot.user.display_name),
Panel(table_counts),
],
equal=True,
align="center",
),
file=text_wrapper,
)
io_file.seek(0)
self.RED_INTRO += io_file.read().decode("utf-8")
self.RED_INTRO += (
f"\nLoaded {len(self.bot.cogs)} cogs with {len(self.bot.commands)} commands"
)
self._last_console_log_sent_timestamp: int = int(
datetime.datetime.now(tz=datetime.timezone.utc).timestamp()
)
self.loops.append(
Loop(
cog=self,
name="Check Console Logs",
function=self.check_console_logs,
minutes=1,
)
)
@property
def console_logs(self) -> typing.List[ConsoleLog]:
# Thanks to Tobotimus for this part!
console_logs_files = sorted(
[
path
for path in (data_manager.core_data_path() / "logs").iterdir()
if LATEST_LOG_RE.match(path.name) is not None
],
key=lambda x: x.name,
)
if not console_logs_files:
return []
console_logs_lines = []
for console_logs_file in console_logs_files:
with console_logs_file.open(mode="rt") as f:
console_logs_lines.extend([line.strip() for line in f.readlines()])
# Parse logs.
console_logs = []
for console_log_line in console_logs_lines:
if (match := re.match(CONSOLE_LOG_RE, console_log_line)) is None:
if not console_logs:
continue
if console_logs[-1].exc_info is None:
console_logs[-1].exc_info = ""
console_logs[-1].exc_info += f"\n{CogsUtils.replace_var_paths(console_log_line)}"
console_logs[-1].exc_info = console_logs[-1].exc_info.strip()
continue
kwargs = match.groupdict()
time = datetime.datetime.strptime(kwargs["time_str"], "%Y-%m-%d %H:%M:%S")
kwargs["time"] = time
kwargs["time_timestamp"] = int(time.timestamp())
kwargs["message"] = kwargs["message"].strip()
if not kwargs["message"]:
continue
kwargs["message"] += (
"."
if not kwargs["message"].endswith((".", "!", "?"))
and kwargs["message"][0] == kwargs["message"][0].upper()
else ""
)
kwargs["exc_info"] = None # Maybe next lines...
console_logs.append(ConsoleLog(id=0, **kwargs))
# Add Red INTRO.
if red_ready_console_log := discord.utils.get(
console_logs, logger_name="red", message="Connected to Discord. Getting ready..."
):
console_logs.insert(
console_logs.index(red_ready_console_log) + 1,
ConsoleLog(
id=0,
time=red_ready_console_log.time,
time_timestamp=red_ready_console_log.time_timestamp,
time_str=red_ready_console_log.time_str,
level="INFO",
logger_name="red",
message=self.RED_INTRO,
exc_info=None,
display_without_informations=True,
),
)
# Update ID.
for id, console_log in enumerate(console_logs, start=1):
console_log.id = id
return console_logs
async def send_console_logs(
self,
ctx: commands.Context,
level: typing.Optional[
typing.Literal["critical", "error", "warning", "info", "debug", "trace", "node"]
] = None,
ids: typing.Optional[typing.List[int]] = None,
logger_name: typing.Optional[str] = None,
view: typing.Optional[int] = -1,
lines_break: int = 2,
) -> None:
console_logs = self.console_logs
console_logs_to_display = [
console_log
for console_log in console_logs
if (level is None or console_log.level == level)
and (
logger_name is None
or ".".join(console_log.logger_name.split(".")[: len(logger_name.split("."))])
== logger_name
)
and (ids is None or console_log.id in ids)
]
if not console_logs_to_display:
raise commands.UserFeedbackCheckFailure(_("No logs to display."))
console_logs_to_display_str = [
console_log.__str__(
with_ansi=not (
ctx.author.is_on_mobile() if isinstance(ctx.author, discord.Member) else False
),
with_extra_break_line=view is not None,
)
for console_log in console_logs_to_display
]
levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "TRACE", "NODE"]
total_stats = [
f"{len(console_logs)} logs",
f"{len({console_log.logger_name for console_log in console_logs})} loggers",
*[
f"{stat[1]} {stat[0]}"
for stat in sorted(
Counter([console_log.level for console_log in console_logs]).items(),
key=lambda x: levels.index(x[0]) if x[0] in levels else 10,
)
],
]
loggers = {console_log.logger_name for console_log in console_logs_to_display}
current_stats = [
f"{len(console_logs_to_display)} log{'' if len(console_logs_to_display) == 1 else 's'}",
f"{len(loggers)} logger{'' if len(loggers) == 1 else 's'}",
*[
f"{stat[1]} {stat[0]}"
for stat in sorted(
Counter(
[console_log.level for console_log in console_logs_to_display]
).items(),
key=lambda x: levels.index(x[0]) if x[0] in levels else 10,
)
],
]
prefix = box(
f"Total stats: {humanize_list(total_stats)}."
+ (
f"\nCurrent stats: {humanize_list(current_stats)}."
if total_stats != current_stats
else ""
),
lang="py",
)
if view is not None:
try:
view = console_logs_to_display_str.index(
console_logs_to_display_str[view]
) # Handle negative index.
except IndexError:
view = len(console_logs_to_display_str)
pages = []
for i, console_log_to_display_str in enumerate(console_logs_to_display_str):
if i == view:
page_index = len(pages)
pages.extend(list(pagify(console_log_to_display_str, shorten_by=12 + len(prefix))))
else:
pages = list(
pagify(
("\n" * lines_break).join(console_logs_to_display_str),
shorten_by=12 + len(prefix),
)
)
page_index = [
i
for i, page in enumerate(pages)
if any(
line.startswith(("#", f"{Fore.CYAN}#", "[", f"{Fore.BLACK}["))
for line in page.split("\n")
)
][-1]
menu = Menu(
pages=pages,
prefix=prefix,
lang=(
"py"
if (ctx.author.is_on_mobile() if isinstance(ctx.author, discord.Member) else False)
else "ansi"
),
)
menu._current_page = page_index
await menu.start(ctx)
@commands.is_owner()
@commands.hybrid_group(aliases=["clogs"], invoke_without_command=True)
async def consolelogs(
self,
ctx: commands.Context,
lines_break: typing.Optional[commands.Range[int, 1, 5]] = 2,
level: typing.Optional[
typing.Literal[
"critical",
"error",
"warning",
"info",
"debug",
"trace",
"node",
"criticals",
"errors",
"warnings",
"infos",
"debugs",
"traces",
"nodes",
]
] = None,
ids: commands.Greedy[IdConverter] = None,
logger_name: typing.Optional[str] = None,
) -> None:
"""View a console log, for a provided level/logger name."""
if ids is not None and len(ids) == 1:
return await self.view(
ctx,
level=level.rstrip("s").upper() if level is not None else None,
ids=ids,
logger_name=logger_name,
)
await self.scroll(
ctx,
lines_break=lines_break,
level=level.rstrip("s").upper() if level is not None else None,
ids=ids,
logger_name=logger_name,
)
@consolelogs.command()
async def scroll(
self,
ctx: commands.Context,
lines_break: typing.Optional[commands.Range[int, 1, 5]] = 2,
level: typing.Optional[
typing.Literal[
"critical",
"error",
"warning",
"info",
"debug",
"trace",
"node",
"criticals",
"errors",
"warnings",
"infos",
"debugs",
"traces",
"nodes",
]
] = None,
ids: commands.Greedy[IdConverter] = None,
logger_name: typing.Optional[str] = None,
) -> None:
"""Scroll the console logs, for all levels/loggers or provided level/logger name."""
await self.send_console_logs(
ctx,
level=level.rstrip("s").upper() if level is not None else None,
ids=ids,
logger_name=logger_name,
view=None,
lines_break=lines_break,
)
@consolelogs.command()
async def view(
self,
ctx: commands.Context,
index: typing.Optional[int] = -1,
level: typing.Optional[
typing.Literal[
"critical",
"error",
"warning",
"info",
"debug",
"trace",
"node",
"criticals",
"errors",
"warnings",
"infos",
"debugs",
"traces",
"nodes",
]
] = None,
ids: commands.Greedy[IdConverter] = None,
logger_name: typing.Optional[str] = None,
) -> None:
"""View the console logs one by one, for all levels/loggers or provided level/logger name."""
await self.send_console_logs(
ctx,
level=level.rstrip("s").upper() if level is not None else None,
ids=ids,
logger_name=logger_name,
view=index,
)
@consolelogs.command(aliases=["listloggers"])
async def stats(self, ctx: commands.Context) -> None:
"""Display the stats for the bot logs since the bot start."""
console_logs = self.console_logs
console_logs_for_each_logger = {"Global Stats": console_logs}
for console_log in console_logs:
if console_log.logger_name not in console_logs_for_each_logger:
console_logs_for_each_logger[console_log.logger_name] = []
console_logs_for_each_logger[console_log.logger_name].append(console_log)
stats = ""
for logger_name, logs in console_logs_for_each_logger.items():
stats += f"\n\n---------- {logger_name} ----------"
stats += f"\n{len(logs)} logs"
levels = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG", "TRACE", "NODE"]
for stat in sorted(
Counter([console_log.level for console_log in logs]).items(),
key=lambda x: levels.index(x[0]) if x[0] in levels else 10,
):
stats += f"\n{stat[1]} {stat[0]}"
await Menu(pages=list(pagify(stats, page_length=500)), lang="py").start(ctx)
@consolelogs.command(aliases=["+"])
async def addchannel(
self,
ctx: commands.Context,
channel: typing.Union[discord.TextChannel, discord.VoiceChannel, discord.Thread],
global_errors: typing.Optional[bool] = True,
prefixed_commands_errors: typing.Optional[bool] = True,
slash_commands_errors: typing.Optional[bool] = True,
dpy_ignored_exceptions: typing.Optional[bool] = False,
full_console: typing.Optional[bool] = False,
guild_invite: typing.Optional[bool] = True,
*,
ignored_cogs: commands.Greedy[commands.CogConverter] = None,
) -> None:
"""Enable errors logging in a channel.
**Parameters:**
- `channel`: The channel where the commands errors will be sent.
- `global_errors`: Log errors for the entire bot, not just the channel server.
- `prefixed_commands_errors`: Log prefixed commands errors.
- `slash_commands_errors`: Log slash commands errors.
- `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).
- `full_console`: Log all the console logs.
- `guild_invite`: Add a button "Guild Invite" in commands errors logs, only for community servers.
- `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.
"""
channel_permissions = channel.permissions_for(ctx.me)
if not all(
[
channel_permissions.view_channel,
channel_permissions.send_messages,
channel_permissions.embed_links,
]
):
raise commands.UserFeedbackCheckFailure(
_("I don't have the permissions to send embeds in this channel.")
)
await self.config.channel(channel).set(
{
"enabled": True,
"global_errors": global_errors,
"prefixed_commands_errors": prefixed_commands_errors,
"slash_commands_errors": slash_commands_errors,
"dpy_ignored_exceptions": dpy_ignored_exceptions,
"full_console": full_console,
"guild_invite": guild_invite,
"ignored_cogs": (
[cog.qualified_name for cog in ignored_cogs]
if ignored_cogs is not None
else []
),
}
)
await ctx.send(_("Errors logging enabled in {channel.mention}.").format(channel=channel))
@consolelogs.command(aliases=["-"])
async def removechannel(
self,
ctx: commands.Context,
channel: typing.Union[discord.TextChannel, discord.VoiceChannel, discord.Thread],
) -> None:
"""Disable errors logging in a channel."""
if not await self.config.channel(channel).enabled():
raise commands.UserFeedbackCheckFailure(
_("Errors logging isn't enabled in this channel.")
)
await self.config.channel(channel).clear()
await ctx.send(_("Errors logging disabled in {channel.mention}.").format(channel=channel))
@consolelogs.command(hidden=True)
async def getdebugloopsstatus(self, ctx: commands.Context) -> None:
"""Get an embed to check loops status."""
embeds = [loop.get_debug_embed() for loop in self.loops]
await Menu(pages=embeds).start(ctx)
@commands.Cog.listener()
async def on_command_error(
self, ctx: commands.Context, error: commands.CommandError, unhandled_by_cog: bool = False
) -> None:
if await self.bot.cog_disabled_in_guild(cog=self, guild=ctx.guild):
return
if isinstance(error, IGNORED_ERRORS):
return
destinations = {
channel: settings
for channel_id, settings in (await self.config.all_channels()).items()
if settings["enabled"]
and (channel := ctx.bot.get_channel(channel_id)) is not None
and channel.permissions_for(channel.guild.me).send_messages
}
if not destinations:
return
# Thanks to Trusty for this part.
if ctx.interaction:
data = ctx.interaction.data
com_id = data.get("id")
root_command = data.get("name")
sub_commands = ""
arguments = ""
for option in data.get("options", []):
if option["type"] in (1, 2):
sub_commands += " " + option["name"]
else:
option_name = option["name"]
option_value = option.get("value")
arguments += f"{option_name}: {option_value}"
for sub_option in option.get("options", []):
if sub_option["type"] in (1, 2):
sub_commands += " " + sub_option["name"]
else:
sub_option_name = sub_option.get("name")
sub_option_value = sub_option.get("value")
arguments += f"{sub_option_name}: {sub_option_value}"
for arg in sub_option.get("options", []):
arg_option_name = arg.get("name")
arg_option_value = arg.get("value")
arguments += f"{arg_option_name}: {arg_option_value} "
command_name = f"{root_command}{sub_commands}"
com_str = f"</{command_name}:{com_id}> {arguments}"
else:
com_str = ctx.message.content
embed = discord.Embed(
title=f"⚠ Exception in command `{ctx.command.qualified_name}`! ¯\\_(ツ)_/¯",
color=discord.Color.red(),
timestamp=ctx.message.created_at,
description=f">>> {com_str}",
)
embed.add_field(
name="Invoker:", value=f"{ctx.author.mention}\n{ctx.author} ({ctx.author.id})"
)
embed.add_field(name="Message:", value=f"[Jump to message.]({ctx.message.jump_url})")
embed.add_field(
name="Channel:",
value=(
f"{ctx.channel.mention}\n{ctx.channel} ({ctx.channel.id})"
if ctx.guild is not None
else str(ctx.channel)
),
)
if ctx.guild is not None:
embed.add_field(name="Guild:", value=f"{ctx.guild.name} ({ctx.guild.id})")
guild_invite = None
if ctx.guild is not None and "COMMUNITY" in ctx.guild.features:
try:
if "VANITY_URL" not in ctx.guild.features:
raise KeyError("VANITY_URL")
guild_invite = await ctx.guild.vanity_invite()
except (KeyError, discord.HTTPException):
try:
invites = await ctx.guild.invites()
except discord.HTTPException:
invites = []
for inv in invites:
if not (inv.max_uses or inv.max_age or inv.temporary):
guild_invite = inv
break
else:
channels_and_perms = zip(
ctx.guild.text_channels,
map(lambda x: x.permissions_for(ctx.guild.me), ctx.guild.text_channels),
)
channel = next(
(
channel
for channel, perms in channels_and_perms
if perms.create_instant_invite
),
None,
)
if channel is not None:
try:
guild_invite = await channel.create_invite(max_age=86400)
except discord.HTTPException:
pass
traceback_error = "".join(
traceback.format_exception(type(error), error, error.__traceback__)
)
_traceback_error = traceback_error.split("\n")
_traceback_error[0] = _traceback_error[0] + (
"" if _traceback_error[0].endswith(":") else ":\n"
)
traceback_error = "\n".join(_traceback_error)
traceback_error = CogsUtils.replace_var_paths(traceback_error)
pages = [box(page, lang="py") for page in pagify(traceback_error, shorten_by=10)]
for channel, settings in destinations.items():
if not settings["global_errors"] and ctx.guild != channel.guild:
continue
if not settings["prefixed_commands_errors"] and ctx.interaction is None:
continue
if not settings["slash_commands_errors"] and ctx.interaction is not None:
continue
if ctx.cog is not None and ctx.cog.qualified_name in settings["ignored_cogs"]:
continue
view = discord.ui.View()
view.add_item(
discord.ui.Button(
style=discord.ButtonStyle.url,
label="Jump to Message",
url=ctx.message.jump_url,
)
)
if settings["guild_invite"] and guild_invite is not None:
view.add_item(
discord.ui.Button(
style=discord.ButtonStyle.url, label="Guild Invite", url=guild_invite.url
)
)
await channel.send(embed=embed, view=view)
for page in pages:
await channel.send(page)
async def check_console_logs(self) -> None:
destinations = {
channel: settings
for channel_id, settings in (await self.config.all_channels()).items()
if settings["enabled"]
and (settings["dpy_ignored_exceptions"] or settings["full_console"])
and (channel := self.bot.get_channel(channel_id)) is not None
and channel.permissions_for(channel.guild.me).send_messages
}
if not destinations:
return
console_logs = self.console_logs
console_logs_to_send: typing.List[
typing.Tuple[typing.Optional[discord.Embed], typing.List[str]]
] = []
pages_to_send: typing.List[str] = []
for console_log in console_logs:
if self._last_console_log_sent_timestamp >= console_log.time_timestamp:
continue
self._last_console_log_sent_timestamp = console_log.time_timestamp
pages_to_send.append(console_log.__str__(with_ansi=False, with_extra_break_line=False))
if (
console_log.level in ("CRITICAL", "ERROR")
and console_log.logger_name.split(".")[0] == "discord"
and console_log.message.split("\n")[0].startswith("Ignoring exception ")
):
if pages_to_send:
console_logs_to_send.append(
(
None,
[
box(page, lang="py")
for page in list(
pagify(
"\n\n".join(pages_to_send),
shorten_by=10,
)
)
],
)
)
pages_to_send = []
embed: discord.Embed = discord.Embed(color=discord.Color.dark_embed())
embed.title = console_log.message.split("\n")[0]
embed.timestamp = console_log.time
embed.add_field(name="Logger name:", value=f"`{console_log.logger_name}`")
embed.add_field(name="Error level:", value=f"`{console_log.level}`")
pages = [
box(page, lang="py")
for page in list(
pagify(
console_log.__str__(with_ansi=False, with_extra_break_line=True),
shorten_by=10,
)
)
]
console_logs_to_send.append((embed, pages))
if pages_to_send:
console_logs_to_send.append(
(
None,
[
box(page, lang="py")
for page in list(
pagify(
"\n\n".join(pages_to_send),
shorten_by=10,
)
)
],
)
)
pages_to_send = []
for channel, settings in destinations.items():
for embed, pages in console_logs_to_send:
if embed is not None and not settings["dpy_ignored_exceptions"]:
continue
elif embed is None and not settings["full_console"]:
continue
if embed is not None:
await channel.send(embed=embed)
for page in pages:
await channel.send(page)

View file

@ -0,0 +1,54 @@
from redbot.core import commands # isort:skip
from redbot.core.bot import Red # isort:skip
from redbot.core.i18n import Translator # isort:skip
import typing # isort:skip
_: Translator = Translator("ConsoleLogs", __file__)
def dashboard_page(*args, **kwargs):
def decorator(func: typing.Callable):
func.__dashboard_decorator_params__ = (args, kwargs)
return func
return decorator
class DashboardIntegration:
bot: Red
tracebacks = []
@commands.Cog.listener()
async def on_dashboard_cog_add(self, dashboard_cog: commands.Cog) -> None:
if hasattr(self, "settings") and hasattr(self.settings, "commands_added"):
await self.settings.commands_added.wait()
dashboard_cog.rpc.third_parties_handler.add_third_party(self)
@dashboard_page(name=None, description="Display the console logs.", is_owner=True)
async def rpc_callback(self, **kwargs) -> typing.Dict[str, typing.Any]:
console_logs = self.console_logs
source = """
{% for console_log in console_logs %}
{{ console_log|highlight("python") }}
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
"""
console_logs = kwargs["Pagination"].from_list(
console_logs,
per_page=kwargs["extra_kwargs"].get("per_page"),
page=kwargs["extra_kwargs"].get("page"),
default_per_page=1,
default_page=len(console_logs),
)
_console_logs = [str(console_log) for console_log in console_logs]
console_logs.clear()
console_logs.extend(_console_logs)
return {
"status": 0,
"web_content": {
"source": source,
"console_logs": console_logs,
},
}

17
consolelogs/info.json Normal file
View file

@ -0,0 +1,17 @@
{
"author": ["AAA3A", "Tobotimus"],
"name": "ConsoleLogs",
"install_msg": "Thank you for installing this cog!\nDo `[p]help CogName` to get the list of commands and their description. If you enjoy my work, please consider donating on [Buy Me a Coffee](<https://www.buymeacoffee.com/aaa3a>) or [Ko-Fi](<https://ko-fi.com/aaa3a>)!",
"short": "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!",
"description": "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!",
"tags": [
"error",
"console",
"logs",
"dev",
"errorlogs"
],
"requirements": ["git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git", "colorama"],
"min_bot_version": "3.5.0",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: German\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: de\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: de_DE\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Ein Zahnrad zur Anzeige der Konsolenprotokolle, mit Schaltflächen und Filteroptionen, und zum Senden von Fehlerbefehlen in konfigurierten Kanälen!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Es werden keine Protokolle angezeigt."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Blättern Sie in den Konsolenprotokollen für alle Ebenen/Logger oder für die angegebene Ebene/Loggernamen."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Zeigen Sie die Konsolenprotokolle einzeln an, für alle Ebenen/Protokollierer oder den angegebenen Namen der Ebene/Protokollierer."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Zeigt die Statistiken für die Bot-Protokolle seit dem Start des Bots an."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Aktivieren der Fehlerprotokollierung in einem Kanal.\n\n"
" **Parameter:**\n"
" - `Kanal`: Der Kanal, an den die Befehlsfehler gesendet werden sollen.\n"
" - global_errors\": Loggt Fehler für den gesamten Bot, nicht nur für den Channel-Server.\n"
" - prefixed_commands_errors`: Loggt Fehler bei vorangestellten Befehlen.\n"
" - Schrägstrich_befehle_Fehler\": Protokolliert Schrägstrich-Befehlsfehler.\n"
" - dpy_ignored_exceptions`: Loggt dpy ignorierte Ausnahmen (Ereignis-Listener und Views Fehler).\n"
" - `full_console`: Protokolliert alle Konsolenprotokolle.\n"
" - Gilde_einladen`: Fügt einen Button \"Guild Invite\" in den Kommandofehler-Logs hinzu, nur für Community-Server.\n"
" - ignorierte_kogs`: Ignoriere einige Cogs für `prefixed_commands_errors` und `slash_commands_errors`. Du musst den cog qualified_name wie `ConsoleLogs` für diesen cog verwenden.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Ich habe nicht die Berechtigung, Einbettungen in diesem Kanal zu senden."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Die Fehlerprotokollierung ist in {channel.mention}aktiviert."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Deaktivieren Sie die Fehlerprotokollierung in einem Kanal."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "Die Fehlerprotokollierung ist in diesem Kanal nicht aktiviert."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Die Fehlerprotokollierung ist in {channel.mention}deaktiviert."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Holen Sie sich eine Einbettung, um den Schleifenstatus zu überprüfen."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Greek\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: el\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: el_GR\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Ένα γρανάζι για την εμφάνιση των αρχείων καταγραφής της κονσόλας, με κουμπιά και επιλογές φίλτρου, και για την αποστολή εντολών σφαλμάτων σε διαμορφωμένα κανάλια!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Δεν υπάρχουν αρχεία καταγραφής για εμφάνιση."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Κύλιση των αρχείων καταγραφής της κονσόλας, για όλα τα επίπεδα/καταγραφείς ή για το όνομα επιπέδου/καταγραφέα που παρέχεται."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Προβολή των αρχείων καταγραφής της κονσόλας ένα προς ένα, για όλα τα επίπεδα/καταχωρητές ή για το όνομα του επιπέδου/καταχωρητή που παρέχεται."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Εμφάνιση των στατιστικών για τα αρχεία καταγραφής του bot από την έναρξη του bot."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Ενεργοποίηση της καταγραφής σφαλμάτων σε ένα κανάλι.\n\n"
" **Παράμετροι:**\n"
" - `κανάλι`: Το κανάλι στο οποίο θα αποστέλλονται τα σφάλματα των εντολών.\n"
" - `global_errors`: Καταγραφή σφαλμάτων για ολόκληρο το bot, όχι μόνο για τον διακομιστή του καναλιού.\n"
" - `prefixed_commands_errors`: Καταγραφή σφαλμάτων εντολών με πρόθεμα.\n"
" - `slash_commands_errors`: Καταγραφή σφαλμάτων εντολών slash.\n"
" - `dpy_ignored_exceptions`: Καταγραφή εξαιρέσεων που αγνοούνται από το dpy (σφάλματα ακροατών συμβάντων και Views).\n"
" - `full_console`: Καταγραφή όλων των αρχείων καταγραφής της κονσόλας.\n"
" - `guild_invite`: Προσθήκη ενός κουμπιού \"Guild Invite\" στα αρχεία καταγραφής σφαλμάτων εντολών, μόνο για διακομιστές κοινότητας.\n"
" - `ignored_cogs`: Αγνοήστε ορισμένα cogs για τα `prefixed_commands_errors` και `slash_commands_errors`. Θα πρέπει να χρησιμοποιήσετε το cog qualified_name όπως το `ConsoleLogs` για αυτό το cog.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Δεν έχω τα δικαιώματα για να στέλνω ενσωματώσεις σε αυτό το κανάλι."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Η καταγραφή σφαλμάτων είναι ενεργοποιημένη στο {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Απενεργοποίηση της καταγραφής σφαλμάτων σε ένα κανάλι."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "Η καταγραφή σφαλμάτων δεν είναι ενεργοποιημένη σε αυτό το κανάλι."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Η καταγραφή σφαλμάτων είναι απενεργοποιημένη στο {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Πάρτε μια ενσωμάτωση για να ελέγξετε την κατάσταση των βρόχων."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: es_ES\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Un engranaje para mostrar los registros de la consola, con botones y opciones de filtrado, y para enviar comandos de errores en los canales configurados."
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "No hay registros para mostrar."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Desplácese por los registros de la consola, para todos los niveles/registradores o nombre de nivel/registrador proporcionado."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Ver los registros de la consola uno a uno, para todos los niveles/registradores o nombre de nivel/registrador proporcionado."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Muestra las estadísticas de los registros del bot desde su inicio."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Habilitar el registro de errores en un canal.\n\n"
" **Parámetros:**\n"
" - `canal`: El canal donde se enviarán los errores de los comandos.\n"
" - `global_errors`: Registra los errores de todo el bot, no sólo del servidor del canal.\n"
" - `prefixed_commands_errors`: Registra errores de comandos prefijados.\n"
" - `slash_commands_errors`: Registra errores de comandos de barra.\n"
" - `dpy_ignored_exceptions`: Registra las excepciones ignoradas por dpy (escuchas de eventos y errores de Views).\n"
" - `full_console`: Registra todos los logs de la consola.\n"
" - `guild_invite`: Añade un botón \"Guild Invite\" en los logs de errores de comandos, sólo para servidores community.\n"
" - `ignored_cogs`: Ignorar algunos cogs para `prefixed_commands_errors` y `slash_commands_errors`. Tienes que usar el nombre_calificado del cog como `ConsoleLogs` para este cog.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "No tengo permisos para enviar archivos incrustados en este canal."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Registro de errores activado en {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Desactivar el registro de errores en un canal."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "El registro de errores no está habilitado en este canal."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Registro de errores desactivado en {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Obtenga una incrustación para comprobar el estado de los bucles."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: fi\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: fi_FI\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Logi näyttää konsolin lokit, painikkeilla ja suodatusvaihtoehtoja, ja lähettää komentoja virheitä määritettyihin kanaviin!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Ei näytettäviä lokitietoja."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Selaa konsolin lokitietoja kaikkien tasojen/loggaajien tai annetun tason/loggaajan nimen osalta."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Näytä konsolin lokit yksitellen, kaikkien tasojen/loggaajien tai tietyn tason/loggaajan nimen osalta."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Näyttää botin lokitilastot botin käynnistymisestä lähtien."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Ota virheiden kirjaaminen käyttöön kanavassa.\n\n"
" **Parametrit:**\n"
" - `kanava`: Kanava, johon komentojen virheet lähetetään.\n"
" - `global_errors`: Loggaa virheet koko botille, ei vain kanavapalvelimelle.\n"
" - `prefixed_commands_errors`: Kirjaa etukäteiskomentojen virheet.\n"
" - `slash_commands_errors`: Kirjaa slash-komentojen virheet.\n"
" - `dpy_ignored_exceptions`: Loki dpy:n huomiotta jätetyt poikkeukset (tapahtumien kuuntelijoiden ja näkymien virheet).\n"
" - `full_console`: Kirjaa kaikki konsolin lokit.\n"
" - `guild_invite`: Lisää painike \"Guild Invite\" komentojen virhelokiin, vain yhteisöpalvelimille.\n"
" - `ignored_cogs`: Joidenkin lokien huomiotta jättäminen `prefixed_commands_errors`- ja `slash_commands_errors`-virheissä. Sinun on käytettävä tätä lokia varten lokin pätevää nimeä, kuten `ConsoleLogs`.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Minulla ei ole oikeuksia lähettää upotuksia tällä kanavalla."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Virheiden kirjaaminen on käytössä osoitteessa {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Poista virheiden kirjaaminen käytöstä kanavassa."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "Virheiden kirjaaminen ei ole käytössä tällä kanavalla."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Virheiden kirjaaminen ei ole käytössä osoitteessa {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Hanki upotus silmukoiden tilan tarkistamiseksi."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: fr_FR\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Un mécanisme pour afficher les journaux de la console, avec des boutons et des options de filtrage, et pour envoyer des commandes d'erreurs dans les canaux configurés !"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Aucun journal à afficher."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Fait défiler les journaux de la console, pour tous les niveaux/enregistreurs ou pour un niveau/enregistreur donné."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Visualiser les journaux de la console un par un, pour tous les niveaux/déclencheurs ou pour un niveau/déclencheur donné."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Affiche les statistiques des logs du bot depuis son démarrage."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Active la journalisation des erreurs dans un canal.\n\n"
" **Paramètres:**\n"
" - `canal` : Le canal où les erreurs de commandes seront envoyées.\n"
" - `global_errors` : Enregistre les erreurs pour l'ensemble du bot, et pas seulement pour le serveur du canal.\n"
" - `prefixed_commands_errors` : Enregistre les erreurs de commandes préfixées.\n"
" - `slash_commands_errors` : Journalise les erreurs des commandes slash.\n"
" - `dpy_ignored_exceptions` : Enregistre les exceptions ignorées par dpy (erreurs des auditeurs d'événements et des vues).\n"
" - `full_console` : Enregistre tous les logs de la console.\n"
" - `guild_invite` : Ajoute un bouton \"Guild Invite\" dans les logs d'erreurs des commandes, seulement pour les serveurs communautaires.\n"
" - `ignored_cogs` : Ignore certains cogs pour `prefixed_commands_errors` et `slash_commands_errors`. Vous devez utiliser le nom qualifié du cog comme `ConsoleLogs` pour ce cog.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Je n'ai pas les autorisations nécessaires pour envoyer des liens dans ce canal."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "La journalisation des erreurs est activée sur {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Désactive l'enregistrement des erreurs dans un canal."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "La journalisation des erreurs n'est pas activée dans ce canal."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "La journalisation des erreurs est désactivée sur le site {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Obtenir un embed pour vérifier l'état des boucles."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: it\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: it_IT\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Un ingranaggio per visualizzare i log della console, con pulsanti e opzioni di filtro, e per inviare comandi di errore nei canali configurati!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Nessun registro da visualizzare."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Scorre i registri della console, per tutti i livelli/logger o per il nome del livello/logger fornito."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Visualizza i registri della console uno per uno, per tutti i livelli/logger o per il nome del livello/logger fornito."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Visualizza le statistiche dei log del bot dall'avvio del bot."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Abilita la registrazione degli errori in un canale.\n\n"
" **Parametri:**\n"
" - `canale`: Il canale dove verranno inviati gli errori dei comandi.\n"
" - `global_errors`: Registra gli errori per l'intero bot, non solo per il server del canale.\n"
" - `prefixed_commands_errors`: Registra gli errori dei comandi prefissati.\n"
" - Errori_comandi_slash`: Registra gli errori dei comandi slash.\n"
" - `dpy_ignored_exceptions`: Registra le eccezioni ignorate da dpy (errori degli ascoltatori di eventi e delle viste).\n"
" - `full_console`: Registra tutti i log della console.\n"
" - `guild_invite`: Aggiunge un pulsante \"Invita la gilda\" nei log degli errori dei comandi, solo per i server della comunità.\n"
" - `ignored_cogs`: Ignora alcuni cog per `prefixed_commands_errors` e `slash_commands_errors`. È necessario utilizzare il nome qualificato del cog, come `ConsoleLogs`, per questo cog.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Non ho i permessi per inviare gli embed in questo canale."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Registrazione degli errori abilitata in {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Disabilita la registrazione degli errori in un canale."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "La registrazione degli errori non è abilitata in questo canale."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Registrazione degli errori disabilitata in {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Ottenere un embed per controllare lo stato dei loop."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: ja_JP\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "コンソールのログを表示し、ボタンとフィルターオプションがあり、設定されたチャンネルでコマンドエラーを送信するための歯車!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "表示するログがない。"
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "すべてのレベル/ロガー、または指定されたレベル/ロガー名のコンソール・ログをスクロールする。"
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "すべてのレベル/ロガー、または指定されたレベル/ロガー名のコンソール・ログを1つずつ表示します。"
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "ボット開始からのボットログの統計情報を表示します。"
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "チャンネルのエラーログを有効にする。\n\n"
" **Parameters:**\n"
" - `channel`:コマンドのエラーを送信するチャンネル。\n"
" - global_errors`:チャンネルサーバーだけでなく、ボット全体のエラーを記録する。\n"
" - prefixed_commands_errors`:接頭辞付きコマンドのエラーをログに記録する。\n"
" - `slash_commands_errors`: スラッシュコマンドのエラーをログに記録する:スラッシュコマンドのエラーをログに記録する。\n"
" - dpy_ignored_exceptions`: dpy が無視した例外を記録するdpy が無視した例外 (イベントリスナーと Views のエラー) を記録する。\n"
" - full_console`: 全てのコンソールログを記録する:全てのコンソールログを記録する。\n"
" - guild_invite`: ギルド招待ボタンを追加する:コミュニティサーバーでのみ、コマンドのエラーログに \"Guild Invite\" ボタンを追加する。\n"
" - ignore_cogs`prefixed_commands_errors`と`slash_commands_errors`のコグを無視するようにした。このコグには `ConsoleLogs` のような修飾名を使う必要があります。\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "このチャンネルでエンベッドを送信する権限がありません。"
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "{channel.mention}でエラーログを有効にする。"
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "チャンネルのエラーログを無効にする。"
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "このチャンネルではエラーログは有効になっていない。"
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "{channel.mention}でエラーロギングを無効にする。"
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "ループの状態を確認するためのエンベデッドを取得します。"

View file

@ -0,0 +1,85 @@
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-12-29 10:43+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
#: consolelogs\consolelogs.py:105
#, docstring
msgid ""
"A cog to display the console logs, with buttons and filter options, and to "
"send commands errors in configured channels!"
msgstr ""
#: consolelogs\consolelogs.py:281
msgid "No logs to display."
msgstr ""
#: consolelogs\consolelogs.py:434
#, docstring
msgid ""
"Scroll the console logs, for all levels/loggers or provided level/logger "
"name."
msgstr ""
#: consolelogs\consolelogs.py:470
#, docstring
msgid ""
"View the console logs one by one, for all levels/loggers or provided "
"level/logger name."
msgstr ""
#: consolelogs\consolelogs.py:481
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr ""
#: consolelogs\consolelogs.py:514
#, docstring
msgid ""
"Enable errors logging in a channel.\n"
"\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr ""
#: consolelogs\consolelogs.py:535
msgid "I don't have the permissions to send embeds in this channel."
msgstr ""
#: consolelogs\consolelogs.py:553
msgid "Errors logging enabled in {channel.mention}."
msgstr ""
#: consolelogs\consolelogs.py:561
#, docstring
msgid "Disable errors logging in a channel."
msgstr ""
#: consolelogs\consolelogs.py:564
msgid "Errors logging isn't enabled in this channel."
msgstr ""
#: consolelogs\consolelogs.py:567
msgid "Errors logging disabled in {channel.mention}."
msgstr ""
#: consolelogs\consolelogs.py:571
#, docstring
msgid "Get an embed to check loops status."
msgstr ""

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: nl\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: nl_NL\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Een cog om console logs weer te geven, met knoppen en filter opties, en om commando's te versturen in ingestelde kanalen!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Geen logs om weer te geven."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Scroll door de console logs, voor alle levels/loggers of geef een naam van een level/logger."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Bekijk de console logs één voor één, voor alle levels/loggers of geef een naam van een level/logger."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "De statistieken van de botlogs sinds de botstart weergeven."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Het loggen van fouten in een kanaal inschakelen.\n\n"
" **Parameters:**\n"
" - `kanaal`: Het kanaal waar de fouten van de commando's naartoe worden gestuurd.\n"
" - `global_errors`: Log fouten voor de hele bot, niet alleen de kanaalserver.\n"
" - `prefixed_commands_errors`: Log prefixed commando fouten.\n"
" - `slash_commands_errors`: Log slash commando fouten.\n"
" - `dpy_geaccepteerde_fouten`: Log dpy genegeerde uitzonderingen (events listeners en Views fouten).\n"
" - `full_console`: Log alle console logs.\n"
" - gilde uitnodigen`: Voeg een knop \"Guild Invite\" toe in commando fouten logs, alleen voor community servers.\n"
" - genegeerde_koggen`: Negeer sommige tandwielen voor `prefixed_commands_errors` en `slash_commands_errors`. Je moet de cog qualified_name gebruiken zoals `ConsoleLogs` voor deze cog.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Ik heb geen permissies om embeds te sturen in dit channel."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Foutregistratie ingeschakeld in {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Schakel foutregistratie uit in een kanaal."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "Foutregistratie is niet ingeschakeld in dit kanaal."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Foutregistratie uitgeschakeld in {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Krijg een embed om de lusstatus te controleren."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: pl\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: pl_PL\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Tryb do wyświetlania logów konsoli, z przyciskami i opcjami filtrowania oraz do wysyłania błędów poleceń w skonfigurowanych kanałach!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Brak dzienników do wyświetlenia."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Przewija logi konsoli dla wszystkich poziomów/loggerów lub dla podanego poziomu/nazwy loggera."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Wyświetlanie dzienników konsoli jeden po drugim, dla wszystkich poziomów/logerów lub podanej nazwy poziomu/logera."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Wyświetla statystyki logów bota od momentu jego uruchomienia."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Włącz rejestrowanie błędów w kanale.\n\n"
" **Parametry:**\n"
" - `channel`: Kanał, na który będą wysyłane błędy poleceń.\n"
" - `global_errors`: Loguje błędy dla całego bota, nie tylko dla serwera kanału.\n"
" - `prefixed_commands_errors`: Loguje błędy poleceń z prefiksem.\n"
" - `slash_commands_errors`: Loguje błędy poleceń z ukośnikiem.\n"
" - `dpy_ignored_exceptions`: Loguje zignorowane wyjątki dpy (event listeners i Views errors).\n"
" - `full_console`: Loguje wszystkie logi konsoli.\n"
" - `guild_invite`: Dodaje przycisk \"Guild Invite\" w logach błędów komend, tylko dla serwerów społeczności.\n"
" - `ignored_cogs`: Ignorowanie niektórych trybów dla `prefixed_commands_errors` i `slash_commands_errors`. Musisz użyć kwalifikowanej nazwy trybiku, takiej jak `ConsoleLogs` dla tego trybiku.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Nie mam uprawnień do wysyłania embedów na tym kanale."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Rejestrowanie błędów włączone w {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Wyłączenie rejestrowania błędów w kanale."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "Rejestrowanie błędów nie jest włączone w tym kanale."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Rejestrowanie błędów wyłączone w {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Pobierz embed, aby sprawdzić status pętli."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Portuguese, Brazilian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: pt-BR\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: pt_BR\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Uma engrenagem para visualizar os registos da consola, com botões e opções de filtragem, e para enviar comandos de erros nos canais configurados!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Não há registos a apresentar."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Percorre os registos da consola, para todos os níveis/loggers ou para o nome do nível/logger fornecido."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Ver os registos da consola um a um, para todos os níveis/registadores ou para o nome do nível/registador fornecido."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Exibe as estatísticas dos registros do bot desde o início do bot."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Ativar o registro de erros em um canal.\n\n"
" **Parametros:**\n"
" - `channel`: O canal para o qual os erros dos comandos serão enviados.\n"
" - `global_errors`: Registrar erros para todo o bot, não apenas para o servidor do canal.\n"
" - `prefixed_commands_errors`: Registra erros de comandos prefixados.\n"
" - `slash_commands_errors`: Registra erros de comandos de barra.\n"
" - `dpy_ignored_exceptions`: Registra exceções ignoradas pelo dpy (ouvintes de eventos e erros de visualizações).\n"
" - `full_console`: Registra todos os logs do console.\n"
" - `guild_invite`: Adiciona um botão \"Guild Invite\" nos registros de erros dos comandos, apenas para servidores comunitários.\n"
" - `ignored_cogs`: Ignora alguns cogs para `prefixed_commands_errors` e `slash_commands_errors`. Você deve usar o nome qualificado do cog como `ConsoleLogs` para esse cog.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Não tenho permissões para enviar incorporações neste canal."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Registo de erros ativado em {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Desativar o registo de erros num canal."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "O registo de erros não está ativado neste canal."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Registo de erros desativado em {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Obter uma incorporação para verificar o estado dos loops."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: pt-PT\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: pt_PT\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Uma engrenagem para visualizar os registos da consola, com botões e opções de filtragem, e para enviar comandos de erros nos canais configurados!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Não há registos a apresentar."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Percorre os registos da consola, para todos os níveis/loggers ou para o nome do nível/logger fornecido."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Ver os registos da consola um a um, para todos os níveis/registadores ou para o nome do nível/registador fornecido."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Mostra as estatísticas dos logs do bot desde o início do bot."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Ativar o registo de erros num canal.\n\n"
" **Parâmetros:**\n"
" - `canal`: O canal para onde serão enviados os erros dos comandos.\n"
" - `global_errors`: Logar erros para todo o bot, não apenas para o servidor do canal.\n"
" - `erros_comandos_prefixados`: Registra erros de comandos prefixados.\n"
" - `slash_commands_errors`: Registra erros de comandos com barra.\n"
" - `dpy_ignored_exceptions`: Registra exceções ignoradas pelo dpy (eventos de escuta e erros de Views).\n"
" - `full_console`: Registra todos os logs do console.\n"
" - `guild_invite`: Adiciona um botão \"Guild Invite\" nos logs de erros dos comandos, apenas para servidores comunitários.\n"
" - `ignored_cogs`: Ignora alguns cogs para `erros_de_comandos_prefixados` e `erros_de_comandos_com barra`. É necessário utilizar o nome qualificado do cog como `ConsoleLogs` para este cog.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Não tenho permissões para enviar incorporações neste canal."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Registo de erros ativado em {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Desativar o registo de erros num canal."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "O registo de erros não está ativado neste canal."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Registo de erros desativado em {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Obter uma incorporação para verificar o estado dos loops."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Romanian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: ro\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: ro_RO\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "O rotiță pentru a afișa jurnalele consolei, cu butoane și opțiuni de filtrare, și pentru a trimite erori de comandă în canalele configurate!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Nu există jurnale de afișat."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Derulează jurnalele consolei, pentru toate nivelurile/loggerii sau pentru numele nivelului/loggerului furnizat."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Vizualizați jurnalele de consolă unul câte unul, pentru toate nivelurile/loggerii sau pentru numele nivelului/loggerului furnizat."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Afișează statisticile pentru jurnalele robotului de la pornirea robotului."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Activați înregistrarea erorilor într-un canal.\n\n"
" **Parametri:**\n"
" - `channel`: Canalul unde vor fi trimise erorile de comandă.\n"
" - `global_errors`: Jurnalizează erorile pentru întregul bot, nu doar pentru serverul canalului.\n"
" - `prefixed_commands_errors`: Înregistrarea erorilor de comenzi prefixate.\n"
" - `slash_commands_errors`: Jurnalizează erorile comenzilor slash.\n"
" - `dpy_ignored_exceptions`: Înregistrați excepțiile ignorate de dpy (erori de ascultare a evenimentelor și erori de vizualizare).\n"
" - `full_console`: Înregistrează toate jurnalele de consolă.\n"
" - `guild_invite`: Adăugați un buton \"Guild Invite\" în jurnalele de erori de comandă, numai pentru serverele comunitare.\n"
" - `ignored_cogs`: Ignoră unele cogs pentru `prefixed_commands_errors` și `slash_commands_errors`. Trebuie să folosiți numele calificat al cog-ului, cum ar fi `ConsoleLogs` pentru acest cog.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Nu am permisiunile necesare pentru a trimite imagini în acest canal."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Înregistrarea erorilor este activată în {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Dezactivați înregistrarea erorilor într-un canal."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "Înregistrarea erorilor nu este activată în acest canal."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Înregistrarea erorilor este dezactivată în {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Obțineți o inserție pentru a verifica starea buclelor."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: ru\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: ru_RU\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Ког для отображения журналов консоли, с кнопками и опциями фильтрации, а также для отправки команд ошибок в настроенных каналах!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Нет журналов для отображения."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Прокрутка журналов консоли для всех уровней/логгеров или указанного уровня/имени блоггера."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Просмотр журналов консоли по одному, для всех уровней/логгеров или указанного уровня/имени блоггера."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Отображение статистики журналов бота с момента его запуска."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Включение регистрации ошибок в канале.\n\n"
" **Параметры:**\n"
" - `канал`: Канал, в который будут отправляться ошибки команд.\n"
" - `global_errors`: Вести журнал ошибок для всего бота, а не только для сервера канала.\n"
" - `prefixed_commands_errors`: Заносить в журнал ошибки префиксных команд.\n"
" - `слэш_команды_ошибки`: Вести журнал ошибок слэш-команд.\n"
" - `dpy_ignored_exceptions`: Журнал игнорируемых dpy исключений (слушателей событий и ошибок Views).\n"
" - `full_console`: Запись всех логов консоли.\n"
" - `guild_invite`: Добавляет кнопку \"Пригласить в гильдию\" в журналы ошибок команд, только для серверов сообщества.\n"
" - `ignored_cogs`: Игнорировать некоторые коги для `prefixed_commands_errors` и `lash_commands_errors`. Вы должны использовать квалифицированное имя_кога, например `ConsoleLogs` для этого кога.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "У меня нет прав на отправку вложений в этом канале."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Регистрация ошибок включена в {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Отключение регистрации ошибок в канале."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "Регистрация ошибок в этом канале не включена."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Регистрация ошибок отключена в {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Получите вставку для проверки состояния петель."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-21 13:27\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: tr\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: tr_TR\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Düğmeler ve filtre seçenekleri ile konsol günlüklerini görüntülemek ve yapılandırılmış kanallara komut hatalarını göndermek için bir cog!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Görüntülenecek günlük yok."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Tüm seviyeler/günlükçüler veya belirtilen seviye/günlükçü adı için konsol günlüklerini kaydırın."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Tüm seviyeler/günlükçüler veya belirtilen seviye/günlükçü adı için konsol günlüklerini tek tek görüntüleyin."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Bot başlatıldığından beri bot günlüklerinin istatistiklerini görüntüleyin."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Bir kanalda hata günlüğünü etkinleştirin.\n\n"
" **Parametreler:**\n"
" - `channel`: Komut hatalarının gönderileceği kanal.\n"
" - `global_errors`: Hataları sadece kanal sunucusu için değil, tüm bot için günlüğe kaydedin.\n"
" - `prefixed_commands_errors`: Ön ekli komut hatalarını günlüğe kaydedin.\n"
" - `slash_commands_errors`: Eğik çizgi komutları hatalarını günlüğe kaydedin.\n"
" - `dpy_ignored_exceptions`: Dpy yok sayılan istisnaları günlüğe kaydedin (olay dinleyicileri ve Görünüm hataları).\n"
" - `full_console`: Tüm konsol günlüklerini günlüğe kaydedin.\n"
" - `guild_invite`: Sadece topluluk sunucuları için komut hataları günlüklerine bir \"Sunucu Daveti\" düğmesi ekleyin.\n"
" - `ignored_cogs`: Bazı `prefixed_commands_errors` ve `slash_commands_errors` çarklarını göz ardı edin. Bu cog için `ConsoleLogs` gibi cog qualified_name kullanmanız gerekir.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Bu kanalda gömme mesaj göndermek için izinlerim yok."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "{channel.mention} kanalında hata günlükleme etkinleştirildi."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Bir kanalda hata günlüklemeyi devre dışı bırakın."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "Bu kanalda hata günlükleme etkinleştirilmemiş."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "{channel.mention} kanalında hata günlükleme devre dışı bırakıldı."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Döngü durumunu kontrol etmek için bir gömme alın."

View file

@ -0,0 +1,94 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-20 20:24\n"
"Last-Translator: \n"
"Language-Team: Ukrainian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: uk\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/consolelogs/locales/messages.pot\n"
"X-Crowdin-File-ID: 261\n"
"Language: uk_UA\n"
#: consolelogs\consolelogs.py:102
#, docstring
msgid "A cog to display the console logs, with buttons and filter options, and to send commands errors in configured channels!"
msgstr "Гвинтик для відображення логів консолі, з кнопками та опціями фільтрації, а також для надсилання команд про помилки в налаштованих каналах!"
#: consolelogs\consolelogs.py:264
msgid "No logs to display."
msgstr "Немає журналів для відображення."
#: consolelogs\consolelogs.py:415
#, docstring
msgid "Scroll the console logs, for all levels/loggers or provided level/logger name."
msgstr "Прокрутіть журнали консолі для всіх рівнів/логгерів або для вказаного рівня/логгера."
#: consolelogs\consolelogs.py:451
#, docstring
msgid "View the console logs one by one, for all levels/loggers or provided level/logger name."
msgstr "Переглядайте журнали консолі по черзі, для всіх рівнів/логгерів або вказаного рівня/логгера."
#: consolelogs\consolelogs.py:462
#, docstring
msgid "Display the stats for the bot logs since the bot start."
msgstr "Відображати статистику логів бота з моменту запуску бота."
#: consolelogs\consolelogs.py:495
#, docstring
msgid "Enable errors logging in a channel.\n\n"
" **Parameters:**\n"
" - `channel`: The channel where the commands errors will be sent.\n"
" - `global_errors`: Log errors for the entire bot, not just the channel server.\n"
" - `prefixed_commands_errors`: Log prefixed commands errors.\n"
" - `slash_commands_errors`: Log slash commands errors.\n"
" - `dpy_ignored_exceptions`: Log dpy ignored exceptions (events listeners and Views errors).\n"
" - `full_console`: Log all the console logs.\n"
" - `guild_invite`: Add a button \"Guild Invite\" in commands errors logs, only for community servers.\n"
" - `ignored_cogs`: Ignore some cogs for `prefixed_commands_errors` and `slash_commands_errors`. You have to use the cog qualified_name like `ConsoleLogs` for this cog.\n"
" "
msgstr "Увімкнути логування помилок у каналі.\n\n"
" **Параметри:**\n"
" - `channel`: Канал, куди будуть надсилатися помилки команд.\n"
" - `global_errors`: Писати помилки для всього бота, а не тільки для сервера каналу.\n"
" - `prefixed_commands_errors`: Записувати помилки префіксованих команд.\n"
" - `lash_commands_errors`: Журнал помилок косої риски.\n"
" - `dpy_ignored_exceptions`: Журнал винятків, проігнорованих dpy (помилки слухачів подій та Views).\n"
" - `full_console`: Записувати всі журнали консолі.\n"
" - `guild_invite`: Додати кнопку \"Запросити до гільдії\" до логів помилок команд, лише для серверів спільноти.\n"
" - `ignored_cogs`: Ігнорувати деякі гвинтики для `prefixed_commands_errors` та `slash_commands_errors`. Ви повинні використовувати кваліфіковане ім'я гвинтика на кшталт `ConsoleLogs` для цього гвинтика.\n"
" "
#: consolelogs\consolelogs.py:516
msgid "I don't have the permissions to send embeds in this channel."
msgstr "Я не маю дозволу надсилати вставки в цьому каналі."
#: consolelogs\consolelogs.py:532
msgid "Errors logging enabled in {channel.mention}."
msgstr "Логування помилок увімкнено в {channel.mention}."
#: consolelogs\consolelogs.py:540
#, docstring
msgid "Disable errors logging in a channel."
msgstr "Вимкнути реєстрацію помилок у каналі."
#: consolelogs\consolelogs.py:543
msgid "Errors logging isn't enabled in this channel."
msgstr "Логування помилок у цьому каналі не ввімкнено."
#: consolelogs\consolelogs.py:546
msgid "Errors logging disabled in {channel.mention}."
msgstr "Логування помилок вимкнено в {channel.mention}."
#: consolelogs\consolelogs.py:550
#, docstring
msgid "Get an embed to check loops status."
msgstr "Отримайте вбудовування для перевірки стану циклів."

View file

@ -0,0 +1 @@
{"needed_utils_version": 7.0}

9
csvparse/__init__.py Normal file
View file

@ -0,0 +1,9 @@
from redbot.core.bot import Red
from .csvparse import CSVParse
async def setup(bot: Red):
await bot.add_cog(CSVParse(bot))

35
csvparse/csvparse.py Normal file
View file

@ -0,0 +1,35 @@
import csv
from redbot.core import commands
import discord
class CSVParse(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.command(name="parsecsv", help="Parses a CSV file and returns it in a more readable format")
async def parse_csv(self, ctx, *, file: str):
if file.endswith(".csv"):
try:
with open(file, 'r') as f:
csv_data = list(csv.reader(f))
formatted_data = "```\n"
for row in csv_data:
formatted_data += ",".join(row) + "\n"
formatted_data += "```"
if len(formatted_data) > 2000:
with open("output.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(csv_data)
file = discord.File("output.csv", filename="output.csv")
await ctx.send("The output is too large to be sent as a message. Here is the file instead:", file=file)
else:
await ctx.send(formatted_data)
except FileNotFoundError:
await ctx.send("The file was not found. Please make sure the file is in the same directory as the bot.")
else:
await ctx.send("Please upload a CSV file.")

9
csvparse/info.json Normal file
View file

@ -0,0 +1,9 @@
{
"author": ["bencos18 (492089091320446976)"],
"install_msg": "thank for adding my repo\n feel free to message me on discord or create a github issue if you need support or help with anything.",
"short": "some random cogs I made for my own use and decided to make public.",
"description": "csv parsing stuff.",
"tags": ["csv"],
"end_user_data_statement": "This cog does not store any End User Data.",
"type": "COG"
}

13
cyclestatus/__init__.py Normal file
View file

@ -0,0 +1,13 @@
import json
import pathlib
from redbot.core.bot import Red
from .cycle_status import CycleStatus
with open(pathlib.Path(__file__).parent / "info.json") as fp:
__red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"]
async def setup(bot: Red):
await bot.add_cog(CycleStatus(bot))

380
cyclestatus/cycle_status.py Normal file
View file

@ -0,0 +1,380 @@
# Copyright (c) 2021 - Jojo#7791
# Licensed under MIT
from __future__ import annotations
import asyncio
import enum
import logging
import random
import re
try:
from datetime import datetime, UTC as DatetimeUTC
def get_datetime():
return datetime.now(DatetimeUTC)
except ImportError:
from datetime import datetime
def get_datetime():
return datetime.utcnow()
from typing import Any, Final, List, Dict, Optional, TYPE_CHECKING
import discord
from discord.ext import tasks
from redbot.core import Config, commands
from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import humanize_list, humanize_number, pagify
from redbot.core.utils.predicates import MessagePredicate
from .menus import Menu, Page, PositiveInt
log = logging.getLogger("red.JojoCogs.cyclestatus")
_config_structure = {
"global": {
"statuses": [],
"use_help": True,
"next_iter": 0,
"toggled": True, # Toggle if the status should be cycled or not
"random": False,
"status_type": 0, # int, the value corresponds with a `discord.ActivityType` value
"status_mode": "online", # str, the value that corresponds with a `discord.Status` value
},
}
_bot_guild_var: Final[str] = r"{bot_guild_count}"
_bot_member_var: Final[str] = r"{bot_member_count}"
_bot_prefix_var: Final[str] = r"{bot_prefix}"
def humanize_enum_vals(e: enum.Enum) -> str:
return humanize_list(
list(map(lambda c: f"`{c.name.replace('_', ' ')}`", e)) # type:ignore
)
class ActivityType(enum.Enum):
"""Copy of `discord.ActivityType` minus `unknown`"""
playing = 0
listening = 2
watching = 3
custom = 4
competing = 5
def __int__(self):
return self.value
if TYPE_CHECKING:
ActivityConverter = ActivityType
else:
class ActivityConverter(commands.Converter):
async def convert(self, ctx: commands.Context, arg: str) -> ActivityType:
arg = arg.lower()
ret = getattr(ActivityType, arg, None)
if not ret:
raise commands.BadArgument(
f"The argument must be one of the following: {humanize_enum_vals(ActivityType)}"
)
return ret
class Status(enum.Enum):
online = "online"
idle = "idle"
do_not_disturb = dnd = "dnd"
def __str__(self) -> str:
return self.value
if TYPE_CHECKING:
StatusConverter = Status
else:
class StatusConverter(commands.Converter):
async def convert(self, ctx: commands.Context, arg: str) -> Status:
arg = arg.lower().replace(" ", "_")
try:
return Status(arg)
except ValueError:
raise commands.BadArgument(
f"The argument must be one of the following: {humanize_enum_vals(Status)}"
)
class CycleStatus(commands.Cog):
"""Automatically change the status of your bot every minute"""
__authors__: Final[List[str]] = ["Jojo#7791"]
# These people have suggested something for this cog!
__suggesters__: Final[List[str]] = ["ItzXenonUnity | Lou#2369", "StormyGalaxy#1297"]
__version__: Final[str] = "1.0.16"
def __init__(self, bot: Red):
self.bot = bot
self.config = Config.get_conf(self, 115849, True)
self.config.register_global(**_config_structure["global"])
self.toggled: Optional[bool] = None
self.random: Optional[bool] = None
self.last_random: Optional[int] = None
self.main_task.start()
async def cog_load(self) -> None:
self.toggled = await self.config.toggled()
self.random = await self.config.random()
async def cog_unload(self) -> None:
self.main_task.cancel()
def format_help_for_context(self, ctx: commands.Context) -> str:
pre = super().format_help_for_context(ctx)
plural = "s" if len(self.__authors__) > 1 else ""
return (
f"{pre}\n"
f"Author{plural}: `{humanize_list(self.__authors__)}`\n"
f"Version: `{self.__version__}`\n"
f"People who have put in suggestions: `{humanize_list(self.__suggesters__)}`"
)
@commands.command(name="cyclestatusversion", aliases=["csversion"], hidden=True)
async def cycle_status_version(self, ctx: commands.Context):
"""Get the version of Cycle Status that [botname] is running"""
await ctx.send(
f"Cycle Status, Version `{self.__version__}`. Made with :heart: by Jojo#7791"
)
@commands.group(name="cyclestatus", aliases=["cstatus"])
@commands.is_owner()
async def status(self, ctx: commands.Context):
"""Commands working with the status"""
pass
@status.command(name="type")
async def status_type(self, ctx: commands.Context, status: ActivityConverter):
"""Change the type of [botname]'s status
**Arguments**
- `status` The status type. Valid types are
`playing, listening, watching, custom, and competing`
"""
await self.config.status_type.set(status.value)
await ctx.send(f"Done, set the status type to `{status.name}`.")
@status.command(name="mode")
async def status_mode(self, ctx: commands.Context, mode: StatusConverter):
"""Change [botname]'s status mode
**Arguments**
- `mode` The mode type. Valid types are:
`online, idle, dnd, and do not disturb`
"""
await self.config.status_mode.set(mode.value)
await ctx.send(f"Done, set the status mode to `{mode.value}`.")
@status.command()
@commands.check(lambda ctx: ctx.cog.random is False) # type:ignore
async def forcenext(self, ctx: commands.Context):
"""Force the next status to display on the bot"""
nl = await self.config.next_iter()
statuses = await self.config.statuses()
if not statuses:
return await ctx.send("There are no statuses")
if len(statuses) == 1:
await ctx.tick()
return await self._status_add(statuses[0], await self.config.use_help())
try:
status = statuses[nl]
except IndexError:
status = statuses[0]
nl = 0
await self.config.next_iter.set(nl + 1 if nl < len(statuses) else 0)
await self._status_add(status, await self.config.use_help())
await ctx.tick()
@status.command(name="usehelp")
async def status_set(self, ctx: commands.Context, toggle: Optional[bool] = None):
"""Change whether the status should have ` | [p]help`
**Arguments**
- `toggle` Whether help should be used or not.
"""
if toggle is None:
msg = f"Added help is {'enabled' if await self.config.use_help() else 'disabled'}"
return await ctx.send(msg)
await self.config.use_help.set(toggle)
await ctx.tick()
@status.command(name="add")
async def status_add(self, ctx: commands.Context, *, status: str):
"""Add a status to the list
Put `{bot_guild_count}` or `{bot_member_count}` in your message to have the user count and guild count of your bot!
You can also put `{bot_prefix}` in your message to have the bot's prefix be displayed (eg. `{bot_prefix}ping`)
**Arguments**
- `status` The status to add to the cycle.
"""
if len(status) > 100:
return await ctx.send("Statuses cannot be longer than 100 characters.")
async with self.config.statuses() as s:
s.append(status)
await ctx.tick()
@status.command(name="remove", aliases=["del", "rm", "delete"])
async def status_remove(self, ctx: commands.Context, num: Optional[PositiveInt] = None):
"""Remove a status from the list
**Arguments**
- `num` The index of the status you want to remove.
"""
if num is None:
return await ctx.invoke(self.status_list)
num -= 1
async with self.config.statuses() as sts:
if num >= len(sts):
return await ctx.send("You don't have that many statuses, silly")
del sts[num]
await ctx.tick()
@status.command(name="list")
async def status_list(self, ctx: commands.Context):
"""List the available statuses"""
if not (status := await self.config.statuses()):
return await ctx.send("There are no statuses")
await self._show_statuses(ctx=ctx, statuses=status)
@status.command(name="clear")
async def status_clear(self, ctx: commands.Context):
"""Clear all of the statuses"""
msg = await ctx.send("Would you like to clear all of your statuses? (y/n)")
pred = MessagePredicate.yes_or_no()
try:
await self.bot.wait_for("message", check=pred)
except asyncio.TimeoutError:
pass
await msg.delete()
if not pred.result:
return await ctx.send("Okay! I won't remove your statuses")
await self.config.statuses.set([])
await self.bot.change_presence()
await ctx.tick()
@status.command(name="random")
async def status_random(self, ctx: commands.Context, value: bool):
"""Have the bot cycle to a random status
**Arguments**
- `value` Whether to have random statuses be enabled or not
"""
if value == self.random:
enabled = "enabled" if value else "disabled"
return await ctx.send(f"Random statuses are already {enabled}")
self.random = value
await self.config.random.set(value)
now_no_longer = "now" if value else "no longer"
await ctx.send(f"Statuses will {now_no_longer} be random")
@status.command(name="toggle")
async def status_toggle(self, ctx: commands.Context, value: Optional[bool]):
"""Toggle whether the status should be cycled.
This is handy for if you want to keep your statuses but don't want them displayed at the moment
**Arguments**
- `value` Whether to toggle cycling statues
"""
if value is None:
await ctx.send(f"Cycling Statuses is {'enabled' if self.toggled else 'disabled'}")
return
if value == self.toggled:
enabled = "enabled" if value else "disabled"
return await ctx.send(f"Cycling statuses is already {enabled}")
self.toggled = value
await self.config.toggled.set(value)
now_not = "now" if value else "not"
await ctx.send(f"I will {now_not} cycle statuses")
@status.command(name="settings")
async def status_settings(self, ctx: commands.Context):
"""Show your current settings for the cycle status cog"""
settings = {
"Randomized statuses?": "Enabled" if self.random else "Disabled",
"Toggled?": "Yes" if self.toggled else "No",
"Statuses?": f"See `{ctx.clean_prefix}cyclestatus list`",
"Status Type?": ActivityType(await self.config.status_type()).name,
}
title = "Your Cycle Status settings"
kwargs: Dict[str, Any] = {
"content": f"**{title}**\n\n" + "\n".join(f"**{k}** {v}" for k, v in settings.items())
}
if await ctx.embed_requested():
embed = discord.Embed(
title=title, colour=await ctx.embed_colour(), timestamp=get_datetime()
)
[embed.add_field(name=k, value=v, inline=False) for k, v in settings.items()]
kwargs = {"embed": embed}
await ctx.send(**kwargs)
@tasks.loop(minutes=1)
async def main_task(self):
if not (statuses := await self.config.statuses()) or not self.toggled:
return
if self.random:
if self.last_random is not None and len(statuses) > 1:
statuses.pop(self.last_random) # Remove that last picked one
msg = random.choice(statuses)
self.last_random = statuses.index(msg)
else:
try:
# So, sometimes this gets larger than the list of the statuses
# so, if this raises an `IndexError` we need to reset the next iter
msg = statuses[(nl := await self.config.next_iter())]
except IndexError:
nl = 0 # Hard reset
msg = statuses[0]
await self._status_add(msg, await self.config.use_help())
if not self.random:
nl = 0 if len(statuses) - 1 == nl else nl + 1
await self.config.next_iter.set(nl)
@main_task.before_loop
async def main_tas_before_loop(self) -> None:
await self.bot.wait_until_red_ready()
async def _num_lists(self, data: List[str]) -> List[str]:
"""|coro|
Return a list of numbered items
"""
return [f"{num}. {d}" for num, d in enumerate(data, 1)]
async def _show_statuses(self, ctx: commands.Context, statuses: List[str]) -> None:
source = Page(
list(pagify("\n".join(await self._num_lists(statuses)), page_length=400)),
title="Statuses",
)
await Menu(source=source, bot=self.bot, ctx=ctx).start()
async def red_delete_data_for_user(self, *, requester: str, user_id: int) -> None:
"""Nothing to delete"""
return
async def _status_add(self, status: str, use_help: bool) -> None:
status = status.replace(_bot_guild_var, humanize_number(len(self.bot.guilds))).replace(
_bot_member_var, humanize_number(len(self.bot.users))
)
prefix = (await self.bot.get_valid_prefixes())[0]
prefix = re.sub(rf"<@!?{self.bot.user.id}>", f"@{self.bot.user.name}", prefix) # type:ignore
status = status.replace(_bot_prefix_var, prefix)
if use_help:
status += f" | {prefix}help"
# For some reason using `discord.Activity(type=discord.ActivityType.custom)` will result in the bot not changing its status
# So I'm gonna use this until I figure out something better lmao
game = discord.Activity(type=status_type, name=status) if (status_type := await self.config.status_type()) != 4 else discord.CustomActivity(name=status)
await self.bot.change_presence(activity=game, status=await self.config.status_mode())

20
cyclestatus/info.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "cyclestatus",
"short": "Cycle your bot's status",
"description": "Cycle the status on your bot so you don't have to update it constantly",
"end_user_data_statement": "This cog does not store user data",
"install_msg": "Thank you for installing Jojo's cycle status cog!\n\nIf you are running on an older version of python (eg. 3.8.1) you might not be able to have the guild and member counts (I don't know why yet)",
"author": [
"Jojo#7791"
],
"required_cogs": {},
"requirements": [],
"tags": [
"utility",
"status"
],
"min_bot_version": "3.5.0.dev0",
"hidden": false,
"disabled": false,
"type": "COG"
}

172
cyclestatus/menus.py Normal file
View file

@ -0,0 +1,172 @@
# Copyright (c) 2021 - Jojo#7791
# Licensed under MIT
from __future__ import annotations
from typing import TYPE_CHECKING, List, Union, Optional
from contextlib import suppress
import discord
import datetime
from redbot.core import commands
from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import box
from redbot.vendored.discord.ext import menus # type:ignore
__all__ = ["Page", "Menu", "PositiveInt"]
button_emojis = {
(False, True): "\N{BLACK LEFT-POINTING DOUBLE TRIANGLE}",
(False, False): "\N{BLACK LEFT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
(True, False): "\N{BLACK RIGHT-POINTING TRIANGLE}\N{VARIATION SELECTOR-16}",
(True, True): "\N{BLACK RIGHT-POINTING DOUBLE TRIANGLE}",
}
class BaseButton(discord.ui.Button):
def __init__(
self,
forward: bool,
skip: bool,
disabled: bool = False,
) -> None:
super().__init__(style=discord.ButtonStyle.grey, emoji=button_emojis[(forward, skip)], disabled=disabled)
self.forward = forward # If the menu should go to the next page or previous
self.skip = skip # If the menu should step once or go to the first/last page
if TYPE_CHECKING:
self.view: Menu
async def callback(self, inter: discord.Interaction) -> None:
page_num = 1 if self.forward else -1
if self.skip:
page_num = -1 if self.forward else 0 # -1 triggers the `else` clause which sends it to the last page
await self.view.show_checked_page(page_number=page_num)
class StopButton(discord.ui.Button):
if TYPE_CHECKING:
view: Menu
def __init__(self):
super().__init__(
style=discord.ButtonStyle.red,
emoji="\N{HEAVY MULTIPLICATION X}\N{VARIATION SELECTOR-16}",
disabled=False,
)
async def callback(self, inter: discord.Interaction) -> None:
self.view.stop()
with suppress(discord.Forbidden):
if msg := self.view.msg:
await msg.delete()
class Page:
def __init__(self, entries: List[str], title: str):
self.entries = entries
self.title = title
self.max_pages = len(entries)
async def format_page(self, page: str, view: Menu) -> Union[str, discord.Embed]:
ctx = view.ctx
footer = f"Page {view.current_page + 1}/{self.max_pages}"
if ctx and await ctx.embed_requested(): # not gonna embed unless
return discord.Embed(
title=self.title,
colour=await ctx.embed_colour(),
description=page,
timestamp=datetime.datetime.now(tz=datetime.timezone.utc),
).set_footer(text=footer)
return f"**{self.title}**\n\n{page}\n\n{footer}"
class Menu(discord.ui.View):
if TYPE_CHECKING:
ctx: commands.Context
def __init__(self, source: Page, bot: Red, ctx: commands.Context):
super().__init__()
self.bot = bot
self.ctx = ctx
self.source = source
self.msg: discord.Message = None # type:ignore
self.current_page: int = 0
self._add_buttons()
def add_item(self, item: discord.ui.Item):
# Editted to just not add the item if it's disabled
if getattr(item, "disabled", False):
return self
return super().add_item(item)
def _add_buttons(self) -> None:
# Stupid me getting myself excited for something
# that I can't even do lmfao
single_disabled = self.source.max_pages <= 1
multi_disabled = self.source.max_pages <= 5
[self.add_item(i) for i in [
BaseButton(False, True),
BaseButton(False, False),
StopButton(),
BaseButton(True, False),
BaseButton(True, True)
]
]
async def on_timeout(self) -> None:
with suppress(discord.Forbidden):
await self.msg.delete()
async def _get_kwargs_from_page(self, page: str) -> dict:
data = await self.source.format_page(page, self)
if isinstance(data, discord.Embed):
return {"embed": data}
return {"content": data}
async def start(self) -> None:
page = self.source.entries[0]
kwargs = await self._get_kwargs_from_page(page)
self.msg = await self.ctx.send(view=self, **kwargs)
async def interaction_check(self, inter: discord.Interaction) -> bool:
if inter.user.id != self.ctx.author.id:
await inter.response.send_message(
"You are not authorized to use this interaction.",
ephemeral=True,
)
return False
return True
async def show_page(self, page_number: int) -> None:
page = self.source.entries[page_number]
self.current_page = page_number
kwargs = await self._get_kwargs_from_page(page)
await self.msg.edit(view=self, **kwargs)
async def show_checked_page(self, page_number: int) -> None:
max_pages = self.source.max_pages
try:
if max_pages > page_number >= 0:
await self.show_page(page_number)
elif max_pages <= page_number:
await self.show_page(0)
else:
await self.show_page(max_pages - 1)
except IndexError:
pass
if TYPE_CHECKING:
PositiveInt = int
else:
class PositiveInt(commands.Converter):
async def convert(self, ctx: commands.Context, arg: str) -> int:
try:
ret = int(arg)
except ValueError:
raise commands.BadArgument("That was not an integer")
if ret <= 0:
raise commands.BadArgument(f"'{arg}' is not a positive integer")
return ret

37
dankutils/__init__.py Normal file
View file

@ -0,0 +1,37 @@
"""
MIT License
Copyright (c) 2021-present Kuro-Rui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import json
from pathlib import Path
from redbot.core.bot import Red
from .dankutils import DankUtils
with open(Path(__file__).parent / "info.json") as fp:
__red_end_user_data_statement__ = json.load(fp)["end_user_data_statement"]
async def setup(bot: Red):
await bot.add_cog(DankUtils(bot))

121
dankutils/dankutils.py Normal file
View file

@ -0,0 +1,121 @@
"""
MIT License
Copyright (c) 2021-present Kuro-Rui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import asyncio
from random import randint
from typing import Union
import discord
import kuroutils
from redbot.core import commands
from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import humanize_number
from .utils import *
from .views import DoxxView
class DankUtils(kuroutils.Cog):
"""Dank Memer related commands and utilities!"""
__author__ = ["Kuro"]
__version__ = "0.0.2"
def __init__(self, bot: Red):
super().__init__(bot)
@commands.command(aliases=["danktax"])
async def taxcalc(self, ctx: commands.Context, amount: Union[int, float]):
"""Calculate Dank Memer tax!"""
amount = round(amount)
q = humanize_number(amount)
tq1 = humanize_number(total(amount))
tq2 = humanize_number(total(amount, False))
desc = (
f"*If you send `⏣ {q}`, you will pay `⏣ {tq1}`.\n"
f"To spend `⏣ {q}` with tax included, send `⏣ {tq2}`.*"
)
tx = humanize_number(tax(amount))
if not await ctx.embed_requested():
await ctx.send(f"{desc}\n\nTax: ⏣ {tx} (Rate: 1%)")
return
embed = discord.Embed(title="Tax Calc", description=desc, color=await ctx.embed_color())
embed.set_footer(text=f"Tax: ⏣ {tx} (Rate: 1%)")
await ctx.send(embed=embed)
@commands.guild_only()
@commands.cooldown(1, 25, commands.BucketType.channel)
@commands.command(aliases=["heck"])
async def hack(self, ctx: commands.Context, member: discord.Member):
"""Hack someone!"""
if member == ctx.author:
await ctx.send("Umm, please don't DOXX yourself \N{SKULL}")
return
# Mass editing lol
message = await ctx.send(f"{loading(0)} Hacking {member.name} now...")
await asyncio.sleep(2)
try:
await message.edit(content=f"{loading(1)} Finding Discord Login...")
await asyncio.sleep(2)
await message.edit(content=f"{loading(2)} Bypassing 2FA...")
await asyncio.sleep(3)
email, password = get_email_and_password(member)
await message.edit(
content=(
f"{loading(3)} Found login information:\n"
f"**Email**: `{email}`\n"
f"**Password**: `{password}`"
)
)
await asyncio.sleep(4)
await message.edit(content=f"{loading(0)} Fetching user DMs...")
await asyncio.sleep(1)
last_dm = get_last_dm()
await message.edit(content=f"{loading(1)} **Last DM**: `{last_dm}`")
await asyncio.sleep(3)
await message.edit(content=f"{loading(2)} Injecting trojan virus into {member}...")
await asyncio.sleep(2)
await message.edit(content=f"{loading(3)} Virus injected. Finding IP Address...")
await asyncio.sleep(3)
# A valid IP address must be in the form of x.x.x.x, where x is a number from 0-255.
ip_address = f"{randint(0, 255)}.{randint(0, 255)}.{randint(0, 255)}.{randint(0, 255)}"
await message.edit(content=f"{loading(0)} **IP Address**: `{ip_address}`")
await asyncio.sleep(2)
await message.edit(content=f"{loading(1)} Selling user data to the government...")
await asyncio.sleep(2)
await message.edit(
content=f"{loading(2)} Reporting account to Discord for breaking ToS..."
)
await asyncio.sleep(1)
await message.edit(content=f"{commands.context.TICK} Finished hacking {member.name}.")
info = format_doxx_info(email, password, ip_address, last_dm)
info_embed = discord.Embed(
title="Hack Information", description=info, color=await ctx.embed_color()
)
view = DoxxView(info_embed=info_embed)
await view.start(ctx, "The *totally* real and dangerous hack is complete.")
except discord.NotFound:
await ctx.send("Process terminated. The hack failed.")
return

14
dankutils/info.json Normal file
View file

@ -0,0 +1,14 @@
{
"author": ["Kuro"],
"description": "Dank Memer related commands and utilities.",
"disabled": false,
"end_user_data_statement": "This cog does not store any end user data.",
"hidden": false,
"install_msg": "Thanks for installing `DankUtils`! Get started with `[p]help DankUtils`.\nThis cog has docs! Check it out at <https://kuro-cogs.readthedocs.io/en/latest/cogs/dankutils.html>.",
"min_bot_version": "3.5.0",
"name": "DankUtils",
"requirements": ["git+https://github.com/Kuro-Rui/Kuro-Utils"],
"short": "Dank Memer commands.",
"tags": ["dankmemer", "dank memer"],
"type": "COG"
}

141
dankutils/utils.py Normal file
View file

@ -0,0 +1,141 @@
"""
MIT License
Copyright (c) 2021-present Kuro-Rui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import random
from string import ascii_letters, digits, punctuation
from typing import Optional, Union
import discord
import emoji
# Tax utils
def percent(number: Union[int, float]):
"""Change number to percent"""
return number / 100
def tax(dmc: int):
"""Tax = 1%"""
return round(dmc * percent(1))
def total(amount: int, tax_included: Optional[bool] = True):
if tax_included:
return amount + tax(amount)
else:
# Math Moment:
# tax_included_amount = tax_unincluded_amount * 101%
# tax_unincluded_amount = tax_included_amount / 101%
return round(amount / percent(101))
# Hack utils
def loading(step: int):
steps = ["", "", "", ""]
screen = f"[{steps[step]}]"
return screen
def remove_punctuations(text: str):
letters = list(text)
for letter in letters:
if letter in punctuation:
letters.remove(letter)
text = "".join(letters)
return text
def get_email_and_password(user: discord.Member):
name = emoji.replace_emoji(remove_punctuations(user.name), "")
name = name.replace(" ", "")
if name == "":
name = random.choice(
[
"bitchass",
"femaledog",
"freeporn",
"ilovesluts",
"ineedbitches",
"smexyuser69",
"takingashit",
"waiting4u",
]
)
domain = random.choice(
[
"@aol.com",
"@disposablemail.com",
"@edu.com",
"@gmail.com",
"@gmx.net",
"@hotmail.com",
"@icloud.com",
"@msn.com",
"@outlook.com",
"@protonmail.com",
"@yahoo.com",
"@yandex.com",
]
)
email = name + domain
letters = "".join(random.choice(ascii_letters) for _ in range(6))
numbers = "".join(random.choice(digits) for _ in range(5))
puncts = "".join(random.choice(punctuation) for _ in range(4))
password = list(letters + numbers + puncts)
random.shuffle(password)
password = "".join(password).replace("`", "'")
return email, password
def get_last_dm():
return random.choice(
[
"I hope blueballs aren't real.",
"I hope noone sees my nudes folder.",
"I think it's smaller than most.",
"UwU",
"can I see your feet pics?",
"dont frgt to like and subscrube!!",
"honestly I'm pretty sure blue waffle is real and I have it.",
"imagine having a peen as small as mine in 2022",
"man I love my mommy.",
"pwetty pwease?",
"yeah I'm just built different.",
"yeah she goes to another school.",
]
)
def format_doxx_info(email: str, password: str, ip: str, last_dm: str):
info = [
f"`Email :` {email}",
f"`Password :` {password}",
f"`IP Address :` {ip}",
f'`Last DM :` "{last_dm}"',
]
return "\n".join(info)

61
dankutils/views.py Normal file
View file

@ -0,0 +1,61 @@
"""
MIT License
Copyright (c) 2021-present Kuro-Rui
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
import discord
from redbot.core.commands import Context
class DoxxView(discord.ui.View):
def __init__(self, *, info_embed: discord.Embed):
super().__init__(timeout=60.0)
self.info_embed = info_embed
@discord.ui.button(label="Send Details", style=discord.ButtonStyle.blurple)
async def send_button(self, interaction: discord.Interaction, button: discord.ui.Button):
button.style = discord.ButtonStyle.gray
button.disabled = True
await self.message.edit(view=self)
await interaction.response.send_message(embed=self.info_embed, ephemeral=True)
async def start(self, ctx: Context, content: str = None, **kwargs):
self.author = ctx.author
self.ctx = ctx
kwargs["reference"] = ctx.message.to_reference(fail_if_not_exists=False)
kwargs["mention_author"] = False
kwargs["view"] = self
self.message = await ctx.send(content, **kwargs)
async def interaction_check(self, interaction: discord.Interaction) -> bool:
if interaction.user != self.author:
await interaction.response.send_message(
"You are not authorized to interact with this menu.", ephemeral=True
)
return False
return True
async def on_timeout(self):
for child in self.children:
child.style = discord.ButtonStyle.gray
child.disabled = True
await self.message.edit(view=self)

662
dashboard/LICENSE Normal file
View file

@ -0,0 +1,662 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for
software and other kinds of works, specifically designed to ensure
cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
our General Public Licenses are intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights
with two steps: (1) assert copyright on the software, and (2) offer
you this License which gives you legal permission to copy, distribute
and/or modify the software.
A secondary benefit of defending all users' freedom is that
improvements made in alternate versions of the program, if they
receive widespread use, become available for other developers to
incorporate. Many developers of free software are heartened and
encouraged by the resulting cooperation. However, in the case of
software used on network servers, this result may fail to come about.
The GNU General Public License permits making a modified version and
letting the public access it on a server without ever releasing its
source code to the public.
The GNU Affero General Public License is designed specifically to
ensure that, in such cases, the modified source code becomes available
to the community. It requires the operator of a network server to
provide the source code of the modified version running there to the
users of that server. Therefore, public use of a modified version, on
a publicly accessible server, gives the public access to the source
code of the modified version.
An older license, called the Affero General Public License and
published by Affero, was designed to accomplish similar goals. This is
a different license, not a version of the Affero GPL, but Affero has
released a new version of the Affero GPL which permits relicensing under
this license.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the
Program, your modified version must prominently offer all users
interacting with it remotely through a computer network (if your version
supports such interaction) an opportunity to receive the Corresponding
Source of your version by providing access to the Corresponding Source
from a network server at no charge, through some standard or customary
means of facilitating copying of software. This Corresponding Source
shall include the Corresponding Source for any work covered by version 3
of the GNU General Public License that is incorporated pursuant to the
following paragraph.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the work with which it is combined will remain governed by version
3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU Affero General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU Affero General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU Affero General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU Affero General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
Red Discord Bot - Dashboard: An easy-to-use interactive web dashboard to control your Redbot.
Copyright (C) 2020 Neuro Assassin
Copyright (C) 2021 Neuro Assassin, Cog Creators
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer
network, you should also make sure that it provides a way for users to
get its source. For example, if your program is a web application, its
interface could display a "Source" link that leads users to an archive
of the code. There are many ways you could offer source, and different
solutions will be better for different programs; see section 13 for the
specific requirements.
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU AGPL, see
<https://www.gnu.org/licenses/>.

121
dashboard/README.rst Normal file
View file

@ -0,0 +1,121 @@
.. _dashboard:
=========
Dashboard
=========
This is the cog guide for the ``Dashboard`` cog. This guide contains the collection of commands which you can use in the cog.
Through this guide, ``[p]`` will always represent your prefix. Replace ``[p]`` with your own prefix when you use these commands in Discord.
.. note::
Ensure that you are up to date by running ``[p]cog update dashboard``.
If there is something missing, or something that needs improving in this documentation, feel free to create an issue `here <https://github.com/AAA3A-AAA3A/AAA3A-cogs/issues>`_.
This documentation is generated everytime this cog receives an update.
---------------
About this cog:
---------------
Interact with your bot through a web Dashboard!
**Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest
⚠️ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.
---------
Commands:
---------
Here are all the commands included in this cog (19):
* ``[p]dashboard``
Get the link to the Dashboard.
* ``[p]setdashboard``
Configure Dashboard.
* ``[p]setdashboard allinone <all_in_one>``
Run the Dashboard in the bot process, without having to open another window. You have to install Red-Web-Dashboard in your bot venv with Pip and reload the cog.
* ``[p]setdashboard allowunsecurehttprequests <allow_unsecure_http_requests>``
Allow unsecure http requests. This is not recommended for production, but required if you can't set up a SSL certificate.
* ``[p]setdashboard defaultbackgroundtheme <default_background_theme>``
Set the default Background theme of the dashboard.
* ``[p]setdashboard defaultcolor <default_color>``
Set the default Color of the dashboard.
* ``[p]setdashboard defaultsidenavtheme <default_sidenav_theme>``
Set the default Sidenav theme of the dashboard.
* ``[p]setdashboard disabledthirdparties <disabled_third_parties>``
The third parties to disable.
* ``[p]setdashboard flaskflags <flask_flags>``
The flags used to setting the webserver if `all_in_one` is enabled. They are the cli flags of `reddash` without `--rpc-port`.
* ``[p]setdashboard metadescription <meta_description>``
The website long description to use.
* ``[p]setdashboard metaicon <meta_icon>``
The website icon to use.
* ``[p]setdashboard metatitle <meta_title>``
The website title to use.
* ``[p]setdashboard metawebsitedescription <meta_website_description>``
The website short description to use.
* ``[p]setdashboard modalconfig [confirmation=False]``
Set all settings for the cog with a Discord Modal.
* ``[p]setdashboard redirecturi <redirect_uri>``
The redirect uri to use for the Discord OAuth.
* ``[p]setdashboard resetsetting <setting>``
Reset a setting.
* ``[p]setdashboard secret [secret]``
Set the client secret needed for Discord OAuth.
* ``[p]setdashboard showsettings [with_dev=False]``
Show all settings for the cog with defaults and values.
* ``[p]setdashboard supportserver <support_server>``
Set the support server url of your bot.
------------
Installation
------------
If you haven't added my repo before, lets add it first. We'll call it "AAA3A-cogs" here.
.. code-block:: ini
[p]repo add AAA3A-cogs https://github.com/AAA3A-AAA3A/AAA3A-cogs
Now, we can install Dashboard.
.. code-block:: ini
[p]cog install AAA3A-cogs dashboard
Once it's installed, it is not loaded by default. Load it by running the following command:
.. code-block:: ini
[p]load dashboard
----------------
Further Support:
----------------
Check out my docs `here <https://aaa3a-cogs.readthedocs.io/en/latest/>`_.
Mention me in the #support_other-cogs in the `cog support server <https://discord.gg/GET4DVk>`_ if you need any help.
Additionally, feel free to open an issue or pull request to this repo.
--------
Credits:
--------
Thanks to Kreusada for the Python code to automatically generate this documentation!

46
dashboard/__init__.py Normal file
View file

@ -0,0 +1,46 @@
from redbot.core import errors # isort:skip
import importlib
import sys
try:
import AAA3A_utils
except ModuleNotFoundError:
raise errors.CogLoadError(
"The needed utils to run the cog were not found. Please execute the command `[p]pipinstall git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
)
modules = sorted(
[module for module in sys.modules if module.split(".")[0] == "AAA3A_utils"], reverse=True
)
for module in modules:
try:
importlib.reload(sys.modules[module])
except ModuleNotFoundError:
pass
del AAA3A_utils
# import AAA3A_utils
# import json
# import os
# __version__ = AAA3A_utils.__version__
# with open(os.path.join(os.path.dirname(__file__), "utils_version.json"), mode="r") as f:
# data = json.load(f)
# needed_utils_version = data["needed_utils_version"]
# if __version__ > needed_utils_version:
# raise errors.CogLoadError(
# "The needed utils to run the cog has a higher version than the one supported by this version of the cog. Please update the cogs of the `AAA3A-cogs` repo."
# )
# elif __version__ < needed_utils_version:
# raise errors.CogLoadError(
# "The needed utils to run the cog has a lower version than the one supported by this version of the cog. Please execute the command `[p]pipinstall --upgrade git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git`. A restart of the bot isn't necessary."
# )
from redbot.core.bot import Red # isort:skip
from redbot.core.utils import get_end_user_data_statement
from .dashboard import Dashboard
__red_end_user_data_statement__ = get_end_user_data_statement(file=__file__)
async def setup(bot: Red) -> None:
cog = Dashboard(bot)
await bot.add_cog(cog)

428
dashboard/dashboard.py Normal file
View file

@ -0,0 +1,428 @@
from AAA3A_utils import Cog, Settings # isort:skip
from redbot.core import commands, Config # isort:skip
from redbot.core.bot import Red # isort:skip
from redbot.core.i18n import Translator, cog_i18n # isort:skip
import discord # isort:skip
import typing # isort:skip
import argparse
import asyncio
# import importlib
# import sys
from fernet import Fernet
from .rpc import DashboardRPC
# Credits:
# General repo credits.
# Thank you very much to Neuro Assassin for the original code (https://github.com/NeuroAssassin/Toxic-Cogs/tree/master/dashboard)!
_: Translator = Translator("Dashboard", __file__)
class StrConverter(commands.Converter):
async def convert(self, ctx: commands.Context, argument: str) -> str:
return argument
class RedirectURIConverter(commands.Converter):
async def convert(self, ctx: commands.Context, argument: str) -> str:
if not argument.startswith("http"):
raise commands.BadArgument(_("This is not a valid URL."))
if not argument.endswith("/callback"):
raise commands.BadArgument(
_("This is not a valid Dashboard redirect URI: it must end with `/callback`.")
)
return argument
class ThirdPartyConverter(commands.Converter):
async def convert(self, ctx: commands.Context, argument: str) -> str:
cog = ctx.bot.get_cog("Dashboard")
if argument not in cog.rpc.third_parties_handler.third_parties:
raise commands.BadArgument(_("This third party is not available."))
return argument
@cog_i18n(_)
class Dashboard(Cog):
"""Interact with your bot through a web Dashboard!
**Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest
This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.
"""
__authors__: typing.List[str] = ["AAA3A", "Neuro Assassin"]
def __init__(self, bot: Red) -> None:
super().__init__(bot=bot)
self.config: Config = Config.get_conf(
self,
identifier=205192943327321000143939875896557571750,
force_registration=True,
)
self.CONFIG_SCHEMA: int = 2
self.config.register_global(
CONFIG_SCHEMA=None,
all_in_one=False,
flask_flags=[],
webserver={
"core": {
"secret_key": None,
"jwt_secret_key": None,
"secret": None,
"redirect_uri": None,
"allow_unsecure_http_requests": False,
"blacklisted_ips": [],
},
"ui": {
"meta": {
"title": None,
"icon": None,
"website_description": None,
"description": None,
"support_server": None,
"default_color": "success",
"default_background_theme": "white",
"default_sidenav_theme": "white",
},
"sidenav": [
{
"pos": 1,
"name": "builtin-home",
"icon": "ni ni-atom text-success",
"route": "base_blueprint.index",
"session": None,
"owner": False,
"locked": True,
"hidden": False,
},
{
"pos": 2,
"name": "builtin-commands",
"icon": "ni ni-bullet-list-67 text-danger",
"route": "base_blueprint.commands",
"session": None,
"owner": False,
"locked": False,
"hidden": False,
},
{
"pos": 3,
"name": "builtin-dashboard",
"icon": "ni ni-settings text-primary",
"route": "base_blueprint.dashboard",
"session": True,
"owner": False,
"locked": False,
"hidden": False,
},
{
"pos": 4,
"name": "builtin-third_parties",
"icon": "ni ni-diamond text-success",
"route": "third_parties_blueprint.third_parties",
"session": True,
"owner": False,
"locked": False,
"hidden": False,
},
{
"pos": 5,
"name": "builtin-admin",
"icon": "ni ni-badge text-danger",
"route": "base_blueprint.admin",
"session": True,
"owner": True,
"locked": True,
"hidden": False,
},
{
"pos": 6,
"name": "builtin-credits",
"icon": "ni ni-book-bookmark text-info",
"route": "base_blueprint.credits",
"session": None,
"owner": False,
"locked": True,
"hidden": False,
},
{
"pos": 7,
"name": "builtin-login",
"icon": "ni ni-key-25 text-success",
"route": "login_blueprint.login",
"session": False,
"owner": False,
"locked": True,
"hidden": False,
},
{
"pos": 8,
"name": "builtin-logout",
"icon": "ni ni-user-run text-warning",
"route": "login_blueprint.logout",
"session": True,
"owner": False,
"locked": True,
"hidden": False,
},
],
},
"disabled_third_parties": [],
"custom_pages": [],
},
)
_settings: typing.Dict[str, typing.Dict[str, typing.Any]] = {
"all_in_one": {
"converter": bool,
"description": "Run the webserver in the bot process, without having to open another window. You have to install Red-Web-Dashboard in your bot venv with Pip and reload the cog.",
"hidden": True,
"no_slash": True,
},
"flask_flags": {
"converter": commands.Greedy[StrConverter],
"description": "The flags used to setting the webserver if `all_in_one` is enabled. They are the cli flags of `reddash` without `--rpc-port`.",
"hidden": True,
"no_slash": True,
},
"redirect_uri": {
"converter": RedirectURIConverter,
"description": "The redirect uri to use for the Discord OAuth.",
"path": ["webserver", "core", "redirect_uri"],
"aliases": ["redirect"],
},
"allow_unsecure_http_requests": {
"converter": bool,
"description": "Allow unsecure http requests. This is not recommended for production, but required if you can't set up a SSL certificate.",
"path": ["webserver", "core", "allow_unsecure_http_requests"],
"aliases": ["allowunsecure"],
},
"meta_title": {
"converter": str,
"description": "The website title to use.",
"path": ["webserver", "ui", "meta", "title"],
},
"meta_icon": {
"converter": str,
"description": "The website icon to use.",
"path": ["webserver", "ui", "meta", "icon"],
},
"meta_website_description": {
"converter": str,
"description": "The website short description to use.",
"path": ["webserver", "ui", "meta", "website_description"],
},
"meta_description": {
"converter": str,
"description": "The website long description to use.",
"path": ["webserver", "ui", "meta", "description"],
},
"support_server": {
"converter": str,
"description": "Set the support server url of your bot.",
"path": ["webserver", "ui", "meta", "support_server"],
"aliases": ["support"],
},
"default_color": {
"converter": typing.Literal[
"success", "danger", "primary", "info", "warning", "dark"
],
"description": "Set the default Color of the dashboard.",
"path": ["webserver", "ui", "meta", "default_color"],
},
"default_background_theme": {
"converter": typing.Literal["white", "dark"],
"description": "Set the default Background theme of the dashboard.",
"path": ["webserver", "ui", "meta", "default_background_theme"],
},
"default_sidenav_theme": {
"converter": typing.Literal["white", "dark"],
"description": "Set the default Sidenav theme of the dashboard.",
"path": ["webserver", "ui", "meta", "default_sidenav_theme"],
},
"disabled_third_parties": {
"converter": commands.Greedy[ThirdPartyConverter],
"description": "The third parties to disable.",
"path": ["webserver", "disabled_third_parties"],
},
}
self.settings: Settings = Settings(
bot=self.bot,
cog=self,
config=self.config,
group=self.config.GLOBAL,
settings=_settings,
global_path=[],
use_profiles_system=False,
can_edit=True,
commands_group=self.setdashboard,
)
self.app: typing.Optional[typing.Any] = None
self.rpc: DashboardRPC = DashboardRPC(bot=self.bot, cog=self)
async def cog_load(self) -> None:
await super().cog_load()
await self.edit_config_schema()
await self.settings.add_commands()
self.logger.info("Loading cog...")
asyncio.create_task(self.create_app(flask_flags=await self.config.flask_flags()))
async def edit_config_schema(self) -> None:
CONFIG_SCHEMA = await self.config.CONFIG_SCHEMA()
if CONFIG_SCHEMA is None:
CONFIG_SCHEMA = 1
await self.config.CONFIG_SCHEMA(CONFIG_SCHEMA)
if CONFIG_SCHEMA == self.CONFIG_SCHEMA:
return
if CONFIG_SCHEMA == 1:
global_group = self.config._get_base_group(self.config.GLOBAL)
async with global_group() as global_data:
if "default_sidebar_theme" in global_data:
global_data["default_sidenav_theme"] = global_data.pop("default_sidebar_theme")
CONFIG_SCHEMA = 2
await self.config.CONFIG_SCHEMA.set(CONFIG_SCHEMA)
if CONFIG_SCHEMA < self.CONFIG_SCHEMA:
CONFIG_SCHEMA = self.CONFIG_SCHEMA
await self.config.CONFIG_SCHEMA.set(CONFIG_SCHEMA)
self.logger.info(
f"The Config schema has been successfully modified to {self.CONFIG_SCHEMA} for the {self.qualified_name} cog."
)
async def cog_unload(self) -> None:
self.logger.info("Unloading cog...")
if self.app is not None and self.app.server_thread is not None:
await asyncio.to_thread(self.app.server_thread.shutdown)
await asyncio.to_thread(self.app.tasks_manager.stop_tasks)
self.rpc.unload()
await super().cog_unload()
async def create_app(self, flask_flags: str) -> None:
await self.bot.wait_until_red_ready()
if await self.config.webserver.core.secret_key() is None:
await self.config.webserver.core.secret_key.set(Fernet.generate_key().decode())
if await self.config.webserver.core.jwt_secret_key() is None:
await self.config.webserver.core.jwt_secret_key.set(Fernet.generate_key().decode())
if await self.config.all_in_one():
try:
# for module_name in ("flask", "reddash"):
# modules = sorted(
# [module for module in sys.modules if module.split(".")[0] == module_name], reverse=True
# )
# for module in modules:
# try:
# importlib.reload(sys.modules[module])
# except ModuleNotFoundError:
# pass
from reddash import FlaskApp
parser: argparse.ArgumentParser = argparse.ArgumentParser(exit_on_error=False)
parser.add_argument("--host", dest="host", type=str, default="0.0.0.0")
parser.add_argument("--port", dest="port", type=int, default=42356)
# parser.add_argument("--rpc-port", dest="rpcport", type=int, default=6133)
parser.add_argument(
"--interval", dest="interval", type=int, default=5, help=argparse.SUPPRESS
)
parser.add_argument(
"--development", dest="dev", action="store_true", help=argparse.SUPPRESS
)
# parser.add_argument("--instance", dest="instance", type=str, default=None)
args = vars(parser.parse_args(args=flask_flags))
self.app: FlaskApp = FlaskApp(cog=self, **args)
await self.app.create_app()
await self.app.run_app()
except Exception as e:
self.logger.critical("Error when creating the Flask webserver app.", exc_info=e)
@commands.bot_has_permissions(embed_links=True)
@commands.hybrid_command()
async def dashboard(self, ctx: commands.Context) -> None:
"""Get the link to the Dashboard."""
if (dashboard_url := getattr(ctx.bot, "dashboard_url", None)) is None:
raise commands.UserFeedbackCheckFailure(
_(
"Red-Web-Dashboard is not installed. Check <https://red-web-dashboard.readthedocs.io>."
)
)
if not dashboard_url[1] and ctx.author.id not in ctx.bot.owner_ids:
raise commands.UserFeedbackCheckFailure(_("You can't access the Dashboard."))
embed: discord.Embed = discord.Embed(
title=_("Red-Web-Dashboard"),
color=await ctx.embed_color(),
)
url = dashboard_url[0]
if ctx.guild is not None and (
ctx.author.id in ctx.bot.owner_ids or await self.bot.is_mod(ctx.author)
):
url += f"/dashboard/{ctx.guild.id}"
embed.set_footer(text=ctx.guild.name, icon_url=ctx.guild.icon)
embed.url = url
await ctx.send(embed=embed)
@commands.is_owner()
@commands.hybrid_group()
async def setdashboard(self, ctx: commands.Context) -> None:
"""Configure Dashboard."""
pass
@setdashboard.command()
async def secret(self, ctx: commands.Context, *, secret: str = None):
"""Set the client secret needed for Discord OAuth."""
if secret is not None:
await self.config.webserver.core.secret.set(secret)
return
class SecretModal(discord.ui.Modal):
def __init__(_self) -> None:
super().__init__(title="Discord OAuth Secret")
_self.secret: discord.ui.TextInput = discord.ui.TextInput(
label=_("Discord Secret"),
style=discord.TextStyle.short,
custom_id="discord_secret",
)
_self.add_item(_self.secret)
async def on_submit(_self, interaction: discord.Interaction) -> None:
await self.config.webserver.core.secret.set(_self.secret.value)
await interaction.response.send_message(_("Discord OAuth secret set."))
class SecretView(discord.ui.View):
def __init__(_self) -> None:
super().__init__()
_self._message: discord.Message = None
async def on_timeout(_self) -> None:
for child in _self.children:
child: discord.ui.Item
if hasattr(child, "disabled") and not (
isinstance(child, discord.ui.Button)
and child.style == discord.ButtonStyle.url
):
child.disabled = True
try:
await _self._message.edit(view=_self)
except discord.HTTPException:
pass
async def interaction_check(_self, interaction: discord.Interaction) -> bool:
if interaction.user.id not in [ctx.author.id] + list(ctx.bot.owner_ids):
await interaction.response.send_message(
_("You are not allowed to use this interaction."), ephemeral=True
)
return False
return True
@discord.ui.button(label=_("Set Discord OAuth Secret"))
async def set_secret_button(
_self, interaction: discord.Interaction, button: discord.ui.Button
) -> None:
await interaction.response.send_modal(SecretModal())
view = SecretView()
view._message = await ctx.send(
_("Click on the button below to set a secret for Discord OAuth."), view=view
)

15
dashboard/info.json Normal file
View file

@ -0,0 +1,15 @@
{
"author": ["AAA3A", "Neuro Assassin"],
"name": "Dashboard",
"install_msg": "Thank you for installing this cog!\nDo `[p]help CogName` to get the list of commands and their description. If you enjoy my work, please consider donating on [Buy Me a Coffee](<https://www.buymeacoffee.com/aaa3a>) or [Ko-Fi](<https://ko-fi.com/aaa3a>)!\nPlease follow this **installation documentation**: https://red-web-dashboard.readthedocs.io/en/latest\n⚠ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.",
"short": "Interact with your bot through a web Dashboard!",
"description": "Interact with your bot through a web Dashboard! Thank you very much to Neuro for the initial work!",
"tags": [
"dashboard",
"web",
"panel"
],
"requirements": ["git+https://github.com/AAA3A-AAA3A/AAA3A_utils.git", "fernet", "markdown2", "wtforms", "werkzeug", "markupsafe", "itsdangerous"],
"min_bot_version": "3.5.0",
"end_user_data_statement": "This cog does not persistently store data or metadata about users."
}

View file

@ -0,0 +1,71 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-21 15:12\n"
"Last-Translator: \n"
"Language-Team: German\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: de\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dashboard/locales/messages.pot\n"
"X-Crowdin-File-ID: 308\n"
"Language: de_DE\n"
#: dashboard\dashboard.py:32
msgid "This is not a valid URL."
msgstr "Dies ist keine gültige URL."
#: dashboard\dashboard.py:35
msgid "This is not a valid Dashboard redirect URI: it must end with `/callback`."
msgstr "Dies ist kein gültiger Dashboard-Redirect-URI: Er muss mit `/callback` enden."
#: dashboard\dashboard.py:44
msgid "This third party is not available."
msgstr "Diese dritte Partei ist nicht verfügbar."
#: dashboard\dashboard.py:50
#, docstring
msgid "Interact with your bot through a web Dashboard!\n\n"
" **Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.\n"
" "
msgstr "Interagieren Sie mit Ihrem Bot über ein Web-Dashboard!\n\n"
" **Installationsanleitung:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ Dieses Paket ist eine Abspaltung der Arbeit von Neuro Assassin und wird von der Org in keiner Weise befürwortet.\n"
" "
#: dashboard\dashboard.py:347
msgid "Red-Web-Dashboard is not installed. Check <https://red-web-dashboard.readthedocs.io>."
msgstr "Red-Web-Dashboard ist nicht installiert. Prüfen Sie <https://red-web-dashboard.readthedocs.io>."
#: dashboard\dashboard.py:352
msgid "You can't access the Dashboard."
msgstr "Sie können nicht auf das Dashboard zugreifen."
#: dashboard\dashboard.py:354
msgid "Red-Web-Dashboard"
msgstr "Red-Web-Dashboard"
#: dashboard\dashboard.py:374
#, docstring
msgid "Set the client secret needed for Discord OAuth."
msgstr "Legen Sie das Client-Geheimnis fest, das für Discord OAuth benötigt wird."
#: dashboard\dashboard.py:383
msgid "Discord Secret"
msgstr "Diskord Geheimnis"
#: dashboard\dashboard.py:391
msgid "Discord OAuth secret set."
msgstr "Discord OAuth-Geheimsatz."
#: dashboard\dashboard.py:427
msgid "Click on the button below to set a secret for Discord OAuth."
msgstr "Klicken Sie auf die Schaltfläche unten, um ein Geheimnis für Discord OAuth festzulegen."

View file

@ -0,0 +1,71 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-21 15:12\n"
"Last-Translator: \n"
"Language-Team: Greek\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: el\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dashboard/locales/messages.pot\n"
"X-Crowdin-File-ID: 308\n"
"Language: el_GR\n"
#: dashboard\dashboard.py:32
msgid "This is not a valid URL."
msgstr "Αυτό δεν είναι έγκυρο URL."
#: dashboard\dashboard.py:35
msgid "This is not a valid Dashboard redirect URI: it must end with `/callback`."
msgstr "Αυτό δεν είναι έγκυρο URI ανακατεύθυνσης Dashboard: πρέπει να τελειώνει με `/callback`."
#: dashboard\dashboard.py:44
msgid "This third party is not available."
msgstr "Αυτό το τρίτο μέρος δεν είναι διαθέσιμο."
#: dashboard\dashboard.py:50
#, docstring
msgid "Interact with your bot through a web Dashboard!\n\n"
" **Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.\n"
" "
msgstr "Αλληλεπίδραση με το bot σας μέσω ενός πίνακα ελέγχου στο διαδίκτυο!\n\n"
" **Οδηγός εγκατάστασης:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ Αυτό το πακέτο είναι μια διακλάδωση της εργασίας του Neuro Assassin και δεν υποστηρίζεται καθόλου από το Org.\n"
" "
#: dashboard\dashboard.py:347
msgid "Red-Web-Dashboard is not installed. Check <https://red-web-dashboard.readthedocs.io>."
msgstr "Το Red-Web-Dashboard δεν είναι εγκατεστημένο. Ελέγξτε το <https://red-web-dashboard.readthedocs.io>."
#: dashboard\dashboard.py:352
msgid "You can't access the Dashboard."
msgstr "Δεν μπορείτε να έχετε πρόσβαση στον πίνακα ελέγχου."
#: dashboard\dashboard.py:354
msgid "Red-Web-Dashboard"
msgstr "Red-Web-Dashboard"
#: dashboard\dashboard.py:374
#, docstring
msgid "Set the client secret needed for Discord OAuth."
msgstr "Ορίστε το μυστικό του πελάτη που απαιτείται για το Discord OAuth."
#: dashboard\dashboard.py:383
msgid "Discord Secret"
msgstr "Μυστικό Discord"
#: dashboard\dashboard.py:391
msgid "Discord OAuth secret set."
msgstr "Σύνολο μυστικών Discord OAuth."
#: dashboard\dashboard.py:427
msgid "Click on the button below to set a secret for Discord OAuth."
msgstr "Κάντε κλικ στο παρακάτω κουμπί για να ορίσετε ένα μυστικό για το Discord OAuth."

View file

@ -0,0 +1,71 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-21 15:12\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: es-ES\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dashboard/locales/messages.pot\n"
"X-Crowdin-File-ID: 308\n"
"Language: es_ES\n"
#: dashboard\dashboard.py:32
msgid "This is not a valid URL."
msgstr "Esta URL no es válida."
#: dashboard\dashboard.py:35
msgid "This is not a valid Dashboard redirect URI: it must end with `/callback`."
msgstr "Este no es un URI de redirección de Dashboard válido: debe terminar con `/callback`."
#: dashboard\dashboard.py:44
msgid "This third party is not available."
msgstr "Este tercero no está disponible."
#: dashboard\dashboard.py:50
#, docstring
msgid "Interact with your bot through a web Dashboard!\n\n"
" **Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.\n"
" "
msgstr "¡Interactuar con su bot a través de una web Dashboard!\n\n"
" **Guía de instalación:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ Este paquete es un fork del trabajo de Neuro Assassin, y no está respaldado por la Org en absoluto.\n"
" "
#: dashboard\dashboard.py:347
msgid "Red-Web-Dashboard is not installed. Check <https://red-web-dashboard.readthedocs.io>."
msgstr "Red-Web-Dashboard no está instalado. Compruebe <https://red-web-dashboard.readthedocs.io>."
#: dashboard\dashboard.py:352
msgid "You can't access the Dashboard."
msgstr "No puedes acceder al Panel de control."
#: dashboard\dashboard.py:354
msgid "Red-Web-Dashboard"
msgstr "Tablero rojo"
#: dashboard\dashboard.py:374
#, docstring
msgid "Set the client secret needed for Discord OAuth."
msgstr "Establece el secreto de cliente necesario para Discord OAuth."
#: dashboard\dashboard.py:383
msgid "Discord Secret"
msgstr "Secreto de la discordia"
#: dashboard\dashboard.py:391
msgid "Discord OAuth secret set."
msgstr "Conjunto de secretos OAuth de Discord."
#: dashboard\dashboard.py:427
msgid "Click on the button below to set a secret for Discord OAuth."
msgstr "Haz clic en el botón de abajo para establecer un secreto para Discord OAuth."

View file

@ -0,0 +1,71 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-21 15:12\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: fi\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dashboard/locales/messages.pot\n"
"X-Crowdin-File-ID: 308\n"
"Language: fi_FI\n"
#: dashboard\dashboard.py:32
msgid "This is not a valid URL."
msgstr "Tämä ei ole kelvollinen URL-osoite."
#: dashboard\dashboard.py:35
msgid "This is not a valid Dashboard redirect URI: it must end with `/callback`."
msgstr "Tämä ei ole kelvollinen Dashboardin uudelleenohjaus-URI: sen on päätyttävä `/callback`."
#: dashboard\dashboard.py:44
msgid "This third party is not available."
msgstr "Tämä kolmas osapuoli ei ole käytettävissä."
#: dashboard\dashboard.py:50
#, docstring
msgid "Interact with your bot through a web Dashboard!\n\n"
" **Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.\n"
" "
msgstr "Vuorovaikuta botin kanssa web Dashboardin kautta!\n\n"
" **Asennusopas:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ Tämä paketti on haara Neuro Assassinin työstä, eikä Org ole lainkaan sen hyväksymä.\n"
" "
#: dashboard\dashboard.py:347
msgid "Red-Web-Dashboard is not installed. Check <https://red-web-dashboard.readthedocs.io>."
msgstr "Red-Web-Dashboardia ei ole asennettu. Tarkista <https://red-web-dashboard.readthedocs.io>."
#: dashboard\dashboard.py:352
msgid "You can't access the Dashboard."
msgstr "Et pääse kojelautaan."
#: dashboard\dashboard.py:354
msgid "Red-Web-Dashboard"
msgstr "Red-Web-Dashboard"
#: dashboard\dashboard.py:374
#, docstring
msgid "Set the client secret needed for Discord OAuth."
msgstr "Aseta asiakassalaisuus, jota tarvitaan Discord OAuthia varten."
#: dashboard\dashboard.py:383
msgid "Discord Secret"
msgstr "Epäsopu salaisuus"
#: dashboard\dashboard.py:391
msgid "Discord OAuth secret set."
msgstr "Discordin OAuth-salaisuus."
#: dashboard\dashboard.py:427
msgid "Click on the button below to set a secret for Discord OAuth."
msgstr "Napsauta alla olevaa painiketta asettaaksesi salaisuuden Discord OAuthia varten."

View file

@ -0,0 +1,71 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-21 15:12\n"
"Last-Translator: \n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: fr\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dashboard/locales/messages.pot\n"
"X-Crowdin-File-ID: 308\n"
"Language: fr_FR\n"
#: dashboard\dashboard.py:32
msgid "This is not a valid URL."
msgstr "Il ne s'agit pas d'une URL valide."
#: dashboard\dashboard.py:35
msgid "This is not a valid Dashboard redirect URI: it must end with `/callback`."
msgstr "Ce n'est pas un URI de redirection valide pour le tableau de bord : il doit se terminer par `/callback`."
#: dashboard\dashboard.py:44
msgid "This third party is not available."
msgstr "Ce tiers n'est pas disponible."
#: dashboard\dashboard.py:50
#, docstring
msgid "Interact with your bot through a web Dashboard!\n\n"
" **Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.\n"
" "
msgstr "Interagissez avec votre bot à travers un tableau de bord web !\n\n"
" **Guide d'installation:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ Ce paquetage est un fork du travail de Neuro Assassin, et n'est pas du tout approuvé par l'Org.\n"
" "
#: dashboard\dashboard.py:347
msgid "Red-Web-Dashboard is not installed. Check <https://red-web-dashboard.readthedocs.io>."
msgstr "Red-Web-Dashboard n'est pas installé. Vérifiez <https://red-web-dashboard.readthedocs.io>."
#: dashboard\dashboard.py:352
msgid "You can't access the Dashboard."
msgstr "Vous ne pouvez pas accéder au tableau de bord."
#: dashboard\dashboard.py:354
msgid "Red-Web-Dashboard"
msgstr "Tableau de bord rouge"
#: dashboard\dashboard.py:374
#, docstring
msgid "Set the client secret needed for Discord OAuth."
msgstr "Définir le secret client nécessaire pour Discord OAuth."
#: dashboard\dashboard.py:383
msgid "Discord Secret"
msgstr "Secret de discorde"
#: dashboard\dashboard.py:391
msgid "Discord OAuth secret set."
msgstr "Ensemble de secrets OAuth de Discord."
#: dashboard\dashboard.py:427
msgid "Click on the button below to set a secret for Discord OAuth."
msgstr "Cliquez sur le bouton ci-dessous pour définir un secret pour Discord OAuth."

View file

@ -0,0 +1,71 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-21 15:12\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: it\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dashboard/locales/messages.pot\n"
"X-Crowdin-File-ID: 308\n"
"Language: it_IT\n"
#: dashboard\dashboard.py:32
msgid "This is not a valid URL."
msgstr "Questo non è un URL valido."
#: dashboard\dashboard.py:35
msgid "This is not a valid Dashboard redirect URI: it must end with `/callback`."
msgstr "Questo non è un URI di reindirizzamento valido per Dashboard: deve terminare con `/callback`."
#: dashboard\dashboard.py:44
msgid "This third party is not available."
msgstr "Questa terza parte non è disponibile."
#: dashboard\dashboard.py:50
#, docstring
msgid "Interact with your bot through a web Dashboard!\n\n"
" **Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.\n"
" "
msgstr "Interagisci con il tuo bot attraverso una Dashboard web!\n\n"
" **Guida all'installazione:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ Questo pacchetto è un fork del lavoro di Neuro Assassin e non è assolutamente approvato dall'Org.\n"
" "
#: dashboard\dashboard.py:347
msgid "Red-Web-Dashboard is not installed. Check <https://red-web-dashboard.readthedocs.io>."
msgstr "Red-Web-Dashboard non è installato. Controllare <https://red-web-dashboard.readthedocs.io>."
#: dashboard\dashboard.py:352
msgid "You can't access the Dashboard."
msgstr "Non è possibile accedere al Dashboard."
#: dashboard\dashboard.py:354
msgid "Red-Web-Dashboard"
msgstr "Cruscotto rosso"
#: dashboard\dashboard.py:374
#, docstring
msgid "Set the client secret needed for Discord OAuth."
msgstr "Impostare il segreto del client necessario per Discord OAuth."
#: dashboard\dashboard.py:383
msgid "Discord Secret"
msgstr "Segreto di discordia"
#: dashboard\dashboard.py:391
msgid "Discord OAuth secret set."
msgstr "Set di segreti OAuth di Discord."
#: dashboard\dashboard.py:427
msgid "Click on the button below to set a secret for Discord OAuth."
msgstr "Fare clic sul pulsante sottostante per impostare un segreto per Discord OAuth."

View file

@ -0,0 +1,71 @@
msgid ""
msgstr ""
"Project-Id-Version: aaa3a-cogs\n"
"POT-Creation-Date: 2024-07-20 22:14+0200\n"
"PO-Revision-Date: 2024-07-21 15:12\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Crowdin-Project: aaa3a-cogs\n"
"X-Crowdin-Project-ID: 531090\n"
"X-Crowdin-Language: ja\n"
"X-Crowdin-File: /[AAA3A-AAA3A.AAA3A-cogs] main/dashboard/locales/messages.pot\n"
"X-Crowdin-File-ID: 308\n"
"Language: ja_JP\n"
#: dashboard\dashboard.py:32
msgid "This is not a valid URL."
msgstr "これは有効なURLではない。"
#: dashboard\dashboard.py:35
msgid "This is not a valid Dashboard redirect URI: it must end with `/callback`."
msgstr "これは有効な Dashboard リダイレクト URI ではありません: `/callback` で終わっていなければなりません。"
#: dashboard\dashboard.py:44
msgid "This third party is not available."
msgstr "この第三者は利用できない。"
#: dashboard\dashboard.py:50
#, docstring
msgid "Interact with your bot through a web Dashboard!\n\n"
" **Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.\n"
" "
msgstr "ウェブダッシュボードを通してボットと対話する!\n\n"
" **インストールガイド: ** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ 本パッケージは Neuro Assassin のフォークであり、Org によって承認されたものではありません。\n"
" "
#: dashboard\dashboard.py:347
msgid "Red-Web-Dashboard is not installed. Check <https://red-web-dashboard.readthedocs.io>."
msgstr "Red-Web-Dashboard がインストールされていません。<https://red-web-dashboard.readthedocs.io>を確認してください。"
#: dashboard\dashboard.py:352
msgid "You can't access the Dashboard."
msgstr "ダッシュボードにアクセスできません。"
#: dashboard\dashboard.py:354
msgid "Red-Web-Dashboard"
msgstr "レッドダッシュボード"
#: dashboard\dashboard.py:374
#, docstring
msgid "Set the client secret needed for Discord OAuth."
msgstr "Discord OAuthに必要なクライアントシークレットを設定します。"
#: dashboard\dashboard.py:383
msgid "Discord Secret"
msgstr "不和の秘密"
#: dashboard\dashboard.py:391
msgid "Discord OAuth secret set."
msgstr "Discord OAuth シークレットセット。"
#: dashboard\dashboard.py:427
msgid "Click on the button below to set a secret for Discord OAuth."
msgstr "Discord OAuthのシークレットを設定するには、下のボタンをクリックしてください。"

View file

@ -0,0 +1,70 @@
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2024-12-29 10:43+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: redgettext 3.4.2\n"
#: dashboard\dashboard.py:32
msgid "This is not a valid URL."
msgstr ""
#: dashboard\dashboard.py:35
msgid ""
"This is not a valid Dashboard redirect URI: it must end with `/callback`."
msgstr ""
#: dashboard\dashboard.py:44
msgid "This third party is not available."
msgstr ""
#: dashboard\dashboard.py:50
#, docstring
msgid ""
"Interact with your bot through a web Dashboard!\n"
"\n"
" **Installation guide:** https://red-web-dashboard.readthedocs.io/en/latest\n"
" ⚠️ This package is a fork of Neuro Assassin's work, and isn't endorsed by the Org at all.\n"
" "
msgstr ""
#: dashboard\dashboard.py:347
msgid ""
"Red-Web-Dashboard is not installed. Check <https://red-web-"
"dashboard.readthedocs.io>."
msgstr ""
#: dashboard\dashboard.py:352
msgid "You can't access the Dashboard."
msgstr ""
#: dashboard\dashboard.py:354
msgid "Red-Web-Dashboard"
msgstr ""
#: dashboard\dashboard.py:374
#, docstring
msgid "Set the client secret needed for Discord OAuth."
msgstr ""
#: dashboard\dashboard.py:383
msgid "Discord Secret"
msgstr ""
#: dashboard\dashboard.py:391
msgid "Discord OAuth secret set."
msgstr ""
#: dashboard\dashboard.py:414
msgid "You are not allowed to use this interaction."
msgstr ""
#: dashboard\dashboard.py:427
msgid "Click on the button below to set a secret for Discord OAuth."
msgstr ""

Some files were not shown because too many files have changed in this diff Show more