[V3 i18n] Internationalise help for commands and cogs (#1143)

* Framework for internationalised command help

* Translator for class docstring of cog

* Remove references to old context module

* Use CogManagerUI as PoC

* Replace all references to RedContext

* Rename CogI18n object to avoid confusion

* Update docs

* Update i18n docs.

* Store translators in list instead of dict

* Change commands module to package, updated refs in cogs

* Updated docs and more references in cogs

* Resolve syntax error

* Update from merge
This commit is contained in:
Tobotimus 2018-05-12 09:47:49 +10:00 committed by Kowlin
parent 1e60d1c265
commit 15ea5440a3
35 changed files with 575 additions and 259 deletions

View File

@ -0,0 +1,21 @@
.. red commands module documentation
================
Commands Package
================
This package acts almost identically to ``discord.ext.commands``; i.e. they both have the same
attributes. Some of these attributes, however, have been slightly modified, as outlined below.
.. autofunction:: redbot.core.commands.command
.. autofunction:: redbot.core.commands.group
.. autoclass:: redbot.core.commands.Command
:members:
.. autoclass:: redbot.core.commands.Group
:members:
.. autoclass:: redbot.core.commands.Context
:members:

View File

@ -1,10 +0,0 @@
.. red invocation context documentation
==========================
Command Invocation Context
==========================
.. automodule:: redbot.core.context
.. autoclass:: redbot.core.RedContext
:members:

View File

@ -13,11 +13,12 @@ Basic Usage
.. code-block:: python
from discord.ext import commands
from redbot.core.i18n import CogI18n
from redbot.core import commands
from redbot.core.i18n import Translator, cog_i18n
_ = CogI18n("ExampleCog", __file__)
_ = Translator("ExampleCog", __file__)
@cog_i18n(_)
class ExampleCog:
"""description"""
@ -39,16 +40,19 @@ In a command prompt in your cog's package (where yourcog.py is),
create a directory called "locales".
Then do one of the following:
Windows: :code:`python <your python install path>\Tools\i18n\pygettext.py -n -p locales`
Windows: :code:`python <your python install path>\Tools\i18n\pygettext.py -D -n -p locales`
Mac: ?
Linux: :code:`pygettext3 -n -p locales`
Linux: :code:`pygettext3 -D -n -p locales`
This will generate a messages.pot file with strings to be translated
This will generate a messages.pot file with strings to be translated, including
docstrings.
-------------
API Reference
-------------
.. automodule:: redbot.core.i18n
:members:
:special-members: __call__

View File

@ -37,12 +37,12 @@ Welcome to Red - Discord Bot's documentation!
framework_bot
framework_cogmanager
framework_config
framework_context
framework_datamanager
framework_downloader
framework_events
framework_i18n
framework_modlog
framework_commands
framework_rpc
framework_utils

View File

@ -3,17 +3,17 @@ from re import search
from typing import Generator, Tuple, Iterable
import discord
from redbot.core import Config
from redbot.core.i18n import CogI18n
from redbot.core import Config, commands
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import box
from discord.ext import commands
from redbot.core.bot import Red
from .alias_entry import AliasEntry
_ = CogI18n("Alias", __file__)
_ = Translator("Alias", __file__)
@cog_i18n(_)
class Alias:
"""
Alias

View File

@ -1,7 +1,7 @@
from typing import Tuple
from discord.ext import commands
import discord
from redbot.core import commands
class AliasEntry:

View File

@ -7,15 +7,19 @@ import lavalink
import math
import re
import redbot.core
from discord.ext import commands
from redbot.core import Config, checks, bank
from redbot.core import commands
from redbot.core.i18n import Translator, cog_i18n
_ = Translator("Audio", __file__)
from .manager import shutdown_lavalink_server
__version__ = "0.0.5a"
__author__ = ["aikaterna", "billy/bollo/ati"]
@cog_i18n(_)
class Audio:
def __init__(self, bot):
self.bot = bot

View File

@ -1,13 +1,12 @@
import discord
from redbot.core.utils.chat_formatting import box
from redbot.core import checks, bank
from redbot.core.i18n import CogI18n
from discord.ext import commands
from redbot.core import checks, bank, commands
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.bot import Red # Only used for type hints
_ = CogI18n('Bank', __file__)
_ = Translator('Bank', __file__)
def check_global_setting_guildowner():
@ -48,6 +47,7 @@ def check_global_setting_admin():
return commands.check(pred)
@cog_i18n(_)
class Bank:
"""Bank"""

View File

@ -1,17 +1,17 @@
import re
import discord
from discord.ext import commands
from redbot.core import checks, RedContext
from redbot.core import checks, commands
from redbot.core.bot import Red
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.mod import slow_deletion, mass_purge
from redbot.cogs.mod.log import log
_ = CogI18n("Cleanup", __file__)
_ = Translator("Cleanup", __file__)
@cog_i18n(_)
class Cleanup:
"""Commands for cleaning messages"""
@ -19,7 +19,7 @@ class Cleanup:
self.bot = bot
@staticmethod
async def check_100_plus(ctx: RedContext, number: int) -> bool:
async def check_100_plus(ctx: commands.Context, number: int) -> bool:
"""
Called when trying to delete more than 100 messages at once
@ -39,7 +39,7 @@ class Cleanup:
@staticmethod
async def get_messages_for_deletion(
ctx: RedContext, channel: discord.TextChannel, number,
ctx: commands.Context, channel: discord.TextChannel, number,
check=lambda x: True, limit=100, before=None, after=None
) -> list:
"""
@ -75,7 +75,7 @@ class Cleanup:
@commands.group()
@checks.mod_or_permissions(manage_messages=True)
async def cleanup(self, ctx: RedContext):
async def cleanup(self, ctx: commands.Context):
"""Deletes messages."""
if ctx.invoked_subcommand is None:
await ctx.send_help()
@ -83,7 +83,7 @@ class Cleanup:
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def text(self, ctx: RedContext, text: str, number: int):
async def text(self, ctx: commands.Context, text: str, number: int):
"""Deletes last X messages matching the specified text.
Example:
@ -124,7 +124,7 @@ class Cleanup:
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def user(self, ctx: RedContext, user: str, number: int):
async def user(self, ctx: commands.Context, user: str, number: int):
"""Deletes last X messages from specified user.
Examples:
@ -176,7 +176,7 @@ class Cleanup:
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def after(self, ctx: RedContext, message_id: int):
async def after(self, ctx: commands.Context, message_id: int):
"""Deletes all messages after specified message.
To get a message id, enable developer mode in Discord's
@ -215,7 +215,7 @@ class Cleanup:
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def messages(self, ctx: RedContext, number: int):
async def messages(self, ctx: commands.Context, number: int):
"""Deletes last X messages.
Example:
@ -248,7 +248,7 @@ class Cleanup:
@cleanup.command(name='bot')
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def cleanup_bot(self, ctx: RedContext, number: int):
async def cleanup_bot(self, ctx: commands.Context, number: int):
"""Cleans up command messages and messages from the bot."""
channel = ctx.message.channel
@ -295,7 +295,7 @@ class Cleanup:
await slow_deletion(to_delete)
@cleanup.command(name='self')
async def cleanup_self(self, ctx: RedContext, number: int, match_pattern: str = None):
async def cleanup_self(self, ctx: commands.Context, number: int, match_pattern: str = None):
"""Cleans up messages owned by the bot.
By default, all messages are cleaned. If a third argument is specified,

View File

@ -4,13 +4,12 @@ import random
from datetime import datetime
import discord
from discord.ext import commands
from redbot.core import Config, checks
from redbot.core import Config, checks, commands
from redbot.core.utils.chat_formatting import box, pagify
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
_ = CogI18n("CustomCommands", __file__)
_ = Translator("CustomCommands", __file__)
class CCError(Exception):
@ -152,6 +151,7 @@ class CommandObj:
command, value=None)
@cog_i18n(_)
class CustomCommands:
"""Custom commands
Creates commands used to display text"""

View File

@ -1,17 +1,16 @@
from pathlib import Path
import asyncio
from discord.ext import commands
from redbot.core import checks, RedContext
from redbot.core import checks, commands
from redbot.core.bot import Red
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
from redbot.cogs.dataconverter.core_specs import SpecResolver
from redbot.core.utils.chat_formatting import box
_ = CogI18n('DataConverter', __file__)
_ = Translator('DataConverter', __file__)
@cog_i18n(_)
class DataConverter:
"""
Cog for importing Red v2 Data
@ -22,7 +21,7 @@ class DataConverter:
@checks.is_owner()
@commands.command(name="convertdata")
async def dataconversioncommand(self, ctx: RedContext, v2path: str):
async def dataconversioncommand(self, ctx: commands.Context, v2path: str):
"""
Interactive prompt for importing data from Red v2

View File

@ -10,9 +10,9 @@ import sys
from redbot.core import Config
from redbot.core import checks
from redbot.core.data_manager import cog_data_path
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import box, pagify
from discord.ext import commands
from redbot.core import commands
from redbot.core.bot import Red
from .checks import install_agreement
@ -22,9 +22,10 @@ from .installable import Installable
from .log import log
from .repo_manager import RepoManager, Repo
_ = CogI18n('Downloader', __file__)
_ = Translator('Downloader', __file__)
@cog_i18n(_)
class Downloader:
def __init__(self, bot: Red):
self.bot = bot

View File

@ -7,14 +7,13 @@ from enum import Enum
import discord
from redbot.cogs.bank import check_global_setting_guildowner, check_global_setting_admin
from redbot.core import Config, bank
from redbot.core.i18n import CogI18n
from redbot.core import Config, bank, commands
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import pagify, box
from discord.ext import commands
from redbot.core.bot import Red
_ = CogI18n("Economy", __file__)
_ = Translator("Economy", __file__)
logger = logging.getLogger("red.economy")
@ -104,6 +103,7 @@ class SetParser:
raise RuntimeError
@cog_i18n(_)
class Economy:
"""Economy

View File

@ -1,15 +1,15 @@
import discord
from discord.ext import commands
from redbot.core import checks, Config, modlog, RedContext
from redbot.core import checks, Config, modlog, commands
from redbot.core.bot import Red
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import pagify
from redbot.core.utils.mod import is_mod_or_superior
_ = CogI18n("Filter", __file__)
_ = Translator("Filter", __file__)
@cog_i18n(_)
class Filter:
"""Filter-related commands"""
@ -46,7 +46,7 @@ class Filter:
@commands.group(name="filter")
@commands.guild_only()
@checks.mod_or_permissions(manage_messages=True)
async def _filter(self, ctx: RedContext):
async def _filter(self, ctx: commands.Context):
"""Adds/removes words from filter
Use double quotes to add/remove sentences
@ -129,7 +129,7 @@ class Filter:
await ctx.send(_("Those words weren't in the filter."))
@_filter.command(name="names")
async def filter_names(self, ctx: RedContext):
async def filter_names(self, ctx: commands.Context):
"""
Toggles whether or not to check names and nicknames against the filter
This is disabled by default
@ -149,7 +149,7 @@ class Filter:
)
@_filter.command(name="defaultname")
async def filter_default_name(self, ctx: RedContext, name: str):
async def filter_default_name(self, ctx: commands.Context, name: str):
"""
Sets the default name to use if filtering names is enabled
Note that this has no effect if filtering names is disabled

View File

@ -6,12 +6,12 @@ from urllib.parse import quote_plus
import aiohttp
import discord
from redbot.core.i18n import CogI18n
from discord.ext import commands
from redbot.core import commands
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import escape, italics, pagify
_ = CogI18n("General", __file__)
_ = Translator("General", __file__)
class RPS(Enum):
@ -33,6 +33,7 @@ class RPSParser:
raise
@cog_i18n(_)
class General:
"""General commands."""

View File

@ -1,16 +1,16 @@
from random import shuffle
import aiohttp
from discord.ext import commands
from redbot.core.i18n import CogI18n
from redbot.core import checks, Config
from redbot.core.i18n import Translator, cog_i18n
from redbot.core import checks, Config, commands
_ = CogI18n("Image", __file__)
_ = Translator("Image", __file__)
GIPHY_API_KEY = "dc6zaTOxFJmzC"
@cog_i18n(_)
class Image:
"""Image related commands."""
default_global = {

View File

@ -3,21 +3,20 @@ from datetime import datetime, timedelta
from collections import deque, defaultdict, namedtuple
import discord
from discord.ext import commands
from redbot.core import checks, Config, modlog, RedContext
from redbot.core import checks, Config, modlog, commands
from redbot.core.bot import Red
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import box, escape
from .checks import mod_or_voice_permissions, admin_or_voice_permissions, bot_has_voice_permissions
from redbot.core.utils.mod import is_mod_or_superior, is_allowed_by_hierarchy, \
get_audit_reason
from .log import log
_ = CogI18n("Mod", __file__)
_ = Translator("Mod", __file__)
@cog_i18n(_)
class Mod:
"""Moderation tools."""
@ -174,7 +173,7 @@ class Mod:
@commands.group()
@commands.guild_only()
@checks.guildowner_or_permissions(administrator=True)
async def modset(self, ctx: RedContext):
async def modset(self, ctx: commands.Context):
"""Manages server administration settings."""
if ctx.invoked_subcommand is None:
guild = ctx.guild
@ -200,7 +199,7 @@ class Mod:
@modset.command()
@commands.guild_only()
async def hierarchy(self, ctx: RedContext):
async def hierarchy(self, ctx: commands.Context):
"""Toggles role hierarchy check for mods / admins"""
guild = ctx.guild
toggled = await self.settings.guild(guild).respect_hierarchy()
@ -215,7 +214,7 @@ class Mod:
@modset.command()
@commands.guild_only()
async def banmentionspam(self, ctx: RedContext, max_mentions: int=False):
async def banmentionspam(self, ctx: commands.Context, max_mentions: int=False):
"""Enables auto ban for messages mentioning X different people
Accepted values: 5 or superior"""
@ -240,7 +239,7 @@ class Mod:
@modset.command()
@commands.guild_only()
async def deleterepeats(self, ctx: RedContext):
async def deleterepeats(self, ctx: commands.Context):
"""Enables auto deletion of repeated messages"""
guild = ctx.guild
cur_setting = await self.settings.guild(guild).delete_repeats()
@ -254,7 +253,7 @@ class Mod:
@modset.command()
@commands.guild_only()
async def deletedelay(self, ctx: RedContext, time: int=None):
async def deletedelay(self, ctx: commands.Context, time: int=None):
"""Sets the delay until the bot removes the command message.
Must be between -1 and 60.
@ -281,7 +280,7 @@ class Mod:
@modset.command()
@commands.guild_only()
async def reinvite(self, ctx: RedContext):
async def reinvite(self, ctx: commands.Context):
"""Toggles whether an invite will be sent when a user is unbanned via [p]unban.
If this is True, the bot will attempt to create and send a single-use invite
@ -298,7 +297,7 @@ class Mod:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(kick_members=True)
async def kick(self, ctx: RedContext, user: discord.Member, *, reason: str = None):
async def kick(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Kicks user.
If a reason is specified, it will be the reason that shows up
@ -338,7 +337,7 @@ class Mod:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(ban_members=True)
async def ban(self, ctx: RedContext, user: discord.Member, days: str = None, *, reason: str = None):
async def ban(self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None):
"""Bans user and deletes last X days worth of messages.
If days is not a number, it's treated as the first word of the reason.
@ -399,7 +398,7 @@ class Mod:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(ban_members=True)
async def hackban(self, ctx: RedContext, user_id: int, *, reason: str = None):
async def hackban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
"""Preemptively bans user from the server
A user ID needs to be provided in order to ban
@ -452,7 +451,7 @@ class Mod:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(ban_members=True)
async def tempban(self, ctx: RedContext, user: discord.Member, days: int=1, *, reason: str=None):
async def tempban(self, ctx: commands.Context, user: discord.Member, days: int=1, *, reason: str=None):
"""Tempbans the user for the specified number of days"""
guild = ctx.guild
author = ctx.author
@ -500,7 +499,7 @@ class Mod:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(ban_members=True)
async def softban(self, ctx: RedContext, user: discord.Member, *, reason: str = None):
async def softban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Kicks the user, deleting 1 day worth of messages."""
guild = ctx.guild
channel = ctx.channel
@ -579,7 +578,7 @@ class Mod:
@commands.guild_only()
@checks.admin_or_permissions(ban_members=True)
@commands.bot_has_permissions(ban_members=True)
async def unban(self, ctx: RedContext, user_id: int, *, reason: str = None):
async def unban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
"""Unbans the target user.
Requires specifying the target user's ID. To find this, you may either:
@ -637,7 +636,7 @@ class Mod:
.format(invite.url))
@staticmethod
async def get_invite_for_reinvite(ctx: RedContext, max_age: int=86400):
async def get_invite_for_reinvite(ctx: commands.Context, max_age: int=86400):
"""Handles the reinvite logic for getting an invite
to send the newly unbanned user
:returns: :class:`Invite`"""
@ -672,7 +671,7 @@ class Mod:
@commands.guild_only()
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
async def voiceban(self, ctx: RedContext, user: discord.Member, *, reason: str=None):
async def voiceban(self, ctx: commands.Context, user: discord.Member, *, reason: str=None):
"""Bans the target user from speaking and listening in voice channels in the server"""
user_voice_state = user.voice
if user_voice_state is None:
@ -709,7 +708,7 @@ class Mod:
@commands.guild_only()
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
async def voiceunban(self, ctx: RedContext, user: discord.Member, *, reason: str=None):
async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str=None):
"""Unbans the user from speaking/listening in the server's voice channels"""
user_voice_state = user.voice
if user_voice_state is None:
@ -743,7 +742,7 @@ class Mod:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(manage_nicknames=True)
async def rename(self, ctx: RedContext, user: discord.Member, *, nickname=""):
async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname=""):
"""Changes user's nickname
Leaving the nickname empty will remove it."""
@ -763,7 +762,7 @@ class Mod:
@commands.group()
@commands.guild_only()
@checks.mod_or_permissions(manage_channel=True)
async def mute(self, ctx: RedContext):
async def mute(self, ctx: commands.Context):
"""Mutes user in the channel/server"""
if ctx.invoked_subcommand is None:
await ctx.send_help()
@ -772,7 +771,7 @@ class Mod:
@commands.guild_only()
@mod_or_voice_permissions(mute_members=True)
@bot_has_voice_permissions(mute_members=True)
async def voice_mute(self, ctx: RedContext, user: discord.Member,
async def voice_mute(self, ctx: commands.Context, user: discord.Member,
*, reason: str = None):
"""Mutes the user in a voice channel"""
user_voice_state = user.voice
@ -811,7 +810,7 @@ class Mod:
@checks.mod_or_permissions(administrator=True)
@mute.command(name="channel")
@commands.guild_only()
async def channel_mute(self, ctx: RedContext, user: discord.Member, *, reason: str = None):
async def channel_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Mutes user in the current channel"""
author = ctx.message.author
channel = ctx.message.channel
@ -839,7 +838,7 @@ class Mod:
@checks.mod_or_permissions(administrator=True)
@mute.command(name="server", aliases=["guild"])
@commands.guild_only()
async def guild_mute(self, ctx: RedContext, user: discord.Member, *, reason: str = None):
async def guild_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Mutes user in the server"""
author = ctx.message.author
guild = ctx.guild
@ -902,7 +901,7 @@ class Mod:
@commands.group()
@commands.guild_only()
@checks.mod_or_permissions(manage_channel=True)
async def unmute(self, ctx: RedContext):
async def unmute(self, ctx: commands.Context):
"""Unmutes user in the channel/server
Defaults to channel"""
@ -913,7 +912,7 @@ class Mod:
@commands.guild_only()
@mod_or_voice_permissions(mute_members=True)
@bot_has_voice_permissions(mute_members=True)
async def voice_unmute(self, ctx: RedContext, user: discord.Member, *, reason: str = None):
async def voice_unmute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Unmutes the user in a voice channel"""
user_voice_state = user.voice
if user_voice_state:
@ -947,7 +946,7 @@ class Mod:
@checks.mod_or_permissions(administrator=True)
@unmute.command(name="channel")
@commands.guild_only()
async def channel_unmute(self, ctx: RedContext, user: discord.Member, *, reason: str=None):
async def channel_unmute(self, ctx: commands.Context, user: discord.Member, *, reason: str=None):
"""Unmutes user in the current channel"""
channel = ctx.channel
author = ctx.author
@ -970,7 +969,7 @@ class Mod:
@checks.mod_or_permissions(administrator=True)
@unmute.command(name="server", aliases=["guild"])
@commands.guild_only()
async def guild_unmute(self, ctx: RedContext, user: discord.Member, *, reason: str=None):
async def guild_unmute(self, ctx: commands.Context, user: discord.Member, *, reason: str=None):
"""Unmutes user in the server"""
guild = ctx.guild
author = ctx.author
@ -1038,14 +1037,14 @@ class Mod:
@commands.group()
@commands.guild_only()
@checks.admin_or_permissions(manage_channels=True)
async def ignore(self, ctx: RedContext):
async def ignore(self, ctx: commands.Context):
"""Adds servers/channels to ignorelist"""
if ctx.invoked_subcommand is None:
await ctx.send_help()
await ctx.send(await self.count_ignored())
@ignore.command(name="channel")
async def ignore_channel(self, ctx: RedContext, channel: discord.TextChannel=None):
async def ignore_channel(self, ctx: commands.Context, channel: discord.TextChannel=None):
"""Ignores channel
Defaults to current one"""
@ -1058,7 +1057,8 @@ class Mod:
await ctx.send(_("Channel already in ignore list."))
@ignore.command(name="server", aliases=["guild"])
async def ignore_guild(self, ctx: RedContext):
@commands.has_permissions(manage_guild=True)
async def ignore_guild(self, ctx: commands.Context):
"""Ignores current server"""
guild = ctx.guild
if not await self.settings.guild(guild).ignored():
@ -1070,14 +1070,14 @@ class Mod:
@commands.group()
@commands.guild_only()
@checks.admin_or_permissions(manage_channels=True)
async def unignore(self, ctx: RedContext):
async def unignore(self, ctx: commands.Context):
"""Removes servers/channels from ignorelist"""
if ctx.invoked_subcommand is None:
await ctx.send_help()
await ctx.send(await self.count_ignored())
@unignore.command(name="channel")
async def unignore_channel(self, ctx: RedContext, channel: discord.TextChannel=None):
async def unignore_channel(self, ctx: commands.Context, channel: discord.TextChannel=None):
"""Removes channel from ignore list
Defaults to current one"""
@ -1091,7 +1091,8 @@ class Mod:
await ctx.send(_("That channel is not in the ignore list."))
@unignore.command(name="server", aliases=["guild"])
async def unignore_guild(self, ctx: RedContext):
@commands.has_permissions(manage_guild=True)
async def unignore_guild(self, ctx: commands.Context):
"""Removes current guild from ignore list"""
guild = ctx.message.guild
if await self.settings.guild(guild).ignored():
@ -1131,11 +1132,8 @@ class Mod:
chann_ignored and not perms.manage_channels)
@commands.command()
async def names(self, ctx: RedContext, user: discord.Member):
async def names(self, ctx: commands.Context, user: discord.Member):
"""Show previous names/nicknames of a user"""
async with self.settings.user(user).past_names() as name_list:
while None in name_list: # clean out null entries from a bug
name_list.remove(None)
names = await self.settings.user(user).past_names()
nicks = await self.settings.member(user).past_nicks()
msg = ""
@ -1225,7 +1223,7 @@ class Mod:
return True
return False
async def on_command(self, ctx: RedContext):
async def on_command(self, ctx: commands.Context):
"""Currently used for:
* delete delay"""
guild = ctx.guild
@ -1359,15 +1357,13 @@ class Mod:
if entry.target == target:
return entry
async def on_member_update(self, before: discord.Member, after: discord.Member):
async def on_member_update(self, before, after):
if before.name != after.name:
async with self.settings.user(before).past_names() as name_list:
while None in name_list: # clean out null entries from a bug
name_list.remove(None)
if after.name in name_list:
if after.nick in name_list:
# Ensure order is maintained without duplicates occuring
name_list.remove(after.name)
name_list.append(after.name)
name_list.remove(after.nick)
name_list.append(after.nick)
while len(name_list) > 20:
name_list.pop(0)

View File

@ -1,14 +1,14 @@
import discord
from discord.ext import commands
from redbot.core import checks, modlog, RedContext
from redbot.core import checks, modlog, commands
from redbot.core.bot import Red
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import box
_ = CogI18n('ModLog', __file__)
_ = Translator('ModLog', __file__)
@cog_i18n(_)
class ModLog:
"""Log for mod actions"""
@ -17,14 +17,14 @@ class ModLog:
@commands.group()
@checks.guildowner_or_permissions(administrator=True)
async def modlogset(self, ctx: RedContext):
async def modlogset(self, ctx: commands.Context):
"""Settings for the mod log"""
if ctx.invoked_subcommand is None:
await ctx.send_help()
@modlogset.command()
@commands.guild_only()
async def modlog(self, ctx: RedContext, channel: discord.TextChannel = None):
async def modlog(self, ctx: commands.Context, channel: discord.TextChannel = None):
"""Sets a channel as mod log
Leaving the channel parameter empty will deactivate it"""
@ -53,7 +53,7 @@ class ModLog:
@modlogset.command(name='cases')
@commands.guild_only()
async def set_cases(self, ctx: RedContext, action: str = None):
async def set_cases(self, ctx: commands.Context, action: str = None):
"""Enables or disables case creation for each type of mod action"""
guild = ctx.guild
@ -87,7 +87,7 @@ class ModLog:
@modlogset.command()
@commands.guild_only()
async def resetcases(self, ctx: RedContext):
async def resetcases(self, ctx: commands.Context):
"""Resets modlog's cases"""
guild = ctx.guild
await modlog.reset_cases(guild)
@ -95,7 +95,7 @@ class ModLog:
@commands.command()
@commands.guild_only()
async def case(self, ctx: RedContext, number: int):
async def case(self, ctx: commands.Context, number: int):
"""Shows the specified case"""
try:
case = await modlog.get_case(number, ctx.guild, self.bot)
@ -107,7 +107,7 @@ class ModLog:
@commands.command()
@commands.guild_only()
async def reason(self, ctx: RedContext, case: int, *, reason: str = ""):
async def reason(self, ctx: commands.Context, case: int, *, reason: str = ""):
"""Lets you specify a reason for mod-log's cases
Please note that you can only edit cases you are
the owner of unless you are a mod/admin or the server owner"""

View File

@ -5,21 +5,21 @@ from datetime import timedelta
from copy import copy
import contextlib
import discord
from discord.ext import commands
from redbot.core import Config, checks, RedContext
from redbot.core import Config, checks, commands
from redbot.core.utils.chat_formatting import pagify, box
from redbot.core.utils.antispam import AntiSpam
from redbot.core.bot import Red
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.tunnel import Tunnel
_ = CogI18n("Reports", __file__)
_ = Translator("Reports", __file__)
log = logging.getLogger("red.reports")
@cog_i18n(_)
class Reports:
default_guild_settings = {
@ -66,7 +66,7 @@ class Reports:
@checks.admin_or_permissions(manage_guild=True)
@commands.guild_only()
@commands.group(name="reportset")
async def reportset(self, ctx: RedContext):
async def reportset(self, ctx: commands.Context):
"""
settings for reports
"""
@ -74,14 +74,14 @@ class Reports:
@checks.admin_or_permissions(manage_guild=True)
@reportset.command(name="output")
async def setoutput(self, ctx: RedContext, channel: discord.TextChannel):
async def setoutput(self, ctx: commands.Context, channel: discord.TextChannel):
"""sets the output channel"""
await self.config.guild(ctx.guild).output_channel.set(channel.id)
await ctx.send(_("Report Channel Set."))
@checks.admin_or_permissions(manage_guild=True)
@reportset.command(name="toggleactive")
async def report_toggle(self, ctx: RedContext):
async def report_toggle(self, ctx: commands.Context):
"""Toggles whether the Reporting tool is enabled or not"""
active = await self.config.guild(ctx.guild).active()
@ -136,7 +136,6 @@ class Reports:
shared_guilds.append(guild)
if len(shared_guilds) == 0:
raise ValueError("No Qualifying Shared Guilds")
return
if len(shared_guilds) == 1:
return shared_guilds[0]
output = ""
@ -216,7 +215,7 @@ class Reports:
return ticket_number
@commands.group(name="report", invoke_without_command=True)
async def report(self, ctx: RedContext, *, _report: str=""):
async def report(self, ctx: commands.Context, *, _report: str=""):
"""
Follow the prompts to make a report

View File

@ -1,9 +1,8 @@
import discord
from discord.ext import commands
from redbot.core import Config, checks, RedContext
from redbot.core import Config, checks, commands
from redbot.core.utils.chat_formatting import pagify
from redbot.core.bot import Red
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
from .streamtypes import TwitchStream, HitboxStream, MixerStream, PicartoStream, TwitchCommunity, YoutubeStream
from .errors import (OfflineStream, StreamNotFound, APIError, InvalidYoutubeCredentials,
CommunityNotFound, OfflineCommunity, StreamsError, InvalidTwitchCredentials)
@ -15,9 +14,10 @@ import re
CHECK_DELAY = 60
_ = CogI18n("Streams", __file__)
_ = Translator("Streams", __file__)
@cog_i18n(_)
class Streams:
global_defaults = {
@ -64,7 +64,7 @@ class Streams:
self.task = self.bot.loop.create_task(self._stream_alerts())
@commands.command()
async def twitch(self, ctx, channel_name: str):
async def twitch(self, ctx: commands.Context, channel_name: str):
"""Checks if a Twitch channel is streaming"""
token = await self.db.tokens.get_raw(TwitchStream.__name__, default=None)
stream = TwitchStream(name=channel_name,
@ -72,7 +72,7 @@ class Streams:
await self.check_online(ctx, stream)
@commands.command()
async def youtube(self, ctx, channel_id_or_name: str):
async def youtube(self, ctx: commands.Context, channel_id_or_name: str):
"""
Checks if a Youtube channel is streaming
"""
@ -85,24 +85,24 @@ class Streams:
await self.check_online(ctx, stream)
@commands.command()
async def hitbox(self, ctx, channel_name: str):
async def hitbox(self, ctx: commands.Context, channel_name: str):
"""Checks if a Hitbox channel is streaming"""
stream = HitboxStream(name=channel_name)
await self.check_online(ctx, stream)
@commands.command()
async def mixer(self, ctx, channel_name: str):
async def mixer(self, ctx: commands.Context, channel_name: str):
"""Checks if a Mixer channel is streaming"""
stream = MixerStream(name=channel_name)
await self.check_online(ctx, stream)
@commands.command()
async def picarto(self, ctx, channel_name: str):
async def picarto(self, ctx: commands.Context, channel_name: str):
"""Checks if a Picarto channel is streaming"""
stream = PicartoStream(name=channel_name)
await self.check_online(ctx, stream)
async def check_online(self, ctx, stream):
async def check_online(self, ctx: commands.Context, stream):
try:
embed = await stream.is_online()
except OfflineStream:
@ -124,49 +124,49 @@ class Streams:
@commands.group()
@commands.guild_only()
@checks.mod()
async def streamalert(self, ctx):
async def streamalert(self, ctx: commands.Context):
if ctx.invoked_subcommand is None:
await ctx.send_help()
@streamalert.group(name="twitch")
async def _twitch(self, ctx):
async def _twitch(self, ctx: commands.Context):
"""Twitch stream alerts"""
if ctx.invoked_subcommand is None or ctx.invoked_subcommand == self._twitch:
await ctx.send_help()
@_twitch.command(name="channel")
async def twitch_alert_channel(self, ctx: RedContext, channel_name: str):
async def twitch_alert_channel(self, ctx: commands.Context, channel_name: str):
"""Sets a Twitch stream alert notification in the channel"""
await self.stream_alert(ctx, TwitchStream, channel_name.lower())
@_twitch.command(name="community")
async def twitch_alert_community(self, ctx: RedContext, community: str):
async def twitch_alert_community(self, ctx: commands.Context, community: str):
"""Sets a Twitch stream alert notification in the channel
for the specified community."""
await self.community_alert(ctx, TwitchCommunity, community.lower())
@streamalert.command(name="youtube")
async def youtube_alert(self, ctx: RedContext, channel_name_or_id: str):
async def youtube_alert(self, ctx: commands.Context, channel_name_or_id: str):
"""Sets a Youtube stream alert notification in the channel"""
await self.stream_alert(ctx, YoutubeStream, channel_name_or_id)
@streamalert.command(name="hitbox")
async def hitbox_alert(self, ctx, channel_name: str):
async def hitbox_alert(self, ctx: commands.Context, channel_name: str):
"""Sets a Hitbox stream alert notification in the channel"""
await self.stream_alert(ctx, HitboxStream, channel_name)
@streamalert.command(name="mixer")
async def mixer_alert(self, ctx, channel_name: str):
async def mixer_alert(self, ctx: commands.Context, channel_name: str):
"""Sets a Mixer stream alert notification in the channel"""
await self.stream_alert(ctx, MixerStream, channel_name)
@streamalert.command(name="picarto")
async def picarto_alert(self, ctx, channel_name: str):
async def picarto_alert(self, ctx: commands.Context, channel_name: str):
"""Sets a Picarto stream alert notification in the channel"""
await self.stream_alert(ctx, PicartoStream, channel_name)
@streamalert.command(name="stop")
async def streamalert_stop(self, ctx, _all: bool=False):
async def streamalert_stop(self, ctx: commands.Context, _all: bool=False):
"""Stops all stream notifications in the channel
Adding 'yes' will disable all notifications in the server"""
@ -197,7 +197,7 @@ class Streams:
await ctx.send(msg)
@streamalert.command(name="list")
async def streamalert_list(self, ctx):
async def streamalert_list(self, ctx: commands.Context):
streams_list = defaultdict(list)
guild_channels_ids = [c.id for c in ctx.guild.channels]
msg = _("Active stream alerts:\n\n")
@ -218,7 +218,7 @@ class Streams:
for page in pagify(msg):
await ctx.send(page)
async def stream_alert(self, ctx, _class, channel_name):
async def stream_alert(self, ctx: commands.Context, _class, channel_name):
stream = self.get_stream(_class, channel_name)
if not stream:
token = await self.db.tokens.get_raw(_class.__name__, default=None)
@ -251,7 +251,7 @@ class Streams:
await self.add_or_remove(ctx, stream)
async def community_alert(self, ctx, _class, community_name):
async def community_alert(self, ctx: commands.Context, _class, community_name):
community = self.get_community(_class, community_name)
if not community:
token = await self.db.tokens.get_raw(_class.__name__, default=None)
@ -278,13 +278,13 @@ class Streams:
@commands.group()
@checks.mod()
async def streamset(self, ctx):
async def streamset(self, ctx: commands.Context):
if ctx.invoked_subcommand is None:
await ctx.send_help()
@streamset.command()
@checks.is_owner()
async def twitchtoken(self, ctx, token: str):
async def twitchtoken(self, ctx: commands.Context, token: str):
"""Set the Client ID for twitch.
To do this, follow these steps:
@ -302,7 +302,7 @@ class Streams:
@streamset.command()
@checks.is_owner()
async def youtubekey(self, ctx: RedContext, key: str):
async def youtubekey(self, ctx: commands.Context, key: str):
"""Sets the API key for Youtube.
To get one, do the following:
@ -318,14 +318,14 @@ class Streams:
@streamset.group()
@commands.guild_only()
async def mention(self, ctx):
async def mention(self, ctx: commands.Context):
"""Sets mentions for stream alerts."""
if ctx.invoked_subcommand is None or ctx.invoked_subcommand == self.mention:
await ctx.send_help()
@mention.command(aliases=["everyone"])
@commands.guild_only()
async def all(self, ctx):
async def all(self, ctx: commands.Context):
"""Toggles everyone mention"""
guild = ctx.guild
current_setting = await self.db.guild(guild).mention_everyone()
@ -340,7 +340,7 @@ class Streams:
@mention.command(aliases=["here"])
@commands.guild_only()
async def online(self, ctx):
async def online(self, ctx: commands.Context):
"""Toggles here mention"""
guild = ctx.guild
current_setting = await self.db.guild(guild).mention_here()
@ -355,7 +355,7 @@ class Streams:
@mention.command()
@commands.guild_only()
async def role(self, ctx, *, role: discord.Role):
async def role(self, ctx: commands.Context, *, role: discord.Role):
"""Toggles role mention"""
current_setting = await self.db.role(role).mention()
if not role.mentionable:
@ -373,7 +373,7 @@ class Streams:
@streamset.command()
@commands.guild_only()
async def autodelete(self, ctx, on_off: bool):
async def autodelete(self, ctx: commands.Context, on_off: bool):
"""Toggles automatic deletion of notifications for streams that go offline"""
await self.db.guild(ctx.guild).autodelete.set(on_off)
if on_off:
@ -382,7 +382,7 @@ class Streams:
else:
await ctx.send("Notifications will never be deleted.")
async def add_or_remove(self, ctx, stream):
async def add_or_remove(self, ctx: commands.Context, stream):
if ctx.channel.id not in stream.channels:
stream.channels.append(ctx.channel.id)
if stream not in self.streams:
@ -398,7 +398,7 @@ class Streams:
await self.save_streams()
async def add_or_remove_community(self, ctx, community):
async def add_or_remove_community(self, ctx: commands.Context, community):
if ctx.channel.id not in community.channels:
community.channels.append(ctx.channel.id)
if community not in self.communities:

View File

@ -1,16 +1,15 @@
from copy import copy
from discord.ext import commands
import asyncio
import inspect
import discord
from redbot.core import RedContext, Config, checks
from redbot.core.i18n import CogI18n
from redbot.core import Config, checks, commands
from redbot.core.i18n import Translator
_ = CogI18n("Warnings", __file__)
_ = Translator("Warnings", __file__)
async def warning_points_add_check(config: Config, ctx: RedContext, user: discord.Member, points: int):
async def warning_points_add_check(config: Config, ctx: commands.Context, user: discord.Member, points: int):
"""Handles any action that needs to be taken or not based on the points"""
guild = ctx.guild
guild_settings = config.guild(guild)
@ -25,7 +24,7 @@ async def warning_points_add_check(config: Config, ctx: RedContext, user: discor
await create_and_invoke_context(ctx, act["exceed_command"], user)
async def warning_points_remove_check(config: Config, ctx: RedContext, user: discord.Member, points: int):
async def warning_points_remove_check(config: Config, ctx: commands.Context, user: discord.Member, points: int):
guild = ctx.guild
guild_settings = config.guild(guild)
act = {}
@ -39,10 +38,10 @@ async def warning_points_remove_check(config: Config, ctx: RedContext, user: dis
await create_and_invoke_context(ctx, act["drop_command"], user)
async def create_and_invoke_context(realctx: RedContext, command_str: str, user: discord.Member):
async def create_and_invoke_context(realctx: commands.Context, command_str: str, user: discord.Member):
m = copy(realctx.message)
m.content = command_str.format(user=user.mention, prefix=realctx.prefix)
fctx = await realctx.bot.get_context(m, cls=RedContext)
fctx = await realctx.bot.get_context(m, cls=commands.Context)
try:
await realctx.bot.invoke(fctx)
except (commands.CheckFailure, commands.CommandOnCooldown):
@ -69,7 +68,7 @@ def get_command_from_input(bot, userinput: str):
return "{prefix}" + orig, None
async def get_command_for_exceeded_points(ctx: RedContext):
async def get_command_for_exceeded_points(ctx: commands.Context):
"""Gets the command to be executed when the user is at or exceeding
the points threshold for the action"""
await ctx.send(
@ -102,7 +101,7 @@ async def get_command_for_exceeded_points(ctx: RedContext):
return command
async def get_command_for_dropping_points(ctx: RedContext):
async def get_command_for_dropping_points(ctx: commands.Context):
"""
Gets the command to be executed when the user drops below the points
threshold

View File

@ -1,21 +1,20 @@
from collections import namedtuple
from discord.ext import commands
import discord
import asyncio
from redbot.cogs.warnings.helpers import warning_points_add_check, get_command_for_exceeded_points, \
get_command_for_dropping_points, warning_points_remove_check
from redbot.core import Config, modlog, checks
from redbot.core import Config, modlog, checks, commands
from redbot.core.bot import Red
from redbot.core.context import RedContext
from redbot.core.i18n import CogI18n
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.mod import is_admin_or_superior
from redbot.core.utils.chat_formatting import warning, pagify
_ = CogI18n("Warnings", __file__)
_ = Translator("Warnings", __file__)
@cog_i18n(_)
class Warnings:
"""A warning system for Red"""
@ -51,14 +50,14 @@ class Warnings:
@commands.group()
@commands.guild_only()
@checks.guildowner_or_permissions(administrator=True)
async def warningset(self, ctx: RedContext):
async def warningset(self, ctx: commands.Context):
"""Warning settings"""
if ctx.invoked_subcommand is None:
await ctx.send_help()
@warningset.command()
@commands.guild_only()
async def allowcustomreasons(self, ctx: RedContext, allowed: bool):
async def allowcustomreasons(self, ctx: commands.Context, allowed: bool):
"""Allow or disallow custom reasons for a warning"""
guild = ctx.guild
await self.config.guild(guild).allow_custom_reasons.set(allowed)
@ -69,14 +68,14 @@ class Warnings:
@commands.group()
@commands.guild_only()
@checks.guildowner_or_permissions(administrator=True)
async def warnaction(self, ctx: RedContext):
async def warnaction(self, ctx: commands.Context):
"""Action management"""
if ctx.invoked_subcommand is None:
await ctx.send_help()
@warnaction.command(name="add")
@commands.guild_only()
async def action_add(self, ctx: RedContext, name: str, points: int):
async def action_add(self, ctx: commands.Context, name: str, points: int):
"""Create an action to be taken at a specified point count
Duplicate action names are not allowed"""
guild = ctx.guild
@ -125,7 +124,7 @@ class Warnings:
@warnaction.command(name="del")
@commands.guild_only()
async def action_del(self, ctx: RedContext, action_name: str):
async def action_del(self, ctx: commands.Context, action_name: str):
"""Delete the point count action with the specified name"""
guild = ctx.guild
guild_settings = self.config.guild(guild)
@ -146,14 +145,14 @@ class Warnings:
@commands.group()
@commands.guild_only()
@checks.guildowner_or_permissions(administrator=True)
async def warnreason(self, ctx: RedContext):
async def warnreason(self, ctx: commands.Context):
"""Add reasons for warnings"""
if ctx.invoked_subcommand is None:
await ctx.send_help()
@warnreason.command(name="add")
@commands.guild_only()
async def reason_add(self, ctx: RedContext, name: str, points: int, *, description: str):
async def reason_add(self, ctx: commands.Context, name: str, points: int, *, description: str):
"""Add a reason to be available for warnings"""
guild = ctx.guild
@ -177,7 +176,7 @@ class Warnings:
@warnreason.command(name="del")
@commands.guild_only()
async def reason_del(self, ctx: RedContext, reason_name: str):
async def reason_del(self, ctx: commands.Context, reason_name: str):
"""Delete the reason with the specified name"""
guild = ctx.guild
guild_settings = self.config.guild(guild)
@ -190,7 +189,7 @@ class Warnings:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(ban_members=True)
async def reasonlist(self, ctx: RedContext):
async def reasonlist(self, ctx: commands.Context):
"""List all configured reasons for warnings"""
guild = ctx.guild
guild_settings = self.config.guild(guild)
@ -210,7 +209,7 @@ class Warnings:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(ban_members=True)
async def actionlist(self, ctx: RedContext):
async def actionlist(self, ctx: commands.Context):
"""List the actions to be taken at specific point values"""
guild = ctx.guild
guild_settings = self.config.guild(guild)
@ -232,7 +231,7 @@ class Warnings:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(ban_members=True)
async def warn(self, ctx: RedContext, user: discord.Member, reason: str):
async def warn(self, ctx: commands.Context, user: discord.Member, reason: str):
"""Warn the user for the specified reason
Reason must be a registered reason, or custom if custom reasons are allowed"""
reason_type = {}
@ -276,7 +275,7 @@ class Warnings:
@commands.command()
@commands.guild_only()
async def warnings(self, ctx: RedContext, userid: int=None):
async def warnings(self, ctx: commands.Context, userid: int=None):
"""Show warnings for the specified user.
If userid is None, show warnings for the person running the command
Note that showing warnings for users other than yourself requires
@ -326,7 +325,7 @@ class Warnings:
@commands.command()
@commands.guild_only()
@checks.admin_or_permissions(ban_members=True)
async def unwarn(self, ctx: RedContext, user_id: int, warn_id: str):
async def unwarn(self, ctx: commands.Context, user_id: int, warn_id: str):
"""Removes the specified warning from the user specified"""
guild = ctx.guild
member = guild.get_member(user_id)
@ -347,7 +346,7 @@ class Warnings:
await ctx.tick()
@staticmethod
async def custom_warning_reason(ctx: RedContext):
async def custom_warning_reason(ctx: commands.Context):
"""Handles getting description and points for custom reasons"""
to_add = {
"points": 0,

View File

@ -1,7 +1,6 @@
from .config import Config
from .context import RedContext
__all__ = ["Config", "RedContext", "__version__"]
__all__ = ["Config", "__version__"]
class VersionInfo:

View File

@ -20,7 +20,7 @@ from .cog_manager import CogManager
from . import (
Config,
i18n,
RedContext,
commands,
rpc
)
from .help_formatter import Help, help as help_
@ -193,7 +193,7 @@ class RedBase(BotBase, RpcMethodMixin):
admin_role = await self.db.guild(member.guild).admin_role()
return any(role.id in (mod_role, admin_role) for role in member.roles)
async def get_context(self, message, *, cls=RedContext):
async def get_context(self, message, *, cls=commands.Context):
return await super().get_context(message, cls=cls)
def list_packages(self):

View File

@ -8,11 +8,10 @@ from typing import Tuple, Union, List
import redbot.cogs
import discord
from . import checks
from . import checks, commands
from .config import Config
from .i18n import CogI18n
from .i18n import Translator, cog_i18n
from .data_manager import cog_data_path
from discord.ext import commands
from .utils.chat_formatting import box, pagify
@ -303,10 +302,13 @@ class CogManager:
invalidate_caches()
_ = CogI18n("CogManagerUI", __file__)
_ = Translator("CogManagerUI", __file__)
@cog_i18n(_)
class CogManagerUI:
"""Commands to interface with Red's cog manager."""
async def visible_paths(self, ctx):
install_path = await ctx.bot.cog_mgr.install_path()
cog_paths = await ctx.bot.cog_mgr.paths()

View File

@ -0,0 +1,4 @@
from discord.ext.commands import *
from .commands import *
from .context import *

View File

@ -0,0 +1,74 @@
"""Module for command helpers and classes.
This module contains extended classes and functions which are intended to
replace those from the `discord.ext.commands` module.
"""
import inspect
from discord.ext import commands
__all__ = ["Command", "Group", "command", "group"]
class Command(commands.Command):
"""Command class for Red.
This should not be created directly, and instead via the decorator.
This class inherits from `discord.ext.commands.Command`.
"""
def __init__(self, *args, **kwargs):
self._help_override = kwargs.pop('help_override', None)
super().__init__(*args, **kwargs)
self.translator = kwargs.pop("i18n", None)
@property
def help(self):
"""Help string for this command.
If the :code:`help` kwarg was passed into the decorator, it will
default to that. If not, it will attempt to translate the docstring
of the command's callback function.
"""
if self._help_override is not None:
return self._help_override
if self.translator is None:
translator = lambda s: s
else:
translator = self.translator
return inspect.cleandoc(translator(self.callback.__doc__))
@help.setter
def help(self, value):
# We don't want our help property to be overwritten, namely by super()
pass
class Group(Command, commands.Group):
"""Group command class for Red.
This class inherits from `discord.ext.commands.Group`, with `Command` mixed
in.
"""
pass
# decorators
def command(name=None, cls=Command, **attrs):
"""A decorator which transforms an async function into a `Command`.
Same interface as `discord.ext.commands.command`.
"""
attrs["help_override"] = attrs.pop("help", None)
return commands.command(name, cls, **attrs)
def group(name=None, **attrs):
"""A decorator which transforms an async function into a `Group`.
Same interface as `discord.ext.commands.group`.
"""
return command(name, cls=Group, **attrs)

View File

@ -1,26 +1,23 @@
"""
The purpose of this module is to allow for Red to further customise the command
invocation context provided by discord.py.
"""
import asyncio
from typing import Iterable, List
import discord
from discord.ext import commands
from redbot.core.utils.chat_formatting import box
__all__ = ["RedContext"]
TICK = "\N{WHITE HEAVY CHECK MARK}"
__all__ = ["Context"]
class RedContext(commands.Context):
class Context(commands.Context):
"""Command invocation context for Red.
All context passed into commands will be of this type.
This class inherits from `commands.Context <discord.ext.commands.Context>`.
This class inherits from `discord.ext.commands.Context`.
"""
async def send_help(self) -> List[discord.Message]:

View File

@ -16,13 +16,12 @@ from distutils.version import StrictVersion
import aiohttp
import discord
import pkg_resources
from discord.ext import commands
from redbot.core import __version__
from redbot.core import checks
from redbot.core import i18n
from redbot.core import rpc
from redbot.core.context import RedContext
from redbot.core import commands
from .utils import TYPE_CHECKING
from .utils.chat_formatting import pagify, box, inline
@ -39,9 +38,10 @@ OWNER_DISCLAIMER = ("⚠ **Only** the person who is hosting Red should be "
"system.** ⚠")
_ = i18n.CogI18n("Core", __file__)
_ = i18n.Translator("Core", __file__)
@i18n.cog_i18n(_)
class Core:
"""Commands related to core functions"""
def __init__(self, bot):
@ -52,7 +52,7 @@ class Core:
rpc.add_method('core', self.rpc_reload)
@commands.command()
async def info(self, ctx: RedContext):
async def info(self, ctx: commands.Context):
"""Shows info about Red"""
author_repo = "https://github.com/Twentysix26"
org_repo = "https://github.com/Cog-Creators"
@ -103,7 +103,7 @@ class Core:
await ctx.send("I need the `Embed links` permission to send this")
@commands.command()
async def uptime(self, ctx: RedContext):
async def uptime(self, ctx: commands.Context):
"""Shows Red's uptime"""
since = ctx.bot.uptime.strftime("%Y-%m-%d %H:%M:%S")
passed = self.get_bot_uptime()
@ -134,7 +134,7 @@ class Core:
return fmt.format(d=days, h=hours, m=minutes, s=seconds)
@commands.group()
async def embedset(self, ctx: RedContext):
async def embedset(self, ctx: commands.Context):
"""
Commands for toggling embeds on or off.
@ -157,7 +157,7 @@ class Core:
@embedset.command(name="global")
@checks.is_owner()
async def embedset_global(self, ctx: RedContext):
async def embedset_global(self, ctx: commands.Context):
"""
Toggle the global embed setting.
@ -175,7 +175,7 @@ class Core:
@embedset.command(name="guild")
@checks.guildowner_or_permissions(administrator=True)
async def embedset_guild(self, ctx: RedContext, enabled: bool=None):
async def embedset_guild(self, ctx: commands.Context, enabled: bool=None):
"""
Toggle the guild's embed setting.
@ -200,7 +200,7 @@ class Core:
)
@embedset.command(name="user")
async def embedset_user(self, ctx: RedContext, enabled: bool=None):
async def embedset_user(self, ctx: commands.Context, enabled: bool=None):
"""
Toggle the user's embed setting.
@ -834,7 +834,7 @@ class Core:
@commands.command()
@checks.is_owner()
async def listlocales(self, ctx: RedContext):
async def listlocales(self, ctx: commands.Context):
"""
Lists all available locales

View File

@ -7,9 +7,8 @@ from contextlib import redirect_stdout
from copy import copy
import discord
from discord.ext import commands
from . import checks
from .i18n import CogI18n
from . import checks, commands
from .i18n import Translator
from .utils.chat_formatting import box, pagify
"""
Notice:
@ -19,7 +18,7 @@ Notice:
https://github.com/Rapptz/RoboDanny/blob/master/cogs/repl.py
"""
_ = CogI18n("Dev", __file__)
_ = Translator("Dev", __file__)
class Dev:

View File

@ -1,5 +1,5 @@
"""The checks in this module run on every command."""
from discord.ext import commands
from . import commands
def init_global_checks(bot):

View File

@ -28,7 +28,6 @@ from collections import namedtuple
from typing import List
import discord
from discord.ext import commands
from discord.ext.commands import formatter
import inspect
import itertools
@ -36,6 +35,8 @@ import re
import sys
import traceback
from . import commands
EMPTY_STRING = u'\u200b'
@ -133,7 +134,12 @@ class Help(formatter.HelpFormatter):
'fields': []
}
description = self.command.description if not self.is_cog() else inspect.getdoc(self.command)
if self.is_cog():
translator = getattr(self.command, '__translator__', lambda s: s)
description = inspect.cleandoc(translator(self.command.__doc__))
else:
description = self.command.description
if not description == '' and description is not None:
description = '*{0}*'.format(description)

View File

@ -1,7 +1,10 @@
import re
from pathlib import Path
__all__ = ['get_locale', 'set_locale', 'reload_locales', 'CogI18n']
from . import commands
__all__ = ['get_locale', 'set_locale', 'reload_locales', 'cog_i18n',
'Translator']
_current_locale = 'en_us'
@ -13,7 +16,7 @@ IN_MSGSTR = 4
MSGID = 'msgid "'
MSGSTR = 'msgstr "'
_i18n_cogs = {}
_translators = []
def get_locale():
@ -27,8 +30,8 @@ def set_locale(locale):
def reload_locales():
for cog_name, i18n in _i18n_cogs.items():
i18n.load_translations()
for translator in _translators:
translator.load_translations()
def _parse(translation_file):
@ -145,25 +148,36 @@ def get_locale_path(cog_folder: Path, extension: str) -> Path:
return cog_folder / 'locales' / "{}.{}".format(get_locale(), extension)
class CogI18n:
class Translator:
"""Function to get translated strings at runtime."""
def __init__(self, name, file_location):
"""
Initializes the internationalization object for a given cog.
Initializes an internationalization object.
:param name: Your cog name.
:param file_location:
Parameters
----------
name : str
Your cog name.
file_location : `str` or `pathlib.Path`
This should always be ``__file__`` otherwise your localizations
will not load.
"""
self.cog_folder = Path(file_location).resolve().parent
self.cog_name = name
self.translations = {}
_i18n_cogs.update({self.cog_name: self})
_translators.append(self)
self.load_translations()
def __call__(self, untranslated: str):
"""Translate the given string.
This will look for the string in the translator's :code:`.pot` file,
with respect to the current locale.
"""
normalized_untranslated = _normalize(untranslated, True)
try:
return self.translations[normalized_untranslated]
@ -172,7 +186,7 @@ class CogI18n:
def load_translations(self):
"""
Loads the current translations for this cog.
Loads the current translations.
"""
self.translations = {}
translation_file = None
@ -201,3 +215,14 @@ class CogI18n:
if translated:
self.translations.update({untranslated: translated})
def cog_i18n(translator: Translator):
"""Get a class decorator to link the translator to this cog."""
def decorator(cog_class: type):
cog_class.__translator__ = translator
for name, attr in cog_class.__dict__.items():
if isinstance(attr, (commands.Group, commands.Command)):
attr.translator = translator
setattr(cog_class, name, attr)
return cog_class
return decorator

View File

@ -0,0 +1,197 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ORGANIZATION
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: 2017-12-06 11:27+1100\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=cp1252\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
#: ../cog_manager.py:21
#, docstring
msgid ""
"Directory manager for Red's cogs.\n"
"\n"
" This module allows you to load cogs from multiple directories and even from\n"
" outside the bot directory. You may also set a directory for downloader to\n"
" install new cogs to, the default being the :code:`cogs/` folder in the root\n"
" bot directory.\n"
" "
msgstr ""
#: ../cog_manager.py:40
#, docstring
msgid ""
"Get all currently valid path directories.\n"
"\n"
" Returns\n"
" -------\n"
" `tuple` of `pathlib.Path`\n"
" All valid cog paths.\n"
"\n"
" "
msgstr ""
#: ../cog_manager.py:64
#, docstring
msgid ""
"Get the install path for 3rd party cogs.\n"
"\n"
" Returns\n"
" -------\n"
" pathlib.Path\n"
" The path to the directory where 3rd party cogs are stored.\n"
"\n"
" "
msgstr ""
#: ../cog_manager.py:273
#, docstring
msgid ""
"Finds the names of all available modules to load.\n"
" "
msgstr ""
#: ../cog_manager.py:285
#, docstring
msgid ""
"Re-evaluate modules in the py cache.\n"
"\n"
" This is an alias for an importlib internal and should be called\n"
" any time that a new module has been installed to a cog directory.\n"
" "
msgstr ""
#: ../cog_manager.py:298
#, docstring
msgid ""
"Commands to interface with Red's cog manager."
msgstr ""
"(TRANSLATED) Commands to interface with Red's cog manager."
#: ../cog_manager.py:302
#, docstring
msgid ""
"\n"
" Lists current cog paths in order of priority."
" "
msgstr ""
"\n"
" (TRANSLATED) Lists current cog paths in order of priority."
" "
#: ../cog_manager.py:321
#, docstring
msgid ""
"\n"
" Add a path to the list of available cog paths."
" "
msgstr ""
"\n"
" (TRANSLATED) Add a path to the list of available cog paths."
" "
#: ../cog_manager.py:340
#, docstring
msgid ""
"\n"
" Removes a path from the available cog paths given the path_number"
" from !paths"
" "
msgstr ""
"\n"
" (TRANSLATED) Removes a path from the available cog paths given the path_number"
" from !paths"
" "
#: ../cog_manager.py:357
#, docstring
msgid ""
"\n"
" Reorders paths internally to allow discovery of different cogs."
" "
msgstr ""
"\n"
" (TRANSLATED) Reorders paths internally to allow discovery of different cogs."
" "
#: ../cog_manager.py:383
#, docstring
msgid ""
"\n"
" Returns the current install path or sets it if one is provided."
" The provided path must be absolute or relative to the bot's"
" directory and it must already exist."
"\n"
" No installed cogs will be transferred in the process."
" "
msgstr ""
"\n"
" (TRANSLATED) Returns the current install path or sets it if one is provided."
" The provided path must be absolute or relative to the bot's"
" directory and it must already exist."
"\n"
" No installed cogs will be transferred in the process."
" "
#: ../cog_manager.py:406
#, docstring
msgid ""
"\n"
" Lists all loaded and available cogs."
" "
msgstr ""
"\n"
" (TRANSLATED) Lists all loaded and available cogs."
" "
#: ../cog_manager.py:309
msgid ""
"Install Path: {}\n"
"\n"
msgstr ""
#: ../cog_manager.py:325
msgid "That path is does not exist or does not point to a valid directory."
msgstr ""
#: ../cog_manager.py:334
msgid "Path successfully added."
msgstr ""
#: ../cog_manager.py:347
msgid "That is an invalid path number."
msgstr ""
#: ../cog_manager.py:351
msgid "Path successfully removed."
msgstr ""
#: ../cog_manager.py:367
msgid "Invalid 'from' index."
msgstr ""
#: ../cog_manager.py:373
msgid "Invalid 'to' index."
msgstr ""
#: ../cog_manager.py:377
msgid "Paths reordered."
msgstr ""
#: ../cog_manager.py:395
msgid "That path does not exist."
msgstr ""
#: ../cog_manager.py:399
msgid "The bot will install new cogs to the `{}` directory."
msgstr ""

View File

@ -7,10 +7,10 @@ Ported to Red V3 by Palm__ (https://github.com/palmtree5)
import asyncio
import discord
from redbot.core import RedContext
from redbot.core import commands
async def menu(ctx: RedContext, pages: list,
async def menu(ctx: commands.Context, pages: list,
controls: dict,
message: discord.Message=None, page: int=0,
timeout: float=30.0):
@ -28,7 +28,7 @@ async def menu(ctx: RedContext, pages: list,
Parameters
----------
ctx: RedContext
ctx: commands.Context
The command context
pages: `list` of `str` or `discord.Embed`
The pages of the menu.
@ -92,7 +92,7 @@ async def menu(ctx: RedContext, pages: list,
timeout, react.emoji)
async def next_page(ctx: RedContext, pages: list,
async def next_page(ctx: commands.Context, pages: list,
controls: dict, message: discord.Message, page: int,
timeout: float, emoji: str):
perms = message.channel.permissions_for(ctx.guild.me)
@ -109,7 +109,7 @@ async def next_page(ctx: RedContext, pages: list,
page=page, timeout=timeout)
async def prev_page(ctx: RedContext, pages: list,
async def prev_page(ctx: commands.Context, pages: list,
controls: dict, message: discord.Message, page: int,
timeout: float, emoji: str):
perms = message.channel.permissions_for(ctx.guild.me)
@ -126,7 +126,7 @@ async def prev_page(ctx: RedContext, pages: list,
page=next_page, timeout=timeout)
async def close_menu(ctx: RedContext, pages: list,
async def close_menu(ctx: commands.Context, pages: list,
controls: dict, message: discord.Message, page: int,
timeout: float, emoji: str):
if message: