mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
[i18n] Pass over modlog, permissions, reports, streams, trivia, warnings
Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
This commit is contained in:
parent
fa692ccc0b
commit
443f2ca556
@ -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 `<days>` 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)
|
||||
|
||||
@ -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 `<channel>` 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] <reason>")
|
||||
@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.
|
||||
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
|
||||
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:
|
||||
|
||||
@ -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)
|
||||
)
|
||||
|
||||
@ -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):
|
||||
`<who_or_what>` 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):
|
||||
|
||||
`<who_or_what>` 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):
|
||||
`<cog_or_command>` 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):
|
||||
`<cog_or_command>` 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."))
|
||||
|
||||
|
||||
@ -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 <text> to use it non-interactively.
|
||||
`[p]report <text>` to use it non-interactively.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
@ -323,8 +319,7 @@ 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
|
||||
)
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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="<true_or_false>")
|
||||
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="<true_or_false>")
|
||||
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.
|
||||
|
||||
<sort_by> can be any of the following fields:
|
||||
- wins : total wins
|
||||
- avg : average score
|
||||
- total : total correct answers
|
||||
`<sort_by>` can be any of the following fields:
|
||||
- `wins` : total wins
|
||||
- `avg` : average score
|
||||
- `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)
|
||||
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.
|
||||
|
||||
<sort_by> can be any of the following fields:
|
||||
- wins : total wins
|
||||
- avg : average score
|
||||
- total : total correct answers from all sessions
|
||||
- games : total games played
|
||||
`<sort_by>` can be any of the following fields:
|
||||
- `wins` : total wins
|
||||
- `avg` : average score
|
||||
- `total` : total correct answers from all sessions
|
||||
- `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)
|
||||
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
|
||||
|
||||
@ -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.
|
||||
|
||||
`<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 `<userid>` 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"]
|
||||
|
||||
@ -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))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user