[i18n] Pass over modlog, permissions, reports, streams, trivia, warnings

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
This commit is contained in:
Toby Harradine 2018-10-05 17:37:26 +10:00
parent fa692ccc0b
commit 443f2ca556
10 changed files with 498 additions and 435 deletions

View File

@ -386,7 +386,7 @@ class Mod(commands.Cog):
async def ban( async def ban(
self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None 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 `<days>` worth of messages. Deletes `<days>` worth of messages.
@ -469,7 +469,7 @@ class Mod(commands.Cog):
@commands.bot_has_permissions(ban_members=True) @commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True) @checks.admin_or_permissions(ban_members=True)
async def hackban(self, ctx: commands.Context, user_id: int, *, reason: str = None): 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 A user ID needs to be provided in order to ban
using this command. using this command.
@ -531,7 +531,7 @@ class Mod(commands.Cog):
async def tempban( async def tempban(
self, ctx: commands.Context, user: discord.Member, days: int = 1, *, reason: str = None 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 guild = ctx.guild
author = ctx.author author = ctx.author
days_delta = timedelta(days=int(days)) days_delta = timedelta(days=int(days))
@ -672,7 +672,7 @@ class Mod(commands.Cog):
@commands.bot_has_permissions(ban_members=True) @commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True) @checks.admin_or_permissions(ban_members=True)
async def unban(self, ctx: commands.Context, user_id: int, *, reason: str = None): 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: 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 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( async def unmute_channel(
self, ctx: commands.Context, user: discord.Member, *, reason: str = None 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 channel = ctx.channel
author = ctx.author author = ctx.author
guild = ctx.guild guild = ctx.guild
@ -1137,7 +1137,7 @@ class Mod(commands.Cog):
async def unmute_guild( async def unmute_guild(
self, ctx: commands.Context, user: discord.Member, *, reason: str = None 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 guild = ctx.guild
author = ctx.author author = ctx.author
@ -1236,7 +1236,7 @@ class Mod(commands.Cog):
@ignore.command(name="server", aliases=["guild"]) @ignore.command(name="server", aliases=["guild"])
@checks.admin_or_permissions(manage_guild=True) @checks.admin_or_permissions(manage_guild=True)
async def ignore_guild(self, ctx: commands.Context): async def ignore_guild(self, ctx: commands.Context):
"""Ignore commands in the current server.""" """Ignore commands in this server."""
guild = ctx.guild guild = ctx.guild
if not await self.settings.guild(guild).ignored(): if not await self.settings.guild(guild).ignored():
await self.settings.guild(guild).ignored.set(True) await self.settings.guild(guild).ignored.set(True)
@ -1270,7 +1270,7 @@ class Mod(commands.Cog):
@unignore.command(name="server", aliases=["guild"]) @unignore.command(name="server", aliases=["guild"])
@checks.admin_or_permissions(manage_guild=True) @checks.admin_or_permissions(manage_guild=True)
async def unignore_guild(self, ctx: commands.Context): 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 guild = ctx.message.guild
if await self.settings.guild(guild).ignored(): if await self.settings.guild(guild).ignored():
await self.settings.guild(guild).ignored.set(False) await self.settings.guild(guild).ignored.set(False)

View File

@ -1,3 +1,5 @@
from typing import Optional
import discord import discord
from redbot.core import checks, modlog, commands from redbot.core import checks, modlog, commands
@ -10,7 +12,7 @@ _ = Translator("ModLog", __file__)
@cog_i18n(_) @cog_i18n(_)
class ModLog(commands.Cog): class ModLog(commands.Cog):
"""Log for mod actions""" """Manage log channels for moderation actions."""
def __init__(self, bot: Red): def __init__(self, bot: Red):
super().__init__() super().__init__()
@ -19,23 +21,28 @@ class ModLog(commands.Cog):
@commands.group() @commands.group()
@checks.guildowner_or_permissions(administrator=True) @checks.guildowner_or_permissions(administrator=True)
async def modlogset(self, ctx: commands.Context): async def modlogset(self, ctx: commands.Context):
"""Settings for the mod log""" """Manage modlog settings."""
pass pass
@modlogset.command() @modlogset.command()
@commands.guild_only() @commands.guild_only()
async def modlog(self, ctx: commands.Context, channel: discord.TextChannel = None): 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 `<channel>` to disable the modlog.
"""
guild = ctx.guild guild = ctx.guild
if channel: if channel:
if channel.permissions_for(guild.me).send_messages: if channel.permissions_for(guild.me).send_messages:
await modlog.set_modlog_channel(guild, channel) 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: else:
await ctx.send( 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: else:
try: try:
@ -49,39 +56,36 @@ class ModLog(commands.Cog):
@modlogset.command(name="cases") @modlogset.command(name="cases")
@commands.guild_only() @commands.guild_only()
async def set_cases(self, ctx: commands.Context, 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""" """Enable or disable case creation for a mod action."""
guild = ctx.guild guild = ctx.guild
if action is None: # No args given if action is None: # No args given
casetypes = await modlog.get_all_casetypes(guild) casetypes = await modlog.get_all_casetypes(guild)
await ctx.send_help() await ctx.send_help()
title = _("Current settings:") lines = []
msg = ""
for ct in casetypes: for ct in casetypes:
enabled = await ct.is_enabled() enabled = "enabled" if await ct.is_enabled() else "disabled"
value = "enabled" if enabled else "disabled" lines.append(f"{ct.name} : {enabled}")
msg += "%s : %s\n" % (ct.name, value)
msg = title + "\n" + box(msg) await ctx.send(_("Current settings:\n") + box("\n".join(lines)))
await ctx.send(msg)
return return
casetype = await modlog.get_casetype(action, guild) casetype = await modlog.get_casetype(action, guild)
if not casetype: if not casetype:
await ctx.send(_("That action is not registered")) await ctx.send(_("That action is not registered"))
else: else:
enabled = await casetype.is_enabled() enabled = await casetype.is_enabled()
await casetype.set_enabled(True if not enabled else False) await casetype.set_enabled(not enabled)
await ctx.send(
msg = _("Case creation for {} actions is now {}.").format( _("Case creation for {action_name} actions is now {enabled}.").format(
action, "enabled" if not enabled else "disabled" action_name=action, enabled="enabled" if not enabled else "disabled"
)
) )
await ctx.send(msg)
@modlogset.command() @modlogset.command()
@commands.guild_only() @commands.guild_only()
async def resetcases(self, ctx: commands.Context): async def resetcases(self, ctx: commands.Context):
"""Resets modlog's cases""" """Reset all modlog cases in this server."""
guild = ctx.guild guild = ctx.guild
await modlog.reset_cases(guild) await modlog.reset_cases(guild)
await ctx.send(_("Cases have been reset.")) await ctx.send(_("Cases have been reset."))
@ -89,7 +93,7 @@ class ModLog(commands.Cog):
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
async def case(self, ctx: commands.Context, number: int): async def case(self, ctx: commands.Context, number: int):
"""Shows the specified case""" """Show the specified case."""
try: try:
case = await modlog.get_case(number, ctx.guild, self.bot) case = await modlog.get_case(number, ctx.guild, self.bot)
except RuntimeError: except RuntimeError:
@ -101,24 +105,21 @@ class ModLog(commands.Cog):
else: else:
await ctx.send(await case.message_content(embed=False)) await ctx.send(await case.message_content(embed=False))
@commands.command(usage="[case] <reason>") @commands.command()
@commands.guild_only() @commands.guild_only()
async def reason(self, ctx: commands.Context, *, reason: str): async def reason(self, ctx: commands.Context, case: Optional[int], *, reason: str):
"""Lets you specify a reason for mod-log's cases """Specify a reason for a modlog case.
Please note that you can only edit cases you are Please note that you can only edit cases you are
the owner of unless you are a mod/admin or the server owner. the owner of unless you are a mod, admin or server owner.
If no number is specified, the latest case will be used.""" If no case number is specified, the latest case will be used.
"""
author = ctx.author author = ctx.author
guild = ctx.guild guild = ctx.guild
potential_case = reason.split()[0] if case is None:
if potential_case.isdigit(): # get the latest case
case = int(potential_case) case = int(await modlog.get_next_case_number(guild)) - 1
reason = reason.replace(potential_case, "")
else:
case = str(int(await modlog.get_next_case_number(guild)) - 1)
# latest case
try: try:
case_before = await modlog.get_case(case, guild, self.bot) case_before = await modlog.get_case(case, guild, self.bot)
except RuntimeError: except RuntimeError:

View File

@ -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 import commands
from redbot.core.i18n import Translator
_ = Translator("PermissionsConverters", __file__)
class CogOrCommand(NamedTuple): class CogOrCommand(NamedTuple):
@ -18,39 +22,34 @@ class CogOrCommand(NamedTuple):
return cls(type="COMMAND", name=cmd.qualified_name, obj=cmd) return cls(type="COMMAND", name=cmd.qualified_name, obj=cmd)
raise commands.BadArgument( 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 raise commands.BadArgument(
@classmethod _('"{arg}" is not a valid rule. Valid rules are "allow" or "deny"').format(arg=arg)
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)
)
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 raise commands.BadArgument(
@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(
'"{arg}" is not a valid rule. Valid rules are "allow" or "deny", or "clear" to ' '"{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)
)

View File

@ -2,7 +2,7 @@ import asyncio
import io import io
import textwrap import textwrap
from copy import copy 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 discord
import yaml import yaml
@ -287,9 +287,11 @@ class Permissions(commands.Cog):
`<who_or_what>` is the user, channel, role or server the rule `<who_or_what>` is the user, channel, role or server the rule
is for. is for.
""" """
# noinspection PyTypeChecker
await self._add_rule( 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.")) await ctx.send(_("Rule added."))
@ -312,9 +314,8 @@ class Permissions(commands.Cog):
`<who_or_what>` is the user, channel or role the rule is for. `<who_or_what>` is the user, channel or role the rule is for.
""" """
# noinspection PyTypeChecker
await self._add_rule( await self._add_rule(
rule=allow_or_deny, rule=cast(bool, allow_or_deny),
cog_or_cmd=cog_or_command, cog_or_cmd=cog_or_command,
model_id=who_or_what.id, model_id=who_or_what.id,
guild_id=ctx.guild.id, guild_id=ctx.guild.id,
@ -381,9 +382,10 @@ class Permissions(commands.Cog):
`<cog_or_command>` is the cog or command to set the default `<cog_or_command>` is the cog or command to set the default
rule for. This is case sensitive. rule for. This is case sensitive.
""" """
# noinspection PyTypeChecker
await self._set_default_rule( 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.")) await ctx.send(_("Default set."))
@ -403,9 +405,8 @@ class Permissions(commands.Cog):
`<cog_or_command>` is the cog or command to set the default `<cog_or_command>` is the cog or command to set the default
rule for. This is case sensitive. rule for. This is case sensitive.
""" """
# noinspection PyTypeChecker
await self._set_default_rule( 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.")) await ctx.send(_("Default set."))

View File

@ -1,6 +1,6 @@
import logging import logging
import asyncio import asyncio
from typing import Union from typing import Union, List
from datetime import timedelta from datetime import timedelta
from copy import copy from copy import copy
import contextlib import contextlib
@ -60,23 +60,20 @@ class Reports(commands.Cog):
@commands.guild_only() @commands.guild_only()
@commands.group(name="reportset") @commands.group(name="reportset")
async def reportset(self, ctx: commands.Context): async def reportset(self, ctx: commands.Context):
""" """Manage Reports."""
Settings for the report system.
"""
pass pass
@checks.admin_or_permissions(manage_guild=True) @checks.admin_or_permissions(manage_guild=True)
@reportset.command(name="output") @reportset.command(name="output")
async def setoutput(self, ctx: commands.Context, channel: discord.TextChannel): async def reportset_output(self, ctx: commands.Context, channel: discord.TextChannel):
"""Set the channel where reports will show up""" """Set the channel where reports will be sent."""
await self.config.guild(ctx.guild).output_channel.set(channel.id) await self.config.guild(ctx.guild).output_channel.set(channel.id)
await ctx.send(_("The report channel has been set.")) await ctx.send(_("The report channel has been set."))
@checks.admin_or_permissions(manage_guild=True) @checks.admin_or_permissions(manage_guild=True)
@reportset.command(name="toggle", aliases=["toggleactive"]) @reportset.command(name="toggle", aliases=["toggleactive"])
async def report_toggle(self, ctx: commands.Context): async def reportset_toggle(self, ctx: commands.Context):
"""Enables or Disables reporting for the server""" """Enable or Disable reporting for this server."""
active = await self.config.guild(ctx.guild).active() active = await self.config.guild(ctx.guild).active()
active = not active active = not active
await self.config.guild(ctx.guild).active.set(active) await self.config.guild(ctx.guild).active.set(active)
@ -168,7 +165,7 @@ class Reports(commands.Cog):
if channel is None: if channel is None:
return 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() ticket_number = await self.config.guild(guild).next_ticket()
await self.config.guild(guild).next_ticket.set(ticket_number + 1) 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) @commands.group(name="report", invoke_without_command=True)
async def report(self, ctx: commands.Context, *, _report: str = ""): async def report(self, ctx: commands.Context, *, _report: str = ""):
""" """Send a report.
Send a report.
Use without arguments for interactive reporting, or do Use without arguments for interactive reporting, or do
[p]report <text> to use it non-interactively. `[p]report <text>` to use it non-interactively.
""" """
author = ctx.author author = ctx.author
guild = ctx.guild guild = ctx.guild
@ -323,8 +319,7 @@ class Reports(commands.Cog):
@checks.mod_or_permissions(manage_members=True) @checks.mod_or_permissions(manage_members=True)
@report.command(name="interact") @report.command(name="interact")
async def response(self, ctx, ticket_number: int): 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 This tunnel will forward things you say in this channel
to the ticket opener's direct messages. to the ticket opener's direct messages.
@ -354,8 +349,7 @@ class Reports(commands.Cog):
) )
big_topic = _( big_topic = _(
"{who} opened a 2-way communication " " Anything you say or upload here "
"about ticket number {ticketnum}. Anything you say or upload here "
"(8MB file size limitation on uploads) " "(8MB file size limitation on uploads) "
"will be forwarded to them until the communication is closed.\n" "will be forwarded to them until the communication is closed.\n"
"You can close a communication at any point by reacting with " "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" "\N{WHITE HEAVY CHECK MARK}.\n"
"Tunnels are not persistent across bot restarts." "Tunnels are not persistent across bot restarts."
) )
topic = big_topic.format( topic = (
ticketnum=ticket_number, who=_("A moderator in `{guild.name}` has").format(guild=guild) _(
"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: try:
m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True) 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.")) await ctx.send(_("That user has DMs disabled."))
else: else:
self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m} 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
)

View File

@ -1,3 +1,5 @@
import contextlib
import discord import discord
from redbot.core import Config, checks, commands from redbot.core import Config, checks, commands
from redbot.core.utils.chat_formatting import pagify from redbot.core.utils.chat_formatting import pagify
@ -22,7 +24,7 @@ from .errors import (
StreamsError, StreamsError,
InvalidTwitchCredentials, InvalidTwitchCredentials,
) )
from . import streamtypes as StreamClasses from . import streamtypes as _streamtypes
from collections import defaultdict from collections import defaultdict
import asyncio import asyncio
import re import re
@ -76,14 +78,14 @@ class Streams(commands.Cog):
@commands.command() @commands.command()
async def twitch(self, ctx: commands.Context, channel_name: str): 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) token = await self.db.tokens.get_raw(TwitchStream.__name__, default=None)
stream = TwitchStream(name=channel_name, token=token) stream = TwitchStream(name=channel_name, token=token)
await self.check_online(ctx, stream) await self.check_online(ctx, stream)
@commands.command() @commands.command()
async def youtube(self, ctx: commands.Context, channel_id_or_name: str): 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) apikey = await self.db.tokens.get_raw(YoutubeStream.__name__, default=None)
is_name = self.check_name_or_id(channel_id_or_name) is_name = self.check_name_or_id(channel_id_or_name)
if is_name: if is_name:
@ -94,23 +96,24 @@ class Streams(commands.Cog):
@commands.command() @commands.command()
async def hitbox(self, ctx: commands.Context, channel_name: str): 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) stream = HitboxStream(name=channel_name)
await self.check_online(ctx, stream) await self.check_online(ctx, stream)
@commands.command() @commands.command()
async def mixer(self, ctx: commands.Context, channel_name: str): 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) stream = MixerStream(name=channel_name)
await self.check_online(ctx, stream) await self.check_online(ctx, stream)
@commands.command() @commands.command()
async def picarto(self, ctx: commands.Context, channel_name: str): 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) stream = PicartoStream(name=channel_name)
await self.check_online(ctx, stream) 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: try:
embed = await stream.is_online() embed = await stream.is_online()
except OfflineStream: except OfflineStream:
@ -119,15 +122,17 @@ class Streams(commands.Cog):
await ctx.send(_("That channel doesn't seem to exist.")) await ctx.send(_("That channel doesn't seem to exist."))
except InvalidTwitchCredentials: except InvalidTwitchCredentials:
await ctx.send( 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: except InvalidYoutubeCredentials:
await ctx.send( 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: except APIError:
await ctx.send( await ctx.send(
@ -140,11 +145,12 @@ class Streams(commands.Cog):
@commands.guild_only() @commands.guild_only()
@checks.mod() @checks.mod()
async def streamalert(self, ctx: commands.Context): async def streamalert(self, ctx: commands.Context):
"""Manage automated stream alerts."""
pass pass
@streamalert.group(name="twitch", invoke_without_command=True) @streamalert.group(name="twitch", invoke_without_command=True)
async def _twitch(self, ctx: commands.Context, channel_name: str = None): async def _twitch(self, ctx: commands.Context, channel_name: str = None):
"""Twitch stream alerts""" """Manage Twitch stream notifications."""
if channel_name is not None: if channel_name is not None:
await ctx.invoke(self.twitch_alert_channel, channel_name) await ctx.invoke(self.twitch_alert_channel, channel_name)
else: else:
@ -152,7 +158,7 @@ class Streams(commands.Cog):
@_twitch.command(name="channel") @_twitch.command(name="channel")
async def twitch_alert_channel(self, ctx: commands.Context, channel_name: str): 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): if re.fullmatch(r"<#\d+>", channel_name):
await ctx.send("Please supply the name of a *Twitch* channel, not a Discord channel.") await ctx.send("Please supply the name of a *Twitch* channel, not a Discord channel.")
return return
@ -160,33 +166,39 @@ class Streams(commands.Cog):
@_twitch.command(name="community") @_twitch.command(name="community")
async def twitch_alert_community(self, ctx: commands.Context, community: str): 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()) await self.community_alert(ctx, TwitchCommunity, community.lower())
@streamalert.command(name="youtube") @streamalert.command(name="youtube")
async def youtube_alert(self, ctx: commands.Context, channel_name_or_id: str): 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) await self.stream_alert(ctx, YoutubeStream, channel_name_or_id)
@streamalert.command(name="hitbox") @streamalert.command(name="hitbox")
async def hitbox_alert(self, ctx: commands.Context, channel_name: str): 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) await self.stream_alert(ctx, HitboxStream, channel_name)
@streamalert.command(name="mixer") @streamalert.command(name="mixer")
async def mixer_alert(self, ctx: commands.Context, channel_name: str): 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) await self.stream_alert(ctx, MixerStream, channel_name)
@streamalert.command(name="picarto") @streamalert.command(name="picarto")
async def picarto_alert(self, ctx: commands.Context, channel_name: str): 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) 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): async def streamalert_stop(self, ctx: commands.Context, _all: bool = False):
"""Stops all stream notifications in the channel """Disable all stream alerts in this channel or server.
Adding 'yes' will disable all notifications in the 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() streams = self.streams.copy()
local_channel_ids = [c.id for c in ctx.guild.channels] local_channel_ids = [c.id for c in ctx.guild.channels]
to_remove = [] to_remove = []
@ -208,9 +220,10 @@ class Streams(commands.Cog):
self.streams = streams self.streams = streams
await self.save_streams() await self.save_streams()
msg = _("All the alerts in the {} have been disabled.").format( if _all:
"server" if _all else "channel" 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) await ctx.send(msg)
@ -250,16 +263,18 @@ class Streams(commands.Cog):
exists = await self.check_exists(stream) exists = await self.check_exists(stream)
except InvalidTwitchCredentials: except InvalidTwitchCredentials:
await ctx.send( 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 return
except InvalidYoutubeCredentials: except InvalidYoutubeCredentials:
await ctx.send( await ctx.send(
_( _(
"Your Youtube API key is either invalid or has not been set. See {}." "The YouTube API key is either invalid or has not been set. See "
).format("`{}streamset youtubekey`".format(ctx.prefix)) "`{prefix}streamset youtubekey`."
).format(prefix=ctx.prefix)
) )
return return
except APIError: except APIError:
@ -283,9 +298,10 @@ class Streams(commands.Cog):
await community.get_community_streams() await community.get_community_streams()
except InvalidTwitchCredentials: except InvalidTwitchCredentials:
await ctx.send( 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 return
except CommunityNotFound: except CommunityNotFound:
@ -309,14 +325,15 @@ class Streams(commands.Cog):
@streamset.command() @streamset.command()
@checks.is_owner() @checks.is_owner()
async def twitchtoken(self, ctx: commands.Context, token: str): 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: To do this, follow these steps:
1. Go to this page: https://dev.twitch.tv/dashboard/apps. 1. Go to this page: https://dev.twitch.tv/dashboard/apps.
2. Click *Register Your Application* 2. Click *Register Your Application*
3. Enter a name, set the OAuth Redirect URI to `http://localhost`, and 3. Enter a name, set the OAuth Redirect URI to `http://localhost`, and
select an Application Category of your choosing. select an Application Category of your choosing.
4. Click *Register*, and on the following page, copy the Client ID. 4. Click *Register*, and on the following page, copy the Client ID.
5. Paste the Client ID into this command. Done! 5. Paste the Client ID into this command. Done!
""" """
await self.db.tokens.set_raw("TwitchStream", value=token) await self.db.tokens.set_raw("TwitchStream", value=token)
await self.db.tokens.set_raw("TwitchCommunity", value=token) await self.db.tokens.set_raw("TwitchCommunity", value=token)
@ -325,64 +342,59 @@ class Streams(commands.Cog):
@streamset.command() @streamset.command()
@checks.is_owner() @checks.is_owner()
async def youtubekey(self, ctx: commands.Context, key: str): 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: To get one, do the following:
1. Create a project (see https://support.google.com/googleapi/answer/6251787 for details) 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) 2. Enable the YouTube Data API v3 (see https://support.google.com/googleapi/answer/6158841
3. Set up your API key (see https://support.google.com/googleapi/answer/6158862 for instructions) 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! 4. Copy your API key and paste it into this command. Done!
""" """
await self.db.tokens.set_raw("YoutubeStream", value=key) await self.db.tokens.set_raw("YoutubeStream", value=key)
await ctx.send(_("Youtube key set.")) await ctx.send(_("YouTube key set."))
@streamset.group() @streamset.group()
@commands.guild_only() @commands.guild_only()
async def mention(self, ctx: commands.Context): async def mention(self, ctx: commands.Context):
"""Sets mentions for alerts.""" """Manage mention settings for stream alerts."""
pass pass
@mention.command(aliases=["everyone"]) @mention.command(aliases=["everyone"])
@commands.guild_only() @commands.guild_only()
async def all(self, ctx: commands.Context): async def all(self, ctx: commands.Context):
"""Toggles everyone mention""" """Toggle the `@\u200beveryone` mention."""
guild = ctx.guild guild = ctx.guild
current_setting = await self.db.guild(guild).mention_everyone() current_setting = await self.db.guild(guild).mention_everyone()
if current_setting: if current_setting:
await self.db.guild(guild).mention_everyone.set(False) await self.db.guild(guild).mention_everyone.set(False)
await ctx.send( await ctx.send(_("`@\u200beveryone` will no longer be mentioned for stream alerts."))
_("{} will no longer be mentioned when a stream or community is live").format(
"@\u200beveryone"
)
)
else: else:
await self.db.guild(guild).mention_everyone.set(True) await self.db.guild(guild).mention_everyone.set(True)
await ctx.send( await ctx.send(
_("When a stream or community " "is live, {} will be mentioned.").format( _("When a stream or community is live, `@\u200beveryone` will be mentioned.")
"@\u200beveryone"
)
) )
@mention.command(aliases=["here"]) @mention.command(aliases=["here"])
@commands.guild_only() @commands.guild_only()
async def online(self, ctx: commands.Context): async def online(self, ctx: commands.Context):
"""Toggles here mention""" """Toggle the `@\u200bhere` mention."""
guild = ctx.guild guild = ctx.guild
current_setting = await self.db.guild(guild).mention_here() current_setting = await self.db.guild(guild).mention_here()
if current_setting: if current_setting:
await self.db.guild(guild).mention_here.set(False) 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: else:
await self.db.guild(guild).mention_here.set(True) await self.db.guild(guild).mention_here.set(True)
await ctx.send( await ctx.send(
_("When a stream or community " "is live, {} will be mentioned.").format( _("When a stream or community is live, `@\u200bhere` will be mentioned.")
"@\u200bhere"
)
) )
@mention.command() @mention.command()
@commands.guild_only() @commands.guild_only()
async def role(self, ctx: commands.Context, *, role: discord.Role): 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() current_setting = await self.db.role(role).mention()
if not role.mentionable: if not role.mentionable:
await ctx.send("That role is not mentionable!") await ctx.send("That role is not mentionable!")
@ -390,27 +402,27 @@ class Streams(commands.Cog):
if current_setting: if current_setting:
await self.db.role(role).mention.set(False) await self.db.role(role).mention.set(False)
await ctx.send( await ctx.send(
_("{} will no longer be mentioned for an alert.").format( _("`@\u200b{role.name}` will no longer be mentioned for stream alerts.").format(
"@\u200b{}".format(role.name) role=role
) )
) )
else: else:
await self.db.role(role).mention.set(True) await self.db.role(role).mention.set(True)
await ctx.send( 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() @streamset.command()
@commands.guild_only() @commands.guild_only()
async def autodelete(self, ctx: commands.Context, on_off: bool): 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) await self.db.guild(ctx.guild).autodelete.set(on_off)
if 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: 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): async def add_or_remove(self, ctx: commands.Context, stream):
if ctx.channel.id not in stream.channels: if ctx.channel.id not in stream.channels:
@ -418,18 +430,18 @@ class Streams(commands.Cog):
if stream not in self.streams: if stream not in self.streams:
self.streams.append(stream) self.streams.append(stream)
await ctx.send( 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: else:
stream.channels.remove(ctx.channel.id) stream.channels.remove(ctx.channel.id)
if not stream.channels: if not stream.channels:
self.streams.remove(stream) self.streams.remove(stream)
await ctx.send( 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() await self.save_streams()
@ -442,9 +454,8 @@ class Streams(commands.Cog):
await ctx.send( await ctx.send(
_( _(
"I'll send a notification in this channel when a " "I'll send a notification in this channel when a "
"channel is live in the {} community." "channel is live in the {community.name} community."
"" ).format(community=community)
).format(community.name)
) )
else: else:
community.channels.remove(ctx.channel.id) community.channels.remove(ctx.channel.id)
@ -453,9 +464,8 @@ class Streams(commands.Cog):
await ctx.send( await ctx.send(
_( _(
"I won't send notifications about channels streaming " "I won't send notifications about channels streaming "
"in the {} community in this channel anymore." "in the {community.name} community in this channel anymore."
"" ).format(community=community)
).format(community.name)
) )
await self.save_communities() await self.save_communities()
@ -481,7 +491,8 @@ class Streams(commands.Cog):
if community.type == _class.__name__ and community.name.lower() == name.lower(): if community.type == _class.__name__ and community.name.lower() == name.lower():
return community return community
async def check_exists(self, stream): @staticmethod
async def check_exists(stream):
try: try:
await stream.is_online() await stream.is_online()
except OfflineStream: except OfflineStream:
@ -506,40 +517,36 @@ class Streams(commands.Cog):
async def check_streams(self): async def check_streams(self):
for stream in self.streams: for stream in self.streams:
try: with contextlib.suppress(Exception):
embed = await stream.is_online() try:
except OfflineStream: embed = await stream.is_online()
if not stream._messages_cache: except OfflineStream:
continue if not stream._messages_cache:
for message in stream._messages_cache: continue
try: for message in stream._messages_cache:
autodelete = await self.db.guild(message.guild).autodelete() with contextlib.suppress(Exception):
if autodelete: autodelete = await self.db.guild(message.guild).autodelete()
await message.delete() if autodelete:
except: await message.delete()
pass stream._messages_cache.clear()
stream._messages_cache.clear() await self.save_streams()
await self.save_streams() else:
except: if stream._messages_cache:
pass continue
else: for channel_id in stream.channels:
if stream._messages_cache: channel = self.bot.get_channel(channel_id)
continue mention_str = await self._get_mention_str(channel.guild)
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: if mention_str:
content = "{}, {} is live!".format(mention_str, stream.name) content = _("{mention}, {stream.name} is live!").format(
else: mention=mention_str, stream=stream
content = "{} is live!".format(stream.name) )
else:
content = _("{stream.name} is live!").format(stream=stream.name)
try:
m = await channel.send(content, embed=embed) m = await channel.send(content, embed=embed)
stream._messages_cache.append(m) stream._messages_cache.append(m)
await self.save_streams() await self.save_streams()
except:
pass
async def _get_mention_str(self, guild: discord.Guild): async def _get_mention_str(self, guild: discord.Guild):
settings = self.db.guild(guild) settings = self.db.guild(guild)
@ -555,45 +562,46 @@ class Streams(commands.Cog):
async def check_communities(self): async def check_communities(self):
for community in self.communities: for community in self.communities:
try: with contextlib.suppress(Exception):
stream_list = await community.get_community_streams() try:
except CommunityNotFound: stream_list = await community.get_community_streams()
print(_("The Community {} was not found!").format(community.name)) except CommunityNotFound:
continue print(
except OfflineCommunity: _("The Community {community.name} was not found!").format(
if not community._messages_cache: community=community
)
)
continue continue
for message in community._messages_cache: except OfflineCommunity:
try: if not community._messages_cache:
autodelete = await self.db.guild(message.guild).autodelete() continue
if autodelete: for message in community._messages_cache:
await message.delete() with contextlib.suppress(Exception):
except: autodelete = await self.db.guild(message.guild).autodelete()
pass if autodelete:
community._messages_cache.clear() await message.delete()
await self.save_communities() community._messages_cache.clear()
except: await self.save_communities()
pass else:
else: for channel in community.channels:
for channel in community.channels: chn = self.bot.get_channel(channel)
chn = self.bot.get_channel(channel) streams = await self.filter_streams(stream_list, chn)
streams = await self.filter_streams(stream_list, chn) emb = await community.make_embed(streams)
emb = await community.make_embed(streams) chn_msg = [m for m in community._messages_cache if m.channel == chn]
chn_msg = [m for m in community._messages_cache if m.channel == chn] if not chn_msg:
if not chn_msg: mentions = await self._get_mention_str(chn.guild)
mentions = await self._get_mention_str(chn.guild) if mentions:
if mentions: msg = await chn.send(mentions, embed=emb)
msg = await chn.send(mentions, embed=emb) else:
msg = await chn.send(embed=emb)
community._messages_cache.append(msg)
await self.save_communities()
else: else:
msg = await chn.send(embed=emb) chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0]
community._messages_cache.append(msg) community._messages_cache.remove(chn_msg)
await self.save_communities() await chn_msg.edit(embed=emb)
else: community._messages_cache.append(chn_msg)
chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0] await self.save_communities()
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: async def filter_streams(self, streams: list, channel: discord.TextChannel) -> list:
filtered = [] filtered = []
@ -611,7 +619,7 @@ class Streams(commands.Cog):
streams = [] streams = []
for raw_stream in await self.db.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: if not _class:
continue continue
raw_msg_cache = raw_stream["messages"] raw_msg_cache = raw_stream["messages"]
@ -631,7 +639,7 @@ class Streams(commands.Cog):
communities = [] communities = []
for raw_community in await self.db.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: if not _class:
continue continue
raw_msg_cache = raw_community["messages"] raw_msg_cache = raw_community["messages"]

View File

@ -4,20 +4,30 @@ import time
import random import random
from collections import Counter from collections import Counter
import discord import discord
from redbot.core.bank import deposit_credits from redbot.core import bank
from redbot.core.utils.chat_formatting import box 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 redbot.core.utils.common_filters import normalize_smartquotes
from .log import LOG from .log import LOG
__all__ = ["TriviaSession"] __all__ = ["TriviaSession"]
_REVEAL_MESSAGES = ("I know this one! {}!", "Easy: {}.", "Oh really? It's {} of course.") T_ = Translator("TriviaSession", __file__)
_FAIL_MESSAGES = (
"To the next one I guess...",
"Moving on...", _ = lambda s: s
"I'm sure you'll know the answer of the next one.", _REVEAL_MESSAGES = (
"\N{PENSIVE FACE} Next one.", _("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: class TriviaSession:
@ -104,7 +114,7 @@ class TriviaSession:
async with self.ctx.typing(): async with self.ctx.typing():
await asyncio.sleep(3) await asyncio.sleep(3)
self.count += 1 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) await self.ctx.send(msg)
continue_ = await self.wait_for_answer(answers, delay, timeout) continue_ = await self.wait_for_answer(answers, delay, timeout)
if continue_ is False: if continue_ is False:
@ -113,7 +123,7 @@ class TriviaSession:
await self.end_game() await self.end_game()
break break
else: else:
await self.ctx.send("There are no more questions!") await self.ctx.send(_("There are no more questions!"))
await self.end_game() await self.end_game()
async def _send_startup_msg(self): async def _send_startup_msg(self):
@ -121,20 +131,13 @@ class TriviaSession:
for idx, tup in enumerate(self.settings["lists"].items()): for idx, tup in enumerate(self.settings["lists"].items()):
name, author = tup name, author = tup
if author: if author:
title = "{} (by {})".format(name, author) title = _("{trivia_list} (by {author})").format(trivia_list=name, author=author)
else: else:
title = name title = name
list_names.append(title) list_names.append(title)
num_lists = len(list_names) await self.ctx.send(
if num_lists > 2: _("Starting Trivia: {list_names}").format(list_names=humanize_list(list_names))
# 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)
def _iter_questions(self): def _iter_questions(self):
"""Iterate over questions and answers for this session. """Iterate over questions and answers for this session.
@ -179,20 +182,20 @@ class TriviaSession:
) )
except asyncio.TimeoutError: except asyncio.TimeoutError:
if time.time() - self._last_response >= timeout: 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() self.stop()
return False return False
if self.settings["reveal_answer"]: if self.settings["reveal_answer"]:
reply = random.choice(_REVEAL_MESSAGES).format(answers[0]) reply = T_(random.choice(_REVEAL_MESSAGES)).format(answer=answers[0])
else: else:
reply = random.choice(_FAIL_MESSAGES) reply = T_(random.choice(_FAIL_MESSAGES))
if self.settings["bot_plays"]: if self.settings["bot_plays"]:
reply += " **+1** for me!" reply += _(" **+1** for me!")
self.scores[self.ctx.guild.me] += 1 self.scores[self.ctx.guild.me] += 1
await self.ctx.send(reply) await self.ctx.send(reply)
else: else:
self.scores[message.author] += 1 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) await self.ctx.send(reply)
return True return True
@ -282,10 +285,16 @@ class TriviaSession:
amount = int(multiplier * score) amount = int(multiplier * score)
if amount > 0: if amount > 0:
LOG.debug("Paying trivia winner: %d credits --> %s", amount, str(winner)) 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( 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: for answer in answers:
if isinstance(answer, bool): if isinstance(answer, bool):
if answer is True: if answer is True:
ret.extend(["True", "Yes"]) ret.extend(["True", "Yes", _("Yes")])
else: else:
ret.extend(["False", "No"]) ret.extend(["False", "No", _("No")])
else: else:
ret.append(str(answer)) ret.append(str(answer))
# Uniquify list # Uniquify list

View File

@ -7,7 +7,8 @@ import discord
from redbot.core import commands from redbot.core import commands
from redbot.core import Config, checks from redbot.core import Config, checks
from redbot.core.data_manager import cog_data_path 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 redbot.cogs.bank import check_global_setting_admin
from .log import LOG from .log import LOG
from .session import TriviaSession from .session import TriviaSession
@ -16,6 +17,8 @@ __all__ = ["Trivia", "UNIQUE_ID", "get_core_lists"]
UNIQUE_ID = 0xB3C0E453 UNIQUE_ID = 0xB3C0E453
_ = Translator("Trivia", __file__)
class InvalidListError(Exception): class InvalidListError(Exception):
"""A Trivia list file is in invalid format.""" """A Trivia list file is in invalid format."""
@ -23,6 +26,7 @@ class InvalidListError(Exception):
pass pass
@cog_i18n(_)
class Trivia(commands.Cog): class Trivia(commands.Cog):
"""Play trivia with friends!""" """Play trivia with friends!"""
@ -47,20 +51,21 @@ class Trivia(commands.Cog):
@commands.guild_only() @commands.guild_only()
@checks.mod_or_permissions(administrator=True) @checks.mod_or_permissions(administrator=True)
async def triviaset(self, ctx: commands.Context): async def triviaset(self, ctx: commands.Context):
"""Manage trivia settings.""" """Manage Trivia settings."""
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
settings = self.conf.guild(ctx.guild) settings = self.conf.guild(ctx.guild)
settings_dict = await settings.all() settings_dict = await settings.all()
msg = box( msg = box(
"**Current settings**\n" _(
"Bot gains points: {bot_plays}\n" "**Current settings**\n"
"Answer time limit: {delay} seconds\n" "Bot gains points: {bot_plays}\n"
"Lack of response timeout: {timeout} seconds\n" "Answer time limit: {delay} seconds\n"
"Points to win: {max_score}\n" "Lack of response timeout: {timeout} seconds\n"
"Reveal answer on timeout: {reveal_answer}\n" "Points to win: {max_score}\n"
"Payout multiplier: {payout_multiplier}\n" "Reveal answer on timeout: {reveal_answer}\n"
"Allow lists to override settings: {allow_override}" "Payout multiplier: {payout_multiplier}\n"
"".format(**settings_dict), "Allow lists to override settings: {allow_override}"
).format(**settings_dict),
lang="py", lang="py",
) )
await ctx.send(msg) await ctx.send(msg)
@ -69,33 +74,34 @@ class Trivia(commands.Cog):
async def triviaset_max_score(self, ctx: commands.Context, score: int): async def triviaset_max_score(self, ctx: commands.Context, score: int):
"""Set the total points required to win.""" """Set the total points required to win."""
if score < 0: if score < 0:
await ctx.send("Score must be greater than 0.") await ctx.send(_("Score must be greater than 0."))
return return
settings = self.conf.guild(ctx.guild) settings = self.conf.guild(ctx.guild)
await settings.max_score.set(score) 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") @triviaset.command(name="timelimit")
async def triviaset_timelimit(self, ctx: commands.Context, seconds: float): async def triviaset_timelimit(self, ctx: commands.Context, seconds: float):
"""Set the maximum seconds permitted to answer a question.""" """Set the maximum seconds permitted to answer a question."""
if seconds < 4.0: if seconds < 4.0:
await ctx.send("Must be at least 4 seconds.") await ctx.send(_("Must be at least 4 seconds."))
return return
settings = self.conf.guild(ctx.guild) settings = self.conf.guild(ctx.guild)
await settings.delay.set(seconds) 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") @triviaset.command(name="stopafter")
async def triviaset_stopafter(self, ctx: commands.Context, seconds: float): async def triviaset_stopafter(self, ctx: commands.Context, seconds: float):
"""Set how long until trivia stops due to no response.""" """Set how long until trivia stops due to no response."""
settings = self.conf.guild(ctx.guild) settings = self.conf.guild(ctx.guild)
if seconds < await settings.delay(): 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 return
await settings.timeout.set(seconds) await settings.timeout.set(seconds)
await ctx.send( 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") @triviaset.command(name="override")
@ -103,46 +109,46 @@ class Trivia(commands.Cog):
"""Allow/disallow trivia lists to override settings.""" """Allow/disallow trivia lists to override settings."""
settings = self.conf.guild(ctx.guild) settings = self.conf.guild(ctx.guild)
await settings.allow_override.set(enabled) await settings.allow_override.set(enabled)
enabled = "now" if enabled else "no longer" if enabled:
await ctx.send( await ctx.send(
"Done. Trivia lists can {} override the trivia settings" _(
" for this server.".format(enabled) "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") @triviaset.command(name="botplays", usage="<true_or_false>")
async def trivaset_bot_plays(self, ctx: commands.Context, true_or_false: bool): async def trivaset_bot_plays(self, ctx: commands.Context, enabled: bool):
"""Set whether or not the bot gains points. """Set whether or not the bot gains points.
If enabled, the bot will gain a point if no one guesses correctly. If enabled, the bot will gain a point if no one guesses correctly.
""" """
settings = self.conf.guild(ctx.guild) settings = self.conf.guild(ctx.guild)
await settings.bot_plays.set(true_or_false) await settings.bot_plays.set(enabled)
await ctx.send( if enabled:
"Done. " await ctx.send(_("Done. I'll now gain a point if users don't answer in time."))
+ ( else:
"I'll gain a point if users don't answer in time." await ctx.send(_("Alright, I won't embarass you at trivia anymore."))
if true_or_false
else "Alright, I won't embarass you at trivia anymore."
)
)
@triviaset.command(name="revealanswer") @triviaset.command(name="revealanswer", usage="<true_or_false>")
async def trivaset_reveal_answer(self, ctx: commands.Context, true_or_false: bool): async def trivaset_reveal_answer(self, ctx: commands.Context, enabled: bool):
"""Set whether or not the answer is revealed. """Set whether or not the answer is revealed.
If enabled, the bot will reveal the answer if no one guesses correctly If enabled, the bot will reveal the answer if no one guesses correctly
in time. in time.
""" """
settings = self.conf.guild(ctx.guild) settings = self.conf.guild(ctx.guild)
await settings.reveal_answer.set(true_or_false) await settings.reveal_answer.set(enabled)
await ctx.send( if enabled:
"Done. " await ctx.send(_("Done. I'll reveal the answer if no one knows it."))
+ ( else:
"I'll reveal the answer if no one knows it." await ctx.send(_("Alright, I won't reveal the answer to the questions anymore."))
if true_or_false
else "I won't reveal the answer to the questions anymore."
)
)
@triviaset.command(name="payout") @triviaset.command(name="payout")
@check_global_setting_admin() @check_global_setting_admin()
@ -158,13 +164,13 @@ class Trivia(commands.Cog):
""" """
settings = self.conf.guild(ctx.guild) settings = self.conf.guild(ctx.guild)
if multiplier < 0: if multiplier < 0:
await ctx.send("Multiplier must be at least 0.") await ctx.send(_("Multiplier must be at least 0."))
return return
await settings.payout_multiplier.set(multiplier) await settings.payout_multiplier.set(multiplier)
if not multiplier: if multiplier:
await ctx.send("Done. I will no longer reward the winner with a payout.") await ctx.send(_("Done. Payout multiplier set to {num}.").format(num=multiplier))
return else:
await ctx.send("Done. Payout multiplier set to {}.".format(multiplier)) await ctx.send(_("Done. I will no longer reward the winner with a payout."))
@commands.group(invoke_without_command=True) @commands.group(invoke_without_command=True)
@commands.guild_only() @commands.guild_only()
@ -180,7 +186,7 @@ class Trivia(commands.Cog):
categories = [c.lower() for c in categories] categories = [c.lower() for c in categories]
session = self._get_trivia_session(ctx.channel) session = self._get_trivia_session(ctx.channel)
if session is not None: 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 return
trivia_dict = {} trivia_dict = {}
authors = [] authors = []
@ -191,15 +197,17 @@ class Trivia(commands.Cog):
dict_ = self.get_trivia_list(category) dict_ = self.get_trivia_list(category)
except FileNotFoundError: except FileNotFoundError:
await ctx.send( await ctx.send(
"Invalid category `{0}`. See `{1}trivia list`" _(
" for a list of trivia categories." "Invalid category `{name}`. See `{prefix}trivia list` for a list of "
"".format(category, ctx.prefix) "trivia categories."
).format(name=category, prefix=ctx.prefix)
) )
except InvalidListError: except InvalidListError:
await ctx.send( await ctx.send(
"There was an error parsing the trivia list for" _(
" the `{}` category. It may be formatted" "There was an error parsing the trivia list for the `{name}` category. It "
" incorrectly.".format(category) "may be formatted incorrectly."
).format(name=category)
) )
else: else:
trivia_dict.update(dict_) trivia_dict.update(dict_)
@ -208,7 +216,7 @@ class Trivia(commands.Cog):
return return
if not trivia_dict: if not trivia_dict:
await ctx.send( 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 return
settings = await self.conf.guild(ctx.guild).all() settings = await self.conf.guild(ctx.guild).all()
@ -225,7 +233,7 @@ class Trivia(commands.Cog):
"""Stop an ongoing trivia session.""" """Stop an ongoing trivia session."""
session = self._get_trivia_session(ctx.channel) session = self._get_trivia_session(ctx.channel)
if session is None: 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 return
author = ctx.author author = ctx.author
auth_checks = ( auth_checks = (
@ -238,20 +246,28 @@ class Trivia(commands.Cog):
if any(auth_checks): if any(auth_checks):
await session.end_game() await session.end_game()
session.force_stop() session.force_stop()
await ctx.send("Trivia stopped.") await ctx.send(_("Trivia stopped."))
else: 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") @trivia.command(name="list")
async def trivia_list(self, ctx: commands.Context): async def trivia_list(self, ctx: commands.Context):
"""List available trivia categories.""" """List available trivia categories."""
lists = set(p.stem for p in self._all_lists()) lists = set(p.stem for p in self._all_lists())
if await ctx.embed_requested():
msg = box("**Available trivia lists**\n\n{}".format(", ".join(sorted(lists)))) await ctx.send(
if len(msg) > 1000: embed=discord.Embed(
await ctx.author.send(msg) title=_("Available trivia lists"),
return colour=await ctx.embed_colour(),
await ctx.send(msg) 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) @trivia.group(name="leaderboard", aliases=["lboard"], autohelp=False)
async def trivia_leaderboard(self, ctx: commands.Context): async def trivia_leaderboard(self, ctx: commands.Context):
@ -273,19 +289,21 @@ class Trivia(commands.Cog):
): ):
"""Leaderboard for this server. """Leaderboard for this server.
<sort_by> can be any of the following fields: `<sort_by>` can be any of the following fields:
- wins : total wins - `wins` : total wins
- avg : average score - `avg` : average score
- total : total correct answers - `total` : total correct answers
- `games` : total games played
<top> is the number of ranks to show on the leaderboard. `<top>` is the number of ranks to show on the leaderboard.
""" """
key = self._get_sort_key(sort_by) key = self._get_sort_key(sort_by)
if key is None: if key is None:
await ctx.send( await ctx.send(
"Unknown field `{}`, see `{}help trivia " _(
"leaderboard server` for valid fields to sort by." "Unknown field `{field_name}`, see `{prefix}help trivia leaderboard server` "
"".format(sort_by, ctx.prefix) "for valid fields to sort by."
).format(field_name=sort_by, prefix=ctx.prefix)
) )
return return
guild = ctx.guild guild = ctx.guild
@ -300,20 +318,21 @@ class Trivia(commands.Cog):
): ):
"""Global trivia leaderboard. """Global trivia leaderboard.
<sort_by> can be any of the following fields: `<sort_by>` can be any of the following fields:
- wins : total wins - `wins` : total wins
- avg : average score - `avg` : average score
- total : total correct answers from all sessions - `total` : total correct answers from all sessions
- games : total games played - `games` : total games played
<top> is the number of ranks to show on the leaderboard. `<top>` is the number of ranks to show on the leaderboard.
""" """
key = self._get_sort_key(sort_by) key = self._get_sort_key(sort_by)
if key is None: if key is None:
await ctx.send( await ctx.send(
"Unknown field `{}`, see `{}help trivia " _(
"leaderboard global` for valid fields to sort by." "Unknown field `{field_name}`, see `{prefix}help trivia leaderboard server` "
"".format(sort_by, ctx.prefix) "for valid fields to sort by."
).format(field_name=sort_by, prefix=ctx.prefix)
) )
return return
data = await self.conf.all_members() data = await self.conf.all_members()
@ -365,7 +384,7 @@ class Trivia(commands.Cog):
""" """
if not data: if not data:
await ctx.send("There are no scores on record!") await ctx.send(_("There are no scores on record!"))
return return
leaderboard = self._get_leaderboard(data, key, top) leaderboard = self._get_leaderboard(data, key, top)
ret = [] ret = []
@ -386,7 +405,7 @@ class Trivia(commands.Cog):
try: try:
priority.remove(key) priority.remove(key)
except ValueError: 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 # Put key last in reverse priority
priority.append(key) priority.append(key)
items = data.items() items = data.items()
@ -395,16 +414,15 @@ class Trivia(commands.Cog):
max_name_len = max(map(lambda m: len(str(m)), data.keys())) max_name_len = max(map(lambda m: len(str(m)), data.keys()))
# Headers # Headers
headers = ( headers = (
"Rank", _("Rank"),
"Member{}".format(" " * (max_name_len - 6)), _("Member") + " " * (max_name_len - 6),
"Wins", _("Wins"),
"Games Played", _("Games Played"),
"Total Score", _("Total Score"),
"Average Score", _("Average Score"),
) )
lines = [" | ".join(headers)] lines = [" | ".join(headers), " | ".join(("-" * len(h) for h in headers))]
# Header underlines # Header underlines
lines.append(" | ".join(("-" * len(h) for h in headers)))
for rank, tup in enumerate(items, 1): for rank, tup in enumerate(items, 1):
member, m_data = tup member, m_data = tup
# Align fields to header width # Align fields to header width

View File

@ -22,7 +22,7 @@ _ = Translator("Warnings", __file__)
@cog_i18n(_) @cog_i18n(_)
class Warnings(commands.Cog): class Warnings(commands.Cog):
"""A warning system for Red""" """Warn misbehaving users and take automated actions."""
default_guild = {"actions": [], "reasons": {}, "allow_custom_reasons": False} default_guild = {"actions": [], "reasons": {}, "allow_custom_reasons": False}
@ -48,31 +48,42 @@ class Warnings(commands.Cog):
@commands.guild_only() @commands.guild_only()
@checks.guildowner_or_permissions(administrator=True) @checks.guildowner_or_permissions(administrator=True)
async def warningset(self, ctx: commands.Context): async def warningset(self, ctx: commands.Context):
"""Warning settings""" """Manage settings for Warnings."""
pass pass
@warningset.command() @warningset.command()
@commands.guild_only() @commands.guild_only()
async def allowcustomreasons(self, ctx: commands.Context, allowed: bool): 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 guild = ctx.guild
await self.config.guild(guild).allow_custom_reasons.set(allowed) await self.config.guild(guild).allow_custom_reasons.set(allowed)
await ctx.send( if allowed:
_("Custom reasons have been {}.").format(_("enabled") if allowed else _("disabled")) await ctx.send(_("Custom reasons have been enabled."))
) else:
await ctx.send(_("Custom reasons have been disabled."))
@commands.group() @commands.group()
@commands.guild_only() @commands.guild_only()
@checks.guildowner_or_permissions(administrator=True) @checks.guildowner_or_permissions(administrator=True)
async def warnaction(self, ctx: commands.Context): 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 pass
@warnaction.command(name="add") @warnaction.command(name="add")
@commands.guild_only() @commands.guild_only()
async def action_add(self, ctx: commands.Context, 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 """Create an automated action.
Duplicate action names are not allowed
Duplicate action names are not allowed.
""" """
guild = ctx.guild guild = ctx.guild
@ -103,7 +114,7 @@ class Warnings(commands.Cog):
@warnaction.command(name="del") @warnaction.command(name="del")
@commands.guild_only() @commands.guild_only()
async def action_del(self, ctx: commands.Context, action_name: str): 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 = ctx.guild
guild_settings = self.config.guild(guild) guild_settings = self.config.guild(guild)
async with guild_settings.actions() as registered_actions: async with guild_settings.actions() as registered_actions:
@ -116,23 +127,29 @@ class Warnings(commands.Cog):
registered_actions.remove(to_remove) registered_actions.remove(to_remove)
await ctx.tick() await ctx.tick()
else: 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.group()
@commands.guild_only() @commands.guild_only()
@checks.guildowner_or_permissions(administrator=True) @checks.guildowner_or_permissions(administrator=True)
async def warnreason(self, ctx: commands.Context): 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 pass
@warnreason.command(name="add") @warnreason.command(name="create", aliases=["add"])
@commands.guild_only() @commands.guild_only()
async def reason_add(self, ctx: commands.Context, name: str, points: int, *, description: str): async def reason_create(
"""Add a reason to be available for warnings""" self, ctx: commands.Context, name: str, points: int, *, description: str
):
"""Create a warning reason."""
guild = ctx.guild guild = ctx.guild
if name.lower() == "custom": 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 return
to_add = {"points": points, "description": description} to_add = {"points": points, "description": description}
completed = {name.lower(): to_add} completed = {name.lower(): to_add}
@ -142,12 +159,12 @@ class Warnings(commands.Cog):
async with guild_settings.reasons() as registered_reasons: async with guild_settings.reasons() as registered_reasons:
registered_reasons.update(completed) 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() @commands.guild_only()
async def reason_del(self, ctx: commands.Context, reason_name: str): 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 = ctx.guild
guild_settings = self.config.guild(guild) guild_settings = self.config.guild(guild)
async with guild_settings.reasons() as registered_reasons: async with guild_settings.reasons() as registered_reasons:
@ -160,7 +177,7 @@ class Warnings(commands.Cog):
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(ban_members=True) @checks.admin_or_permissions(ban_members=True)
async def reasonlist(self, ctx: commands.Context): async def reasonlist(self, ctx: commands.Context):
"""List all configured reasons for warnings""" """List all configured reasons for Warnings."""
guild = ctx.guild guild = ctx.guild
guild_settings = self.config.guild(guild) guild_settings = self.config.guild(guild)
msg_list = [] msg_list = []
@ -174,9 +191,9 @@ class Warnings(commands.Cog):
msg_list.append(em) msg_list.append(em)
else: else:
msg_list.append( 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: if msg_list:
await menu(ctx, msg_list, DEFAULT_CONTROLS) await menu(ctx, msg_list, DEFAULT_CONTROLS)
@ -187,7 +204,7 @@ class Warnings(commands.Cog):
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(ban_members=True) @checks.admin_or_permissions(ban_members=True)
async def actionlist(self, ctx: commands.Context): 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 = ctx.guild
guild_settings = self.config.guild(guild) guild_settings = self.config.guild(guild)
msg_list = [] msg_list = []
@ -201,10 +218,10 @@ class Warnings(commands.Cog):
msg_list.append(em) msg_list.append(em)
else: else:
msg_list.append( msg_list.append(
"Name: {}\nPoints: {}\nExceed command: {}\n" _(
"Drop command: {}".format( "Name: {action_name}\nPoints: {points}\n"
r["action_name"], r["points"], r["exceed_command"], r["drop_command"] "Exceed command: {exceed_command}\nDrop command: {drop_command}"
) ).format(**r)
) )
if msg_list: if msg_list:
await menu(ctx, msg_list, DEFAULT_CONTROLS) await menu(ctx, msg_list, DEFAULT_CONTROLS)
@ -215,8 +232,10 @@ class Warnings(commands.Cog):
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(ban_members=True) @checks.admin_or_permissions(ban_members=True)
async def warn(self, ctx: commands.Context, user: discord.Member, reason: str): async def warn(self, ctx: commands.Context, user: discord.Member, reason: str):
"""Warn the user for the specified reason """Warn the user for the specified reason.
Reason must be a registered reason, or "custom" if custom reasons are allowed
`<reason>` must be a registered reason name, or *custom* if
custom reasons are enabled.
""" """
if user == ctx.author: if user == ctx.author:
await ctx.send(_("You cannot warn yourself.")) await ctx.send(_("You cannot warn yourself."))
@ -226,9 +245,9 @@ class Warnings(commands.Cog):
if not custom_allowed: if not custom_allowed:
await ctx.send( 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." "a complete list of valid reasons."
).format("`{}reasonlist`".format(ctx.prefix)) ).format(prefix=ctx.prefix)
) )
return return
reason_type = await self.custom_warning_reason(ctx) 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) await warning_points_add_check(self.config, ctx, user, current_point_count)
try: try:
em = discord.Embed( em = discord.Embed(
title=_("Warning from {mod_name}#{mod_discrim}").format( title=_("Warning from {user}").format(user=ctx.author),
mod_name=ctx.author.display_name, mod_discrim=ctx.author.discriminator
),
description=reason_type["description"], description=reason_type["description"],
) )
em.add_field(name=_("Points"), value=str(reason_type["points"])) em.add_field(name=_("Points"), value=str(reason_type["points"]))
@ -286,19 +303,17 @@ class Warnings(commands.Cog):
) )
except discord.HTTPException: except discord.HTTPException:
pass pass
await ctx.send( await ctx.send(_("User {user} has been warned.").format(user=user))
_("User {user_name}#{user_discrim} has been warned.").format(
user_name=user.display_name, user_discrim=user.discriminator
)
)
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
async def warnings(self, ctx: commands.Context, userid: int = None): async def warnings(self, ctx: commands.Context, userid: int = None):
"""Show warnings for the specified user. """List the warnings for the specified user.
If userid is None, show warnings for the person running the command
Emit `<userid>` to see your own warnings.
Note that showing warnings for users other than yourself requires Note that showing warnings for users other than yourself requires
appropriate permissions appropriate permissions.
""" """
if userid is None: if userid is None:
user = ctx.author user = ctx.author
@ -326,18 +341,24 @@ class Warnings(commands.Cog):
) )
if mod is None: if mod is None:
mod = await self.bot.get_user_info(user_warnings[key]["mod"]) mod = await self.bot.get_user_info(user_warnings[key]["mod"])
msg += "{} point warning {} issued by {} for {}\n".format( msg += _(
user_warnings[key]["points"], key, mod, user_warnings[key]["description"] "{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( 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.command()
@commands.guild_only() @commands.guild_only()
@checks.admin_or_permissions(ban_members=True) @checks.admin_or_permissions(ban_members=True)
async def unwarn(self, ctx: commands.Context, 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""" """Remove a warning from a user."""
if user_id == ctx.author.id: if user_id == ctx.author.id:
await ctx.send(_("You cannot remove warnings from yourself.")) await ctx.send(_("You cannot remove warnings from yourself."))
return return
@ -351,7 +372,7 @@ class Warnings(commands.Cog):
await warning_points_remove_check(self.config, ctx, member, current_point_count) await warning_points_remove_check(self.config, ctx, member, current_point_count)
async with member_settings.warnings() as user_warnings: async with member_settings.warnings() as user_warnings:
if warn_id not in user_warnings.keys(): 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 return
else: else:
current_point_count -= user_warnings[warn_id]["points"] current_point_count -= user_warnings[warn_id]["points"]

View File

@ -4,7 +4,7 @@ from redbot.core.utils.chat_formatting import pagify
import io import io
import sys import sys
import weakref import weakref
from typing import List from typing import List, Optional
from .common_filters import filter_mass_mentions from .common_filters import filter_mass_mentions
_instances = weakref.WeakValueDictionary({}) _instances = weakref.WeakValueDictionary({})
@ -86,7 +86,11 @@ class Tunnel(metaclass=TunnelMeta):
@staticmethod @staticmethod
async def message_forwarder( 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]: ) -> List[discord.Message]:
""" """
This does the actual sending, use this instead of a full tunnel This does the actual sending, use this instead of a full tunnel
@ -95,19 +99,19 @@ class Tunnel(metaclass=TunnelMeta):
Parameters Parameters
---------- ----------
destination: `discord.abc.Messageable` destination: discord.abc.Messageable
Where to send Where to send
content: `str` content: str
The message content The message content
embed: `discord.Embed` embed: discord.Embed
The embed to send The embed to send
files: `list` of `discord.File` files: Optional[List[discord.File]]
A list of files to send. A list of files to send.
Returns Returns
------- -------
list of `discord.Message` List[discord.Message]
The `discord.Message`\ (s) sent as a result The messages sent as a result.
Raises Raises
------ ------
@ -117,7 +121,6 @@ class Tunnel(metaclass=TunnelMeta):
see `discord.abc.Messageable.send` see `discord.abc.Messageable.send`
""" """
rets = [] rets = []
files = files if files else None
if content: if content:
for page in pagify(content): for page in pagify(content):
rets.append(await destination.send(page, files=files, embed=embed)) rets.append(await destination.send(page, files=files, embed=embed))