From 443f2ca556a25de440562f502a8362ce9b31bdf7 Mon Sep 17 00:00:00 2001 From: Toby Harradine Date: Fri, 5 Oct 2018 17:37:26 +1000 Subject: [PATCH] [i18n] Pass over modlog, permissions, reports, streams, trivia, warnings Signed-off-by: Toby Harradine --- redbot/cogs/mod/mod.py | 16 +- redbot/cogs/modlog/modlog.py | 73 +++--- redbot/cogs/permissions/converters.py | 57 +++-- redbot/cogs/permissions/permissions.py | 19 +- redbot/cogs/reports/reports.py | 45 ++-- redbot/cogs/streams/streams.py | 308 +++++++++++++------------ redbot/cogs/trivia/session.py | 71 +++--- redbot/cogs/trivia/trivia.py | 206 +++++++++-------- redbot/cogs/warnings/warnings.py | 117 ++++++---- redbot/core/utils/tunnel.py | 21 +- 10 files changed, 498 insertions(+), 435 deletions(-) diff --git a/redbot/cogs/mod/mod.py b/redbot/cogs/mod/mod.py index cea005bfa..5c4b6c1f6 100644 --- a/redbot/cogs/mod/mod.py +++ b/redbot/cogs/mod/mod.py @@ -386,7 +386,7 @@ class Mod(commands.Cog): async def ban( self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None ): - """Ban a user from the current server. + """Ban a user from this server. Deletes `` worth of messages. @@ -469,7 +469,7 @@ class Mod(commands.Cog): @commands.bot_has_permissions(ban_members=True) @checks.admin_or_permissions(ban_members=True) async def hackban(self, ctx: commands.Context, user_id: int, *, reason: str = None): - """Pre-emptively ban a user from the current server. + """Pre-emptively ban a user from this server. A user ID needs to be provided in order to ban using this command. @@ -531,7 +531,7 @@ class Mod(commands.Cog): async def tempban( self, ctx: commands.Context, user: discord.Member, days: int = 1, *, reason: str = None ): - """Temporarily ban a user from the current server.""" + """Temporarily ban a user from this server.""" guild = ctx.guild author = ctx.author days_delta = timedelta(days=int(days)) @@ -672,7 +672,7 @@ class Mod(commands.Cog): @commands.bot_has_permissions(ban_members=True) @checks.admin_or_permissions(ban_members=True) async def unban(self, ctx: commands.Context, user_id: int, *, reason: str = None): - """Unban a user from the current server. + """Unban a user from this server. Requires specifying the target user's ID. To find this, you may either: 1. Copy it from the mod log case (if one was created), or @@ -1104,7 +1104,7 @@ class Mod(commands.Cog): async def unmute_channel( self, ctx: commands.Context, user: discord.Member, *, reason: str = None ): - """Unmute a user in the current channel.""" + """Unmute a user in this channel.""" channel = ctx.channel author = ctx.author guild = ctx.guild @@ -1137,7 +1137,7 @@ class Mod(commands.Cog): async def unmute_guild( self, ctx: commands.Context, user: discord.Member, *, reason: str = None ): - """Unmute a user in the current server.""" + """Unmute a user in this server.""" guild = ctx.guild author = ctx.author @@ -1236,7 +1236,7 @@ class Mod(commands.Cog): @ignore.command(name="server", aliases=["guild"]) @checks.admin_or_permissions(manage_guild=True) async def ignore_guild(self, ctx: commands.Context): - """Ignore commands in the current server.""" + """Ignore commands in this server.""" guild = ctx.guild if not await self.settings.guild(guild).ignored(): await self.settings.guild(guild).ignored.set(True) @@ -1270,7 +1270,7 @@ class Mod(commands.Cog): @unignore.command(name="server", aliases=["guild"]) @checks.admin_or_permissions(manage_guild=True) async def unignore_guild(self, ctx: commands.Context): - """Remove the current server from the ignore list.""" + """Remove this server from the ignore list.""" guild = ctx.message.guild if await self.settings.guild(guild).ignored(): await self.settings.guild(guild).ignored.set(False) diff --git a/redbot/cogs/modlog/modlog.py b/redbot/cogs/modlog/modlog.py index 5cdf5c894..aa0b40b8c 100644 --- a/redbot/cogs/modlog/modlog.py +++ b/redbot/cogs/modlog/modlog.py @@ -1,3 +1,5 @@ +from typing import Optional + import discord from redbot.core import checks, modlog, commands @@ -10,7 +12,7 @@ _ = Translator("ModLog", __file__) @cog_i18n(_) class ModLog(commands.Cog): - """Log for mod actions""" + """Manage log channels for moderation actions.""" def __init__(self, bot: Red): super().__init__() @@ -19,23 +21,28 @@ class ModLog(commands.Cog): @commands.group() @checks.guildowner_or_permissions(administrator=True) async def modlogset(self, ctx: commands.Context): - """Settings for the mod log""" + """Manage modlog settings.""" pass @modlogset.command() @commands.guild_only() async def modlog(self, ctx: commands.Context, channel: discord.TextChannel = None): - """Sets a channel as mod log + """Set a channel as the modlog. - Leaving the channel parameter empty will deactivate it""" + Omit `` to disable the modlog. + """ guild = ctx.guild if channel: if channel.permissions_for(guild.me).send_messages: await modlog.set_modlog_channel(guild, channel) - await ctx.send(_("Mod events will be sent to {}").format(channel.mention)) + await ctx.send( + _("Mod events will be sent to {channel}").format(channel=channel.mention) + ) else: await ctx.send( - _("I do not have permissions to send messages in {}!").format(channel.mention) + _("I do not have permissions to send messages in {channel}!").format( + channel=channel.mention + ) ) else: try: @@ -49,39 +56,36 @@ class ModLog(commands.Cog): @modlogset.command(name="cases") @commands.guild_only() async def set_cases(self, ctx: commands.Context, action: str = None): - """Enables or disables case creation for each type of mod action""" + """Enable or disable case creation for a mod action.""" guild = ctx.guild if action is None: # No args given casetypes = await modlog.get_all_casetypes(guild) await ctx.send_help() - title = _("Current settings:") - msg = "" + lines = [] for ct in casetypes: - enabled = await ct.is_enabled() - value = "enabled" if enabled else "disabled" - msg += "%s : %s\n" % (ct.name, value) + enabled = "enabled" if await ct.is_enabled() else "disabled" + lines.append(f"{ct.name} : {enabled}") - msg = title + "\n" + box(msg) - await ctx.send(msg) + await ctx.send(_("Current settings:\n") + box("\n".join(lines))) return + casetype = await modlog.get_casetype(action, guild) if not casetype: await ctx.send(_("That action is not registered")) else: - enabled = await casetype.is_enabled() - await casetype.set_enabled(True if not enabled else False) - - msg = _("Case creation for {} actions is now {}.").format( - action, "enabled" if not enabled else "disabled" + await casetype.set_enabled(not enabled) + await ctx.send( + _("Case creation for {action_name} actions is now {enabled}.").format( + action_name=action, enabled="enabled" if not enabled else "disabled" + ) ) - await ctx.send(msg) @modlogset.command() @commands.guild_only() async def resetcases(self, ctx: commands.Context): - """Resets modlog's cases""" + """Reset all modlog cases in this server.""" guild = ctx.guild await modlog.reset_cases(guild) await ctx.send(_("Cases have been reset.")) @@ -89,7 +93,7 @@ class ModLog(commands.Cog): @commands.command() @commands.guild_only() async def case(self, ctx: commands.Context, number: int): - """Shows the specified case""" + """Show the specified case.""" try: case = await modlog.get_case(number, ctx.guild, self.bot) except RuntimeError: @@ -101,24 +105,21 @@ class ModLog(commands.Cog): else: await ctx.send(await case.message_content(embed=False)) - @commands.command(usage="[case] ") + @commands.command() @commands.guild_only() - async def reason(self, ctx: commands.Context, *, reason: str): - """Lets you specify a reason for mod-log's cases - + async def reason(self, ctx: commands.Context, case: Optional[int], *, reason: str): + """Specify a reason for a modlog case. + Please note that you can only edit cases you are - the owner of unless you are a mod/admin or the server owner. - - If no number is specified, the latest case will be used.""" + the owner of unless you are a mod, admin or server owner. + + If no case number is specified, the latest case will be used. + """ author = ctx.author guild = ctx.guild - potential_case = reason.split()[0] - if potential_case.isdigit(): - case = int(potential_case) - reason = reason.replace(potential_case, "") - else: - case = str(int(await modlog.get_next_case_number(guild)) - 1) - # latest case + if case is None: + # get the latest case + case = int(await modlog.get_next_case_number(guild)) - 1 try: case_before = await modlog.get_case(case, guild, self.bot) except RuntimeError: diff --git a/redbot/cogs/permissions/converters.py b/redbot/cogs/permissions/converters.py index 0cfaddbc8..1b1668dc6 100644 --- a/redbot/cogs/permissions/converters.py +++ b/redbot/cogs/permissions/converters.py @@ -1,5 +1,9 @@ -from typing import NamedTuple, Union, Optional +from typing import NamedTuple, Union, Optional, cast, Type + from redbot.core import commands +from redbot.core.i18n import Translator + +_ = Translator("PermissionsConverters", __file__) class CogOrCommand(NamedTuple): @@ -18,39 +22,34 @@ class CogOrCommand(NamedTuple): return cls(type="COMMAND", name=cmd.qualified_name, obj=cmd) raise commands.BadArgument( - 'Cog or command "{arg}" not found. Please note that this is case sensitive.' - "".format(arg=arg) + _( + 'Cog or command "{name}" not found. Please note that this is case sensitive.' + ).format(name=arg) ) -class RuleType: +def RuleType(arg: str) -> bool: + if arg.lower() in ("allow", "whitelist", "allowed"): + return True + if arg.lower() in ("deny", "blacklist", "denied"): + return False - # noinspection PyUnusedLocal - @classmethod - async def convert(cls, ctx: commands.Context, arg: str) -> bool: - if arg.lower() in ("allow", "whitelist", "allowed"): - return True - if arg.lower() in ("deny", "blacklist", "denied"): - return False - - raise commands.BadArgument( - '"{arg}" is not a valid rule. Valid rules are "allow" or "deny"'.format(arg=arg) - ) + raise commands.BadArgument( + _('"{arg}" is not a valid rule. Valid rules are "allow" or "deny"').format(arg=arg) + ) -class ClearableRuleType: +def ClearableRuleType(arg: str) -> Optional[bool]: + if arg.lower() in ("allow", "whitelist", "allowed"): + return True + if arg.lower() in ("deny", "blacklist", "denied"): + return False + if arg.lower() in ("clear", "reset"): + return None - # noinspection PyUnusedLocal - @classmethod - async def convert(cls, ctx: commands.Context, arg: str) -> Optional[bool]: - if arg.lower() in ("allow", "whitelist", "allowed"): - return True - if arg.lower() in ("deny", "blacklist", "denied"): - return False - if arg.lower() in ("clear", "reset"): - return None - - raise commands.BadArgument( + raise commands.BadArgument( + _( '"{arg}" is not a valid rule. Valid rules are "allow" or "deny", or "clear" to ' - "remove the rule".format(arg=arg) - ) + "remove the rule" + ).format(arg=arg) + ) diff --git a/redbot/cogs/permissions/permissions.py b/redbot/cogs/permissions/permissions.py index 48346b8d2..f0bbd13d8 100644 --- a/redbot/cogs/permissions/permissions.py +++ b/redbot/cogs/permissions/permissions.py @@ -2,7 +2,7 @@ import asyncio import io import textwrap from copy import copy -from typing import Union, Optional, Dict, List, Tuple, Any, Iterator, ItemsView +from typing import Union, Optional, Dict, List, Tuple, Any, Iterator, ItemsView, cast import discord import yaml @@ -287,9 +287,11 @@ class Permissions(commands.Cog): `` is the user, channel, role or server the rule is for. """ - # noinspection PyTypeChecker await self._add_rule( - rule=allow_or_deny, cog_or_cmd=cog_or_command, model_id=who_or_what.id, guild_id=0 + rule=cast(bool, allow_or_deny), + cog_or_cmd=cog_or_command, + model_id=who_or_what.id, + guild_id=0, ) await ctx.send(_("Rule added.")) @@ -312,9 +314,8 @@ class Permissions(commands.Cog): `` is the user, channel or role the rule is for. """ - # noinspection PyTypeChecker await self._add_rule( - rule=allow_or_deny, + rule=cast(bool, allow_or_deny), cog_or_cmd=cog_or_command, model_id=who_or_what.id, guild_id=ctx.guild.id, @@ -381,9 +382,10 @@ class Permissions(commands.Cog): `` is the cog or command to set the default rule for. This is case sensitive. """ - # noinspection PyTypeChecker await self._set_default_rule( - rule=allow_or_deny, cog_or_cmd=cog_or_command, guild_id=ctx.guild.id + rule=cast(Optional[bool], allow_or_deny), + cog_or_cmd=cog_or_command, + guild_id=ctx.guild.id, ) await ctx.send(_("Default set.")) @@ -403,9 +405,8 @@ class Permissions(commands.Cog): `` is the cog or command to set the default rule for. This is case sensitive. """ - # noinspection PyTypeChecker await self._set_default_rule( - rule=allow_or_deny, cog_or_cmd=cog_or_command, guild_id=GLOBAL + rule=cast(Optional[bool], allow_or_deny), cog_or_cmd=cog_or_command, guild_id=GLOBAL ) await ctx.send(_("Default set.")) diff --git a/redbot/cogs/reports/reports.py b/redbot/cogs/reports/reports.py index 08cfc1d8a..61b000897 100644 --- a/redbot/cogs/reports/reports.py +++ b/redbot/cogs/reports/reports.py @@ -1,6 +1,6 @@ import logging import asyncio -from typing import Union +from typing import Union, List from datetime import timedelta from copy import copy import contextlib @@ -60,23 +60,20 @@ class Reports(commands.Cog): @commands.guild_only() @commands.group(name="reportset") async def reportset(self, ctx: commands.Context): - """ - Settings for the report system. - """ + """Manage Reports.""" pass @checks.admin_or_permissions(manage_guild=True) @reportset.command(name="output") - async def setoutput(self, ctx: commands.Context, channel: discord.TextChannel): - """Set the channel where reports will show up""" + async def reportset_output(self, ctx: commands.Context, channel: discord.TextChannel): + """Set the channel where reports will be sent.""" await self.config.guild(ctx.guild).output_channel.set(channel.id) await ctx.send(_("The report channel has been set.")) @checks.admin_or_permissions(manage_guild=True) @reportset.command(name="toggle", aliases=["toggleactive"]) - async def report_toggle(self, ctx: commands.Context): - """Enables or Disables reporting for the server""" - + async def reportset_toggle(self, ctx: commands.Context): + """Enable or Disable reporting for this server.""" active = await self.config.guild(ctx.guild).active() active = not active await self.config.guild(ctx.guild).active.set(active) @@ -168,7 +165,7 @@ class Reports(commands.Cog): if channel is None: return None - files = await Tunnel.files_from_attatch(msg) + files: List[discord.File] = await Tunnel.files_from_attatch(msg) ticket_number = await self.config.guild(guild).next_ticket() await self.config.guild(guild).next_ticket.set(ticket_number + 1) @@ -204,11 +201,10 @@ class Reports(commands.Cog): @commands.group(name="report", invoke_without_command=True) async def report(self, ctx: commands.Context, *, _report: str = ""): - """ - Send a report. + """Send a report. Use without arguments for interactive reporting, or do - [p]report to use it non-interactively. + `[p]report ` to use it non-interactively. """ author = ctx.author guild = ctx.guild @@ -323,9 +319,8 @@ class Reports(commands.Cog): @checks.mod_or_permissions(manage_members=True) @report.command(name="interact") async def response(self, ctx, ticket_number: int): - """ - Open a message tunnel. - + """Open a message tunnel. + This tunnel will forward things you say in this channel to the ticket opener's direct messages. @@ -354,8 +349,7 @@ class Reports(commands.Cog): ) big_topic = _( - "{who} opened a 2-way communication " - "about ticket number {ticketnum}. Anything you say or upload here " + " Anything you say or upload here " "(8MB file size limitation on uploads) " "will be forwarded to them until the communication is closed.\n" "You can close a communication at any point by reacting with " @@ -364,8 +358,12 @@ class Reports(commands.Cog): "\N{WHITE HEAVY CHECK MARK}.\n" "Tunnels are not persistent across bot restarts." ) - topic = big_topic.format( - ticketnum=ticket_number, who=_("A moderator in `{guild.name}` has").format(guild=guild) + topic = ( + _( + "A moderator in the server `{guild.name}` has opened a 2-way communication about " + "ticket number {ticket_number}." + ).format(guild=guild, ticket_number=ticket_number) + + big_topic ) try: m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True) @@ -373,4 +371,9 @@ class Reports(commands.Cog): await ctx.send(_("That user has DMs disabled.")) else: self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m} - await ctx.send(big_topic.format(who=_("You have"), ticketnum=ticket_number)) + await ctx.send( + _( + "You have opened a 2-way communication about ticket number {ticket_number}." + ).format(ticket_number=ticket_number) + + big_topic + ) diff --git a/redbot/cogs/streams/streams.py b/redbot/cogs/streams/streams.py index 381ee3cb6..901ca6803 100644 --- a/redbot/cogs/streams/streams.py +++ b/redbot/cogs/streams/streams.py @@ -1,3 +1,5 @@ +import contextlib + import discord from redbot.core import Config, checks, commands from redbot.core.utils.chat_formatting import pagify @@ -22,7 +24,7 @@ from .errors import ( StreamsError, InvalidTwitchCredentials, ) -from . import streamtypes as StreamClasses +from . import streamtypes as _streamtypes from collections import defaultdict import asyncio import re @@ -76,14 +78,14 @@ class Streams(commands.Cog): @commands.command() async def twitch(self, ctx: commands.Context, channel_name: str): - """Checks if a Twitch channel is live""" + """Check if a Twitch channel is live.""" token = await self.db.tokens.get_raw(TwitchStream.__name__, default=None) stream = TwitchStream(name=channel_name, token=token) await self.check_online(ctx, stream) @commands.command() async def youtube(self, ctx: commands.Context, channel_id_or_name: str): - """Checks if a Youtube channel is live""" + """Check if a YouTube channel is live.""" apikey = await self.db.tokens.get_raw(YoutubeStream.__name__, default=None) is_name = self.check_name_or_id(channel_id_or_name) if is_name: @@ -94,23 +96,24 @@ class Streams(commands.Cog): @commands.command() async def hitbox(self, ctx: commands.Context, channel_name: str): - """Checks if a Hitbox channel is live""" + """Check if a Hitbox channel is live.""" stream = HitboxStream(name=channel_name) await self.check_online(ctx, stream) @commands.command() async def mixer(self, ctx: commands.Context, channel_name: str): - """Checks if a Mixer channel is live""" + """Check if a Mixer channel is live.""" stream = MixerStream(name=channel_name) await self.check_online(ctx, stream) @commands.command() async def picarto(self, ctx: commands.Context, channel_name: str): - """Checks if a Picarto channel is live""" + """Check if a Picarto channel is live.""" stream = PicartoStream(name=channel_name) await self.check_online(ctx, stream) - async def check_online(self, ctx: commands.Context, stream): + @staticmethod + async def check_online(ctx: commands.Context, stream): try: embed = await stream.is_online() except OfflineStream: @@ -119,15 +122,17 @@ class Streams(commands.Cog): await ctx.send(_("That channel doesn't seem to exist.")) except InvalidTwitchCredentials: await ctx.send( - _("The twitch token is either invalid or has not been set. See `{}`.").format( - "{}streamset twitchtoken".format(ctx.prefix) - ) + _( + "The Twitch token is either invalid or has not been set. See " + "`{prefix}streamset twitchtoken`." + ).format(prefix=ctx.prefix) ) except InvalidYoutubeCredentials: await ctx.send( - _("Your Youtube API key is either invalid or has not been set. See {}.").format( - "`{}streamset youtubekey`".format(ctx.prefix) - ) + _( + "The YouTube API key is either invalid or has not been set. See " + "`{prefix}streamset youtubekey`." + ).format(prefix=ctx.prefix) ) except APIError: await ctx.send( @@ -140,11 +145,12 @@ class Streams(commands.Cog): @commands.guild_only() @checks.mod() async def streamalert(self, ctx: commands.Context): + """Manage automated stream alerts.""" pass @streamalert.group(name="twitch", invoke_without_command=True) async def _twitch(self, ctx: commands.Context, channel_name: str = None): - """Twitch stream alerts""" + """Manage Twitch stream notifications.""" if channel_name is not None: await ctx.invoke(self.twitch_alert_channel, channel_name) else: @@ -152,7 +158,7 @@ class Streams(commands.Cog): @_twitch.command(name="channel") async def twitch_alert_channel(self, ctx: commands.Context, channel_name: str): - """Sets a Twitch alert notification in the channel""" + """Toggle alerts in this channel for a Twitch stream.""" if re.fullmatch(r"<#\d+>", channel_name): await ctx.send("Please supply the name of a *Twitch* channel, not a Discord channel.") return @@ -160,33 +166,39 @@ class Streams(commands.Cog): @_twitch.command(name="community") async def twitch_alert_community(self, ctx: commands.Context, community: str): - """Sets an alert notification in the channel for the specified twitch community.""" + """Toggle alerts in this channel for a Twitch community.""" await self.community_alert(ctx, TwitchCommunity, community.lower()) @streamalert.command(name="youtube") async def youtube_alert(self, ctx: commands.Context, channel_name_or_id: str): - """Sets a Youtube alert notification in the channel""" + """Toggle alerts in this channel for a YouTube stream.""" await self.stream_alert(ctx, YoutubeStream, channel_name_or_id) @streamalert.command(name="hitbox") async def hitbox_alert(self, ctx: commands.Context, channel_name: str): - """Sets a Hitbox alert notification in the channel""" + """Toggle alerts in this channel for a Hitbox stream.""" await self.stream_alert(ctx, HitboxStream, channel_name) @streamalert.command(name="mixer") async def mixer_alert(self, ctx: commands.Context, channel_name: str): - """Sets a Mixer alert notification in the channel""" + """Toggle alerts in this channel for a Mixer stream.""" await self.stream_alert(ctx, MixerStream, channel_name) @streamalert.command(name="picarto") async def picarto_alert(self, ctx: commands.Context, channel_name: str): - """Sets a Picarto alert notification in the channel""" + """Toggle alerts in this channel for a Picarto stream.""" await self.stream_alert(ctx, PicartoStream, channel_name) - @streamalert.command(name="stop") + @streamalert.command(name="stop", usage="[disable_all=No]") 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""" + """Disable all stream alerts in this channel or server. + + `[p]streamalert stop` will disable this channel's stream + alerts. + + Do `[p]streamalert stop yes` to disable all stream alerts in + this server. + """ streams = self.streams.copy() local_channel_ids = [c.id for c in ctx.guild.channels] to_remove = [] @@ -208,9 +220,10 @@ class Streams(commands.Cog): self.streams = streams await self.save_streams() - msg = _("All the alerts in the {} have been disabled.").format( - "server" if _all else "channel" - ) + if _all: + msg = _("All the stream alerts in this server have been disabled.") + else: + msg = _("All the stream alerts in this channel have been disabled.") await ctx.send(msg) @@ -250,16 +263,18 @@ class Streams(commands.Cog): exists = await self.check_exists(stream) except InvalidTwitchCredentials: await ctx.send( - _("Your twitch token is either invalid or has not been set. See {}.").format( - "`{}streamset twitchtoken`".format(ctx.prefix) - ) + _( + "The Twitch token is either invalid or has not been set. See " + "`{prefix}streamset twitchtoken`." + ).format(prefix=ctx.prefix) ) return except InvalidYoutubeCredentials: await ctx.send( _( - "Your Youtube API key is either invalid or has not been set. See {}." - ).format("`{}streamset youtubekey`".format(ctx.prefix)) + "The YouTube API key is either invalid or has not been set. See " + "`{prefix}streamset youtubekey`." + ).format(prefix=ctx.prefix) ) return except APIError: @@ -283,9 +298,10 @@ class Streams(commands.Cog): await community.get_community_streams() except InvalidTwitchCredentials: await ctx.send( - _("The twitch token is either invalid or has not been set. See {}.").format( - "`{}streamset twitchtoken`".format(ctx.prefix) - ) + _( + "The Twitch token is either invalid or has not been set. See " + "`{prefix}streamset twitchtoken`." + ).format(prefix=ctx.prefix) ) return except CommunityNotFound: @@ -309,14 +325,15 @@ class Streams(commands.Cog): @streamset.command() @checks.is_owner() async def twitchtoken(self, ctx: commands.Context, token: str): - """Set the Client ID for twitch. + """Set the Client ID for Twitch. + To do this, follow these steps: - 1. Go to this page: https://dev.twitch.tv/dashboard/apps. - 2. Click *Register Your Application* - 3. Enter a name, set the OAuth Redirect URI to `http://localhost`, and - select an Application Category of your choosing. - 4. Click *Register*, and on the following page, copy the Client ID. - 5. Paste the Client ID into this command. Done! + 1. Go to this page: https://dev.twitch.tv/dashboard/apps. + 2. Click *Register Your Application* + 3. Enter a name, set the OAuth Redirect URI to `http://localhost`, and + select an Application Category of your choosing. + 4. Click *Register*, and on the following page, copy the Client ID. + 5. Paste the Client ID into this command. Done! """ await self.db.tokens.set_raw("TwitchStream", value=token) await self.db.tokens.set_raw("TwitchCommunity", value=token) @@ -325,64 +342,59 @@ class Streams(commands.Cog): @streamset.command() @checks.is_owner() async def youtubekey(self, ctx: commands.Context, key: str): - """Sets the API key for Youtube. + """Set the API key for YouTube. + To get one, do the following: 1. Create a project (see https://support.google.com/googleapi/answer/6251787 for details) - 2. Enable the Youtube Data API v3 (see https://support.google.com/googleapi/answer/6158841 for instructions) - 3. Set up your API key (see https://support.google.com/googleapi/answer/6158862 for instructions) + 2. Enable the YouTube Data API v3 (see https://support.google.com/googleapi/answer/6158841 + for instructions) + 3. Set up your API key (see https://support.google.com/googleapi/answer/6158862 for + instructions) 4. Copy your API key and paste it into this command. Done! """ await self.db.tokens.set_raw("YoutubeStream", value=key) - await ctx.send(_("Youtube key set.")) + await ctx.send(_("YouTube key set.")) @streamset.group() @commands.guild_only() async def mention(self, ctx: commands.Context): - """Sets mentions for alerts.""" + """Manage mention settings for stream alerts.""" pass @mention.command(aliases=["everyone"]) @commands.guild_only() async def all(self, ctx: commands.Context): - """Toggles everyone mention""" + """Toggle the `@\u200beveryone` mention.""" guild = ctx.guild current_setting = await self.db.guild(guild).mention_everyone() if current_setting: await self.db.guild(guild).mention_everyone.set(False) - await ctx.send( - _("{} will no longer be mentioned when a stream or community is live").format( - "@\u200beveryone" - ) - ) + await ctx.send(_("`@\u200beveryone` will no longer be mentioned for stream alerts.")) else: await self.db.guild(guild).mention_everyone.set(True) await ctx.send( - _("When a stream or community " "is live, {} will be mentioned.").format( - "@\u200beveryone" - ) + _("When a stream or community is live, `@\u200beveryone` will be mentioned.") ) @mention.command(aliases=["here"]) @commands.guild_only() async def online(self, ctx: commands.Context): - """Toggles here mention""" + """Toggle the `@\u200bhere` mention.""" guild = ctx.guild current_setting = await self.db.guild(guild).mention_here() if current_setting: await self.db.guild(guild).mention_here.set(False) - await ctx.send(_("{} will no longer be mentioned for an alert.").format("@\u200bhere")) + await ctx.send(_("`@\u200bhere` will no longer be mentioned for stream alerts.")) else: await self.db.guild(guild).mention_here.set(True) await ctx.send( - _("When a stream or community " "is live, {} will be mentioned.").format( - "@\u200bhere" - ) + _("When a stream or community is live, `@\u200bhere` will be mentioned.") ) @mention.command() @commands.guild_only() async def role(self, ctx: commands.Context, *, role: discord.Role): - """Toggles role mention""" + """Toggle a role mention.""" current_setting = await self.db.role(role).mention() if not role.mentionable: await ctx.send("That role is not mentionable!") @@ -390,27 +402,27 @@ class Streams(commands.Cog): if current_setting: await self.db.role(role).mention.set(False) await ctx.send( - _("{} will no longer be mentioned for an alert.").format( - "@\u200b{}".format(role.name) + _("`@\u200b{role.name}` will no longer be mentioned for stream alerts.").format( + role=role ) ) else: await self.db.role(role).mention.set(True) await ctx.send( - _("When a stream or community " "is live, {} will be mentioned." "").format( - "@\u200b{}".format(role.name) - ) + _( + "When a stream or community is live, `@\u200b{role.name}` will be mentioned." + ).format(role=role) ) @streamset.command() @commands.guild_only() async def autodelete(self, ctx: commands.Context, on_off: bool): - """Toggles automatic deletion of notifications for streams that go offline""" + """Toggle alert deletion for when streams go offline.""" await self.db.guild(ctx.guild).autodelete.set(on_off) if on_off: - await ctx.send("The notifications will be deleted once streams go offline.") + await ctx.send(_("The notifications will be deleted once streams go offline.")) else: - await ctx.send("Notifications will never be deleted.") + await ctx.send(_("Notifications will no longer be deleted.")) async def add_or_remove(self, ctx: commands.Context, stream): if ctx.channel.id not in stream.channels: @@ -418,18 +430,18 @@ class Streams(commands.Cog): if stream not in self.streams: self.streams.append(stream) await ctx.send( - _("I'll now send a notification in this channel when {} is live.").format( - stream.name - ) + _( + "I'll now send a notification in this channel when {stream.name} is live." + ).format(stream=stream) ) else: stream.channels.remove(ctx.channel.id) if not stream.channels: self.streams.remove(stream) await ctx.send( - _("I won't send notifications about {} in this channel anymore.").format( - stream.name - ) + _( + "I won't send notifications about {stream.name} in this channel anymore." + ).format(stream=stream) ) await self.save_streams() @@ -442,9 +454,8 @@ class Streams(commands.Cog): await ctx.send( _( "I'll send a notification in this channel when a " - "channel is live in the {} community." - "" - ).format(community.name) + "channel is live in the {community.name} community." + ).format(community=community) ) else: community.channels.remove(ctx.channel.id) @@ -453,9 +464,8 @@ class Streams(commands.Cog): await ctx.send( _( "I won't send notifications about channels streaming " - "in the {} community in this channel anymore." - "" - ).format(community.name) + "in the {community.name} community in this channel anymore." + ).format(community=community) ) await self.save_communities() @@ -481,7 +491,8 @@ class Streams(commands.Cog): if community.type == _class.__name__ and community.name.lower() == name.lower(): return community - async def check_exists(self, stream): + @staticmethod + async def check_exists(stream): try: await stream.is_online() except OfflineStream: @@ -506,40 +517,36 @@ class Streams(commands.Cog): async def check_streams(self): for stream in self.streams: - try: - embed = await stream.is_online() - except OfflineStream: - if not stream._messages_cache: - continue - for message in stream._messages_cache: - try: - autodelete = await self.db.guild(message.guild).autodelete() - if autodelete: - await message.delete() - except: - pass - stream._messages_cache.clear() - await self.save_streams() - except: - pass - else: - if stream._messages_cache: - continue - for channel_id in stream.channels: - channel = self.bot.get_channel(channel_id) - mention_str = await self._get_mention_str(channel.guild) + with contextlib.suppress(Exception): + try: + embed = await stream.is_online() + except OfflineStream: + if not stream._messages_cache: + continue + for message in stream._messages_cache: + with contextlib.suppress(Exception): + autodelete = await self.db.guild(message.guild).autodelete() + if autodelete: + await message.delete() + stream._messages_cache.clear() + await self.save_streams() + else: + if stream._messages_cache: + continue + for channel_id in stream.channels: + channel = self.bot.get_channel(channel_id) + mention_str = await self._get_mention_str(channel.guild) - if mention_str: - content = "{}, {} is live!".format(mention_str, stream.name) - else: - content = "{} is live!".format(stream.name) + if mention_str: + content = _("{mention}, {stream.name} is live!").format( + mention=mention_str, stream=stream + ) + else: + content = _("{stream.name} is live!").format(stream=stream.name) - try: m = await channel.send(content, embed=embed) stream._messages_cache.append(m) await self.save_streams() - except: - pass async def _get_mention_str(self, guild: discord.Guild): settings = self.db.guild(guild) @@ -555,45 +562,46 @@ class Streams(commands.Cog): async def check_communities(self): for community in self.communities: - try: - stream_list = await community.get_community_streams() - except CommunityNotFound: - print(_("The Community {} was not found!").format(community.name)) - continue - except OfflineCommunity: - if not community._messages_cache: + with contextlib.suppress(Exception): + try: + stream_list = await community.get_community_streams() + except CommunityNotFound: + print( + _("The Community {community.name} was not found!").format( + community=community + ) + ) continue - for message in community._messages_cache: - try: - autodelete = await self.db.guild(message.guild).autodelete() - if autodelete: - await message.delete() - except: - pass - community._messages_cache.clear() - await self.save_communities() - except: - pass - else: - for channel in community.channels: - chn = self.bot.get_channel(channel) - streams = await self.filter_streams(stream_list, chn) - emb = await community.make_embed(streams) - chn_msg = [m for m in community._messages_cache if m.channel == chn] - if not chn_msg: - mentions = await self._get_mention_str(chn.guild) - if mentions: - msg = await chn.send(mentions, embed=emb) + except OfflineCommunity: + if not community._messages_cache: + continue + for message in community._messages_cache: + with contextlib.suppress(Exception): + autodelete = await self.db.guild(message.guild).autodelete() + if autodelete: + await message.delete() + community._messages_cache.clear() + await self.save_communities() + else: + for channel in community.channels: + chn = self.bot.get_channel(channel) + streams = await self.filter_streams(stream_list, chn) + emb = await community.make_embed(streams) + chn_msg = [m for m in community._messages_cache if m.channel == chn] + if not chn_msg: + mentions = await self._get_mention_str(chn.guild) + if mentions: + msg = await chn.send(mentions, embed=emb) + else: + msg = await chn.send(embed=emb) + community._messages_cache.append(msg) + await self.save_communities() else: - msg = await chn.send(embed=emb) - community._messages_cache.append(msg) - await self.save_communities() - else: - chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0] - community._messages_cache.remove(chn_msg) - await chn_msg.edit(embed=emb) - community._messages_cache.append(chn_msg) - await self.save_communities() + chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0] + community._messages_cache.remove(chn_msg) + await chn_msg.edit(embed=emb) + community._messages_cache.append(chn_msg) + await self.save_communities() async def filter_streams(self, streams: list, channel: discord.TextChannel) -> list: filtered = [] @@ -611,7 +619,7 @@ class Streams(commands.Cog): streams = [] for raw_stream in await self.db.streams(): - _class = getattr(StreamClasses, raw_stream["type"], None) + _class = getattr(_streamtypes, raw_stream["type"], None) if not _class: continue raw_msg_cache = raw_stream["messages"] @@ -631,7 +639,7 @@ class Streams(commands.Cog): communities = [] for raw_community in await self.db.communities(): - _class = getattr(StreamClasses, raw_community["type"], None) + _class = getattr(_streamtypes, raw_community["type"], None) if not _class: continue raw_msg_cache = raw_community["messages"] diff --git a/redbot/cogs/trivia/session.py b/redbot/cogs/trivia/session.py index 0dd34d959..cc6e2e142 100644 --- a/redbot/cogs/trivia/session.py +++ b/redbot/cogs/trivia/session.py @@ -4,20 +4,30 @@ import time import random from collections import Counter import discord -from redbot.core.bank import deposit_credits -from redbot.core.utils.chat_formatting import box +from redbot.core import bank +from redbot.core.i18n import Translator +from redbot.core.utils.chat_formatting import box, bold, humanize_list from redbot.core.utils.common_filters import normalize_smartquotes from .log import LOG __all__ = ["TriviaSession"] -_REVEAL_MESSAGES = ("I know this one! {}!", "Easy: {}.", "Oh really? It's {} of course.") -_FAIL_MESSAGES = ( - "To the next one I guess...", - "Moving on...", - "I'm sure you'll know the answer of the next one.", - "\N{PENSIVE FACE} Next one.", +T_ = Translator("TriviaSession", __file__) + + +_ = lambda s: s +_REVEAL_MESSAGES = ( + _("I know this one! {answer}!"), + _("Easy: {answer}."), + _("Oh really? It's {answer} of course."), ) +_FAIL_MESSAGES = ( + _("To the next one I guess..."), + _("Moving on..."), + _("I'm sure you'll know the answer of the next one."), + _("\N{PENSIVE FACE} Next one."), +) +_ = T_ class TriviaSession: @@ -104,7 +114,7 @@ class TriviaSession: async with self.ctx.typing(): await asyncio.sleep(3) self.count += 1 - msg = "**Question number {}!**\n\n{}".format(self.count, question) + msg = bold(_("**Question number {num}!").format(num=self.count)) + "\n\n" + question await self.ctx.send(msg) continue_ = await self.wait_for_answer(answers, delay, timeout) if continue_ is False: @@ -113,7 +123,7 @@ class TriviaSession: await self.end_game() break else: - await self.ctx.send("There are no more questions!") + await self.ctx.send(_("There are no more questions!")) await self.end_game() async def _send_startup_msg(self): @@ -121,20 +131,13 @@ class TriviaSession: for idx, tup in enumerate(self.settings["lists"].items()): name, author = tup if author: - title = "{} (by {})".format(name, author) + title = _("{trivia_list} (by {author})").format(trivia_list=name, author=author) else: title = name list_names.append(title) - num_lists = len(list_names) - if num_lists > 2: - # at least 3 lists, join all but last with comma - msg = ", ".join(list_names[: num_lists - 1]) - # join onto last with "and" - msg = " and ".join((msg, list_names[num_lists - 1])) - else: - # either 1 or 2 lists, join together with "and" - msg = " and ".join(list_names) - await self.ctx.send("Starting Trivia: " + msg) + await self.ctx.send( + _("Starting Trivia: {list_names}").format(list_names=humanize_list(list_names)) + ) def _iter_questions(self): """Iterate over questions and answers for this session. @@ -179,20 +182,20 @@ class TriviaSession: ) except asyncio.TimeoutError: if time.time() - self._last_response >= timeout: - await self.ctx.send("Guys...? Well, I guess I'll stop then.") + await self.ctx.send(_("Guys...? Well, I guess I'll stop then.")) self.stop() return False if self.settings["reveal_answer"]: - reply = random.choice(_REVEAL_MESSAGES).format(answers[0]) + reply = T_(random.choice(_REVEAL_MESSAGES)).format(answer=answers[0]) else: - reply = random.choice(_FAIL_MESSAGES) + reply = T_(random.choice(_FAIL_MESSAGES)) if self.settings["bot_plays"]: - reply += " **+1** for me!" + reply += _(" **+1** for me!") self.scores[self.ctx.guild.me] += 1 await self.ctx.send(reply) else: self.scores[message.author] += 1 - reply = "You got it {}! **+1** to you!".format(message.author.display_name) + reply = _("You got it {user}! **+1** to you!").format(user=message.author.display_name) await self.ctx.send(reply) return True @@ -282,10 +285,16 @@ class TriviaSession: amount = int(multiplier * score) if amount > 0: LOG.debug("Paying trivia winner: %d credits --> %s", amount, str(winner)) - await deposit_credits(winner, int(multiplier * score)) + await bank.deposit_credits(winner, int(multiplier * score)) await self.ctx.send( - "Congratulations, {0}, you have received {1} credits" - " for coming first.".format(winner.display_name, amount) + _( + "Congratulations, {user}, you have received {num} {currency}" + " for coming first." + ).format( + user=winner.display_name, + num=amount, + currency=await bank.get_currency_name(self.ctx.guild), + ) ) @@ -313,9 +322,9 @@ def _parse_answers(answers): for answer in answers: if isinstance(answer, bool): if answer is True: - ret.extend(["True", "Yes"]) + ret.extend(["True", "Yes", _("Yes")]) else: - ret.extend(["False", "No"]) + ret.extend(["False", "No", _("No")]) else: ret.append(str(answer)) # Uniquify list diff --git a/redbot/cogs/trivia/trivia.py b/redbot/cogs/trivia/trivia.py index 55ce6337a..b04aa1141 100644 --- a/redbot/cogs/trivia/trivia.py +++ b/redbot/cogs/trivia/trivia.py @@ -7,7 +7,8 @@ import discord from redbot.core import commands from redbot.core import Config, checks from redbot.core.data_manager import cog_data_path -from redbot.core.utils.chat_formatting import box, pagify +from redbot.core.i18n import Translator, cog_i18n +from redbot.core.utils.chat_formatting import box, pagify, bold from redbot.cogs.bank import check_global_setting_admin from .log import LOG from .session import TriviaSession @@ -16,6 +17,8 @@ __all__ = ["Trivia", "UNIQUE_ID", "get_core_lists"] UNIQUE_ID = 0xB3C0E453 +_ = Translator("Trivia", __file__) + class InvalidListError(Exception): """A Trivia list file is in invalid format.""" @@ -23,6 +26,7 @@ class InvalidListError(Exception): pass +@cog_i18n(_) class Trivia(commands.Cog): """Play trivia with friends!""" @@ -47,20 +51,21 @@ class Trivia(commands.Cog): @commands.guild_only() @checks.mod_or_permissions(administrator=True) async def triviaset(self, ctx: commands.Context): - """Manage trivia settings.""" + """Manage Trivia settings.""" if ctx.invoked_subcommand is None: settings = self.conf.guild(ctx.guild) settings_dict = await settings.all() msg = box( - "**Current settings**\n" - "Bot gains points: {bot_plays}\n" - "Answer time limit: {delay} seconds\n" - "Lack of response timeout: {timeout} seconds\n" - "Points to win: {max_score}\n" - "Reveal answer on timeout: {reveal_answer}\n" - "Payout multiplier: {payout_multiplier}\n" - "Allow lists to override settings: {allow_override}" - "".format(**settings_dict), + _( + "**Current settings**\n" + "Bot gains points: {bot_plays}\n" + "Answer time limit: {delay} seconds\n" + "Lack of response timeout: {timeout} seconds\n" + "Points to win: {max_score}\n" + "Reveal answer on timeout: {reveal_answer}\n" + "Payout multiplier: {payout_multiplier}\n" + "Allow lists to override settings: {allow_override}" + ).format(**settings_dict), lang="py", ) await ctx.send(msg) @@ -69,33 +74,34 @@ class Trivia(commands.Cog): async def triviaset_max_score(self, ctx: commands.Context, score: int): """Set the total points required to win.""" if score < 0: - await ctx.send("Score must be greater than 0.") + await ctx.send(_("Score must be greater than 0.")) return settings = self.conf.guild(ctx.guild) await settings.max_score.set(score) - await ctx.send("Done. Points required to win set to {}.".format(score)) + await ctx.send(_("Done. Points required to win set to {num}.").format(num=score)) @triviaset.command(name="timelimit") async def triviaset_timelimit(self, ctx: commands.Context, seconds: float): """Set the maximum seconds permitted to answer a question.""" if seconds < 4.0: - await ctx.send("Must be at least 4 seconds.") + await ctx.send(_("Must be at least 4 seconds.")) return settings = self.conf.guild(ctx.guild) await settings.delay.set(seconds) - await ctx.send("Done. Maximum seconds to answer set to {}.".format(seconds)) + await ctx.send(_("Done. Maximum seconds to answer set to {num}.").format(num=seconds)) @triviaset.command(name="stopafter") async def triviaset_stopafter(self, ctx: commands.Context, seconds: float): """Set how long until trivia stops due to no response.""" settings = self.conf.guild(ctx.guild) if seconds < await settings.delay(): - await ctx.send("Must be larger than the answer time limit.") + await ctx.send(_("Must be larger than the answer time limit.")) return await settings.timeout.set(seconds) await ctx.send( - "Done. Trivia sessions will now time out after {}" - " seconds of no responses.".format(seconds) + _( + "Done. Trivia sessions will now time out after {num} seconds of no responses." + ).format(num=seconds) ) @triviaset.command(name="override") @@ -103,46 +109,46 @@ class Trivia(commands.Cog): """Allow/disallow trivia lists to override settings.""" settings = self.conf.guild(ctx.guild) await settings.allow_override.set(enabled) - enabled = "now" if enabled else "no longer" - await ctx.send( - "Done. Trivia lists can {} override the trivia settings" - " for this server.".format(enabled) - ) + if enabled: + await ctx.send( + _( + "Done. Trivia lists can now override the trivia settings for this server." + ).format(now=enabled) + ) + else: + await ctx.send( + _( + "Done. Trivia lists can no longer override the trivia settings for this " + "server." + ).format(now=enabled) + ) - @triviaset.command(name="botplays") - async def trivaset_bot_plays(self, ctx: commands.Context, true_or_false: bool): + @triviaset.command(name="botplays", usage="") + async def trivaset_bot_plays(self, ctx: commands.Context, enabled: bool): """Set whether or not the bot gains points. If enabled, the bot will gain a point if no one guesses correctly. """ settings = self.conf.guild(ctx.guild) - await settings.bot_plays.set(true_or_false) - await ctx.send( - "Done. " - + ( - "I'll gain a point if users don't answer in time." - if true_or_false - else "Alright, I won't embarass you at trivia anymore." - ) - ) + await settings.bot_plays.set(enabled) + if enabled: + await ctx.send(_("Done. I'll now gain a point if users don't answer in time.")) + else: + await ctx.send(_("Alright, I won't embarass you at trivia anymore.")) - @triviaset.command(name="revealanswer") - async def trivaset_reveal_answer(self, ctx: commands.Context, true_or_false: bool): + @triviaset.command(name="revealanswer", usage="") + async def trivaset_reveal_answer(self, ctx: commands.Context, enabled: bool): """Set whether or not the answer is revealed. If enabled, the bot will reveal the answer if no one guesses correctly in time. """ settings = self.conf.guild(ctx.guild) - await settings.reveal_answer.set(true_or_false) - await ctx.send( - "Done. " - + ( - "I'll reveal the answer if no one knows it." - if true_or_false - else "I won't reveal the answer to the questions anymore." - ) - ) + await settings.reveal_answer.set(enabled) + if enabled: + await ctx.send(_("Done. I'll reveal the answer if no one knows it.")) + else: + await ctx.send(_("Alright, I won't reveal the answer to the questions anymore.")) @triviaset.command(name="payout") @check_global_setting_admin() @@ -158,13 +164,13 @@ class Trivia(commands.Cog): """ settings = self.conf.guild(ctx.guild) if multiplier < 0: - await ctx.send("Multiplier must be at least 0.") + await ctx.send(_("Multiplier must be at least 0.")) return await settings.payout_multiplier.set(multiplier) - if not multiplier: - await ctx.send("Done. I will no longer reward the winner with a payout.") - return - await ctx.send("Done. Payout multiplier set to {}.".format(multiplier)) + if multiplier: + await ctx.send(_("Done. Payout multiplier set to {num}.").format(num=multiplier)) + else: + await ctx.send(_("Done. I will no longer reward the winner with a payout.")) @commands.group(invoke_without_command=True) @commands.guild_only() @@ -180,7 +186,7 @@ class Trivia(commands.Cog): categories = [c.lower() for c in categories] session = self._get_trivia_session(ctx.channel) if session is not None: - await ctx.send("There is already an ongoing trivia session in this channel.") + await ctx.send(_("There is already an ongoing trivia session in this channel.")) return trivia_dict = {} authors = [] @@ -191,15 +197,17 @@ class Trivia(commands.Cog): dict_ = self.get_trivia_list(category) except FileNotFoundError: await ctx.send( - "Invalid category `{0}`. See `{1}trivia list`" - " for a list of trivia categories." - "".format(category, ctx.prefix) + _( + "Invalid category `{name}`. See `{prefix}trivia list` for a list of " + "trivia categories." + ).format(name=category, prefix=ctx.prefix) ) except InvalidListError: await ctx.send( - "There was an error parsing the trivia list for" - " the `{}` category. It may be formatted" - " incorrectly.".format(category) + _( + "There was an error parsing the trivia list for the `{name}` category. It " + "may be formatted incorrectly." + ).format(name=category) ) else: trivia_dict.update(dict_) @@ -208,7 +216,7 @@ class Trivia(commands.Cog): return if not trivia_dict: await ctx.send( - "The trivia list was parsed successfully, however it appears to be empty!" + _("The trivia list was parsed successfully, however it appears to be empty!") ) return settings = await self.conf.guild(ctx.guild).all() @@ -225,7 +233,7 @@ class Trivia(commands.Cog): """Stop an ongoing trivia session.""" session = self._get_trivia_session(ctx.channel) if session is None: - await ctx.send("There is no ongoing trivia session in this channel.") + await ctx.send(_("There is no ongoing trivia session in this channel.")) return author = ctx.author auth_checks = ( @@ -238,20 +246,28 @@ class Trivia(commands.Cog): if any(auth_checks): await session.end_game() session.force_stop() - await ctx.send("Trivia stopped.") + await ctx.send(_("Trivia stopped.")) else: - await ctx.send("You are not allowed to do that.") + await ctx.send(_("You are not allowed to do that.")) @trivia.command(name="list") async def trivia_list(self, ctx: commands.Context): """List available trivia categories.""" lists = set(p.stem for p in self._all_lists()) - - msg = box("**Available trivia lists**\n\n{}".format(", ".join(sorted(lists)))) - if len(msg) > 1000: - await ctx.author.send(msg) - return - await ctx.send(msg) + if await ctx.embed_requested(): + await ctx.send( + embed=discord.Embed( + title=_("Available trivia lists"), + colour=await ctx.embed_colour(), + description=", ".join(sorted(lists)), + ) + ) + else: + msg = box(bold(_("Available trivia lists")) + "\n\n" + ", ".join(sorted(lists))) + if len(msg) > 1000: + await ctx.author.send(msg) + else: + await ctx.send(msg) @trivia.group(name="leaderboard", aliases=["lboard"], autohelp=False) async def trivia_leaderboard(self, ctx: commands.Context): @@ -273,19 +289,21 @@ class Trivia(commands.Cog): ): """Leaderboard for this server. - can be any of the following fields: - - wins : total wins - - avg : average score - - total : total correct answers + `` can be any of the following fields: + - `wins` : total wins + - `avg` : average score + - `total` : total correct answers + - `games` : total games played - is the number of ranks to show on the leaderboard. + `` is the number of ranks to show on the leaderboard. """ key = self._get_sort_key(sort_by) if key is None: await ctx.send( - "Unknown field `{}`, see `{}help trivia " - "leaderboard server` for valid fields to sort by." - "".format(sort_by, ctx.prefix) + _( + "Unknown field `{field_name}`, see `{prefix}help trivia leaderboard server` " + "for valid fields to sort by." + ).format(field_name=sort_by, prefix=ctx.prefix) ) return guild = ctx.guild @@ -300,20 +318,21 @@ class Trivia(commands.Cog): ): """Global trivia leaderboard. - can be any of the following fields: - - wins : total wins - - avg : average score - - total : total correct answers from all sessions - - games : total games played + `` can be any of the following fields: + - `wins` : total wins + - `avg` : average score + - `total` : total correct answers from all sessions + - `games` : total games played - is the number of ranks to show on the leaderboard. + `` is the number of ranks to show on the leaderboard. """ key = self._get_sort_key(sort_by) if key is None: await ctx.send( - "Unknown field `{}`, see `{}help trivia " - "leaderboard global` for valid fields to sort by." - "".format(sort_by, ctx.prefix) + _( + "Unknown field `{field_name}`, see `{prefix}help trivia leaderboard server` " + "for valid fields to sort by." + ).format(field_name=sort_by, prefix=ctx.prefix) ) return data = await self.conf.all_members() @@ -365,7 +384,7 @@ class Trivia(commands.Cog): """ if not data: - await ctx.send("There are no scores on record!") + await ctx.send(_("There are no scores on record!")) return leaderboard = self._get_leaderboard(data, key, top) ret = [] @@ -386,7 +405,7 @@ class Trivia(commands.Cog): try: priority.remove(key) except ValueError: - raise ValueError("{} is not a valid key.".format(key)) + raise ValueError(f"{key} is not a valid key.") # Put key last in reverse priority priority.append(key) items = data.items() @@ -395,16 +414,15 @@ class Trivia(commands.Cog): max_name_len = max(map(lambda m: len(str(m)), data.keys())) # Headers headers = ( - "Rank", - "Member{}".format(" " * (max_name_len - 6)), - "Wins", - "Games Played", - "Total Score", - "Average Score", + _("Rank"), + _("Member") + " " * (max_name_len - 6), + _("Wins"), + _("Games Played"), + _("Total Score"), + _("Average Score"), ) - lines = [" | ".join(headers)] + lines = [" | ".join(headers), " | ".join(("-" * len(h) for h in headers))] # Header underlines - lines.append(" | ".join(("-" * len(h) for h in headers))) for rank, tup in enumerate(items, 1): member, m_data = tup # Align fields to header width diff --git a/redbot/cogs/warnings/warnings.py b/redbot/cogs/warnings/warnings.py index 77937b645..1b545591b 100644 --- a/redbot/cogs/warnings/warnings.py +++ b/redbot/cogs/warnings/warnings.py @@ -22,7 +22,7 @@ _ = Translator("Warnings", __file__) @cog_i18n(_) class Warnings(commands.Cog): - """A warning system for Red""" + """Warn misbehaving users and take automated actions.""" default_guild = {"actions": [], "reasons": {}, "allow_custom_reasons": False} @@ -48,31 +48,42 @@ class Warnings(commands.Cog): @commands.guild_only() @checks.guildowner_or_permissions(administrator=True) async def warningset(self, ctx: commands.Context): - """Warning settings""" + """Manage settings for Warnings.""" pass @warningset.command() @commands.guild_only() async def allowcustomreasons(self, ctx: commands.Context, allowed: bool): - """Enable or Disable custom reasons for a warning""" + """Enable or disable custom reasons for a warning.""" guild = ctx.guild await self.config.guild(guild).allow_custom_reasons.set(allowed) - await ctx.send( - _("Custom reasons have been {}.").format(_("enabled") if allowed else _("disabled")) - ) + if allowed: + await ctx.send(_("Custom reasons have been enabled.")) + else: + await ctx.send(_("Custom reasons have been disabled.")) @commands.group() @commands.guild_only() @checks.guildowner_or_permissions(administrator=True) async def warnaction(self, ctx: commands.Context): - """Action management""" + """Manage automated actions for Warnings. + + Actions are essentially command macros. Any command can be run + when the action is initially triggered, and/or when the action + is lifted. + + Actions must be given a name and a points threshold. When a + user is warned enough so that their points go over this + threshold, the action will be executed. + """ pass @warnaction.command(name="add") @commands.guild_only() 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 + """Create an automated action. + + Duplicate action names are not allowed. """ guild = ctx.guild @@ -103,7 +114,7 @@ class Warnings(commands.Cog): @warnaction.command(name="del") @commands.guild_only() async def action_del(self, ctx: commands.Context, action_name: str): - """Delete the point count action with the specified name""" + """Delete the action with the specified name.""" guild = ctx.guild guild_settings = self.config.guild(guild) async with guild_settings.actions() as registered_actions: @@ -116,23 +127,29 @@ class Warnings(commands.Cog): registered_actions.remove(to_remove) await ctx.tick() else: - await ctx.send(_("No action named {} exists!").format(action_name)) + await ctx.send(_("No action named {name} exists!").format(name=action_name)) @commands.group() @commands.guild_only() @checks.guildowner_or_permissions(administrator=True) async def warnreason(self, ctx: commands.Context): - """Add reasons for warnings""" + """Manage warning reasons. + + Reasons must be given a name, description and points value. The + name of the reason must be given when a user is warned. + """ pass - @warnreason.command(name="add") + @warnreason.command(name="create", aliases=["add"]) @commands.guild_only() - async def reason_add(self, ctx: commands.Context, name: str, points: int, *, description: str): - """Add a reason to be available for warnings""" + async def reason_create( + self, ctx: commands.Context, name: str, points: int, *, description: str + ): + """Create a warning reason.""" guild = ctx.guild if name.lower() == "custom": - await ctx.send("That cannot be used as a reason name!") + await ctx.send(_("*Custom* cannot be used as a reason name!")) return to_add = {"points": points, "description": description} completed = {name.lower(): to_add} @@ -142,12 +159,12 @@ class Warnings(commands.Cog): async with guild_settings.reasons() as registered_reasons: registered_reasons.update(completed) - await ctx.send(_("That reason has been registered.")) + await ctx.send(_("The new reason has been registered.")) - @warnreason.command(name="del") + @warnreason.command(name="del", aliases=["remove"]) @commands.guild_only() async def reason_del(self, ctx: commands.Context, reason_name: str): - """Delete the reason with the specified name""" + """Delete a warning reason.""" guild = ctx.guild guild_settings = self.config.guild(guild) async with guild_settings.reasons() as registered_reasons: @@ -160,7 +177,7 @@ class Warnings(commands.Cog): @commands.guild_only() @checks.admin_or_permissions(ban_members=True) async def reasonlist(self, ctx: commands.Context): - """List all configured reasons for warnings""" + """List all configured reasons for Warnings.""" guild = ctx.guild guild_settings = self.config.guild(guild) msg_list = [] @@ -174,9 +191,9 @@ class Warnings(commands.Cog): msg_list.append(em) else: msg_list.append( - "Name: {}\nPoints: {}\nDescription: {}".format( - r, v["points"], v["description"] - ) + _( + "Name: {reason_name}\nPoints: {points}\nDescription: {description}" + ).format(reason_name=r, **v) ) if msg_list: await menu(ctx, msg_list, DEFAULT_CONTROLS) @@ -187,7 +204,7 @@ class Warnings(commands.Cog): @commands.guild_only() @checks.admin_or_permissions(ban_members=True) async def actionlist(self, ctx: commands.Context): - """List the actions to be taken at specific point values""" + """List all configured automated actions for Warnings.""" guild = ctx.guild guild_settings = self.config.guild(guild) msg_list = [] @@ -201,10 +218,10 @@ class Warnings(commands.Cog): msg_list.append(em) else: msg_list.append( - "Name: {}\nPoints: {}\nExceed command: {}\n" - "Drop command: {}".format( - r["action_name"], r["points"], r["exceed_command"], r["drop_command"] - ) + _( + "Name: {action_name}\nPoints: {points}\n" + "Exceed command: {exceed_command}\nDrop command: {drop_command}" + ).format(**r) ) if msg_list: await menu(ctx, msg_list, DEFAULT_CONTROLS) @@ -215,8 +232,10 @@ class Warnings(commands.Cog): @commands.guild_only() @checks.admin_or_permissions(ban_members=True) 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 + """Warn the user for the specified reason. + + `` must be a registered reason name, or *custom* if + custom reasons are enabled. """ if user == ctx.author: await ctx.send(_("You cannot warn yourself.")) @@ -226,9 +245,9 @@ class Warnings(commands.Cog): if not custom_allowed: await ctx.send( _( - "Custom reasons are not allowed! Please see {} for " + "Custom reasons are not allowed! Please see `{prefix}reasonlist` for " "a complete list of valid reasons." - ).format("`{}reasonlist`".format(ctx.prefix)) + ).format(prefix=ctx.prefix) ) return reason_type = await self.custom_warning_reason(ctx) @@ -272,9 +291,7 @@ class Warnings(commands.Cog): await warning_points_add_check(self.config, ctx, user, current_point_count) try: em = discord.Embed( - title=_("Warning from {mod_name}#{mod_discrim}").format( - mod_name=ctx.author.display_name, mod_discrim=ctx.author.discriminator - ), + title=_("Warning from {user}").format(user=ctx.author), description=reason_type["description"], ) em.add_field(name=_("Points"), value=str(reason_type["points"])) @@ -286,19 +303,17 @@ class Warnings(commands.Cog): ) except discord.HTTPException: pass - await ctx.send( - _("User {user_name}#{user_discrim} has been warned.").format( - user_name=user.display_name, user_discrim=user.discriminator - ) - ) + await ctx.send(_("User {user} has been warned.").format(user=user)) @commands.command() @commands.guild_only() 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 + """List the warnings for the specified user. + + Emit `` to see your own warnings. + Note that showing warnings for users other than yourself requires - appropriate permissions + appropriate permissions. """ if userid is None: user = ctx.author @@ -326,18 +341,24 @@ class Warnings(commands.Cog): ) if mod is None: mod = await self.bot.get_user_info(user_warnings[key]["mod"]) - msg += "{} point warning {} issued by {} for {}\n".format( - user_warnings[key]["points"], key, mod, user_warnings[key]["description"] + msg += _( + "{num_points} point warning {reason_name} issued by {user} for " + "{description}\n" + ).format( + num_points=user_warnings[key]["points"], + reason_name=key, + user=mod, + description=user_warnings[key]["description"], ) await ctx.send_interactive( - pagify(msg, shorten_by=58), box_lang="Warnings for {}".format(user) + pagify(msg, shorten_by=58), box_lang=_("Warnings for {user}").format(user=user) ) @commands.command() @commands.guild_only() @checks.admin_or_permissions(ban_members=True) async def unwarn(self, ctx: commands.Context, user_id: int, warn_id: str): - """Removes the specified warning from the user specified""" + """Remove a warning from a user.""" if user_id == ctx.author.id: await ctx.send(_("You cannot remove warnings from yourself.")) return @@ -351,7 +372,7 @@ class Warnings(commands.Cog): await warning_points_remove_check(self.config, ctx, member, current_point_count) async with member_settings.warnings() as user_warnings: if warn_id not in user_warnings.keys(): - await ctx.send("That warning doesn't exist!") + await ctx.send(_("That warning doesn't exist!")) return else: current_point_count -= user_warnings[warn_id]["points"] diff --git a/redbot/core/utils/tunnel.py b/redbot/core/utils/tunnel.py index 32684a721..5b757543d 100644 --- a/redbot/core/utils/tunnel.py +++ b/redbot/core/utils/tunnel.py @@ -4,7 +4,7 @@ from redbot.core.utils.chat_formatting import pagify import io import sys import weakref -from typing import List +from typing import List, Optional from .common_filters import filter_mass_mentions _instances = weakref.WeakValueDictionary({}) @@ -86,7 +86,11 @@ class Tunnel(metaclass=TunnelMeta): @staticmethod async def message_forwarder( - *, destination: discord.abc.Messageable, content: str = None, embed=None, files=[] + *, + destination: discord.abc.Messageable, + content: str = None, + embed=None, + files: Optional[List[discord.File]] = None ) -> List[discord.Message]: """ This does the actual sending, use this instead of a full tunnel @@ -95,19 +99,19 @@ class Tunnel(metaclass=TunnelMeta): Parameters ---------- - destination: `discord.abc.Messageable` + destination: discord.abc.Messageable Where to send - content: `str` + content: str The message content - embed: `discord.Embed` + embed: discord.Embed The embed to send - files: `list` of `discord.File` + files: Optional[List[discord.File]] A list of files to send. Returns ------- - list of `discord.Message` - The `discord.Message`\ (s) sent as a result + List[discord.Message] + The messages sent as a result. Raises ------ @@ -117,7 +121,6 @@ class Tunnel(metaclass=TunnelMeta): see `discord.abc.Messageable.send` """ rets = [] - files = files if files else None if content: for page in pagify(content): rets.append(await destination.send(page, files=files, embed=embed))