Add Cogs
13
advancedinvite/__init__.py
Normal 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))
|
451
advancedinvite/advanced_invite.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
142
autotraceback/autotraceback.py
Normal 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]
|
56
autotraceback/dashboard_integration.py
Normal 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
|
@ -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."
|
||||
}
|
28
autotraceback/locales/de-DE.po
Normal 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."
|
||||
|
28
autotraceback/locales/el-GR.po
Normal 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 "Καμία εξαίρεση δεν έχει συμβεί ακόμη."
|
||||
|
28
autotraceback/locales/es-ES.po
Normal 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."
|
||||
|
28
autotraceback/locales/fi-FI.po
Normal 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."
|
||||
|
28
autotraceback/locales/fr-FR.po
Normal 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."
|
||||
|
28
autotraceback/locales/it-IT.po
Normal 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."
|
||||
|
28
autotraceback/locales/ja-JP.po
Normal 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 "まだ例外は発生していません。"
|
||||
|
23
autotraceback/locales/messages.pot
Normal 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 ""
|
28
autotraceback/locales/nl-NL.po
Normal 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."
|
||||
|
28
autotraceback/locales/pl-PL.po
Normal 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."
|
||||
|
28
autotraceback/locales/pt-BR.po
Normal 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."
|
||||
|
28
autotraceback/locales/pt-PT.po
Normal 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."
|
||||
|
28
autotraceback/locales/ro-RO.po
Normal 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."
|
||||
|
28
autotraceback/locales/ru-RU.po
Normal 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 "Исключение пока не произошло."
|
||||
|
28
autotraceback/locales/tr-TR.po
Normal 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."
|
||||
|
28
autotraceback/locales/uk-UA.po
Normal 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 "Винятків поки що не було."
|
||||
|
1
autotraceback/utils_version.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"needed_utils_version": 7.0}
|
20
avatar/__init__.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
After Width: | Height: | Size: 137 KiB |
BIN
battleship/data/hit.png
Normal file
After Width: | Height: | Size: 637 B |
BIN
battleship/data/len2.png
Normal file
After Width: | Height: | Size: 391 B |
BIN
battleship/data/len2destroyed.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
BIN
battleship/data/len3.png
Normal file
After Width: | Height: | Size: 570 B |
BIN
battleship/data/len3destroyed.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
battleship/data/len4.png
Normal file
After Width: | Height: | Size: 666 B |
BIN
battleship/data/len4destroyed.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
battleship/data/len5.png
Normal file
After Width: | Height: | Size: 819 B |
BIN
battleship/data/len5destroyed.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
battleship/data/miss.png
Normal file
After Width: | Height: | Size: 508 B |
366
battleship/game.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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)
|
54
consolelogs/dashboard_integration.py
Normal 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
|
@ -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."
|
||||
}
|
94
consolelogs/locales/de-DE.po
Normal 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."
|
||||
|
94
consolelogs/locales/el-GR.po
Normal 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 "Πάρτε μια ενσωμάτωση για να ελέγξετε την κατάσταση των βρόχων."
|
||||
|
94
consolelogs/locales/es-ES.po
Normal 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."
|
||||
|
94
consolelogs/locales/fi-FI.po
Normal 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."
|
||||
|
94
consolelogs/locales/fr-FR.po
Normal 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."
|
||||
|
94
consolelogs/locales/it-IT.po
Normal 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."
|
||||
|
94
consolelogs/locales/ja-JP.po
Normal 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 "ループの状態を確認するためのエンベデッドを取得します。"
|
||||
|
85
consolelogs/locales/messages.pot
Normal 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 ""
|
94
consolelogs/locales/nl-NL.po
Normal 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."
|
||||
|
94
consolelogs/locales/pl-PL.po
Normal 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."
|
||||
|
94
consolelogs/locales/pt-BR.po
Normal 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."
|
||||
|
94
consolelogs/locales/pt-PT.po
Normal 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."
|
||||
|
94
consolelogs/locales/ro-RO.po
Normal 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."
|
||||
|
94
consolelogs/locales/ru-RU.po
Normal 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 "Получите вставку для проверки состояния петель."
|
||||
|
94
consolelogs/locales/tr-TR.po
Normal 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."
|
||||
|
94
consolelogs/locales/uk-UA.po
Normal 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 "Отримайте вбудовування для перевірки стану циклів."
|
||||
|
1
consolelogs/utils_version.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"needed_utils_version": 7.0}
|
9
csvparse/__init__.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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."
|
||||
}
|
71
dashboard/locales/de-DE.po
Normal 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."
|
||||
|
71
dashboard/locales/el-GR.po
Normal 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."
|
||||
|
71
dashboard/locales/es-ES.po
Normal 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."
|
||||
|
71
dashboard/locales/fi-FI.po
Normal 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."
|
||||
|
71
dashboard/locales/fr-FR.po
Normal 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."
|
||||
|
71
dashboard/locales/it-IT.po
Normal 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."
|
||||
|
71
dashboard/locales/ja-JP.po
Normal 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のシークレットを設定するには、下のボタンをクリックしてください。"
|
||||
|
70
dashboard/locales/messages.pot
Normal 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 ""
|