mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
Merge branch V3/feature/i18n_pass into V3/develop (#2024)
[i18n] Improves the coverage and quality of extracted user-facing string literals in the `redbot.core` package and makes them less ambiguous for the translator.
This commit is contained in:
commit
7b15ad5989
@ -1,44 +1,50 @@
|
||||
import logging
|
||||
from typing import Tuple
|
||||
|
||||
import discord
|
||||
|
||||
from redbot.core import Config, checks, commands
|
||||
|
||||
import logging
|
||||
|
||||
from redbot.core.i18n import Translator, cog_i18n
|
||||
from redbot.core.utils.chat_formatting import box
|
||||
from .announcer import Announcer
|
||||
from .converters import MemberDefaultAuthor, SelfRole
|
||||
|
||||
log = logging.getLogger("red.admin")
|
||||
|
||||
GENERIC_FORBIDDEN = (
|
||||
T_ = Translator("Admin", __file__)
|
||||
|
||||
_ = lambda s: s
|
||||
GENERIC_FORBIDDEN = _(
|
||||
"I attempted to do something that Discord denied me permissions for."
|
||||
" Your command failed to successfully complete."
|
||||
)
|
||||
|
||||
HIERARCHY_ISSUE = (
|
||||
HIERARCHY_ISSUE = _(
|
||||
"I tried to add {role.name} to {member.display_name} but that role"
|
||||
" is higher than my highest role in the Discord hierarchy so I was"
|
||||
" unable to successfully add it. Please give me a higher role and "
|
||||
"try again."
|
||||
)
|
||||
|
||||
USER_HIERARCHY_ISSUE = (
|
||||
USER_HIERARCHY_ISSUE = _(
|
||||
"I tried to add {role.name} to {member.display_name} but that role"
|
||||
" is higher than your highest role in the Discord hierarchy so I was"
|
||||
" unable to successfully add it. Please get a higher role and "
|
||||
"try again."
|
||||
)
|
||||
|
||||
RUNNING_ANNOUNCEMENT = (
|
||||
RUNNING_ANNOUNCEMENT = _(
|
||||
"I am already announcing something. If you would like to make a"
|
||||
" different announcement please use `{prefix}announce cancel`"
|
||||
" first."
|
||||
)
|
||||
_ = T_
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
class Admin(commands.Cog):
|
||||
"""A collection of server administration utilities."""
|
||||
|
||||
def __init__(self, config=Config):
|
||||
super().__init__()
|
||||
self.conf = config.get_conf(self, 8237492837454039, force_registration=True)
|
||||
@ -98,13 +104,14 @@ class Admin(commands.Cog):
|
||||
await member.add_roles(role)
|
||||
except discord.Forbidden:
|
||||
if not self.pass_hierarchy_check(ctx, role):
|
||||
await self.complain(ctx, HIERARCHY_ISSUE, role=role, member=member)
|
||||
await self.complain(ctx, T_(HIERARCHY_ISSUE), role=role, member=member)
|
||||
else:
|
||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
||||
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
|
||||
else:
|
||||
await ctx.send(
|
||||
"I successfully added {role.name} to"
|
||||
" {member.display_name}".format(role=role, member=member)
|
||||
_("I successfully added {role.name} to {member.display_name}").format(
|
||||
role=role, member=member
|
||||
)
|
||||
)
|
||||
|
||||
async def _removerole(self, ctx: commands.Context, member: discord.Member, role: discord.Role):
|
||||
@ -112,13 +119,14 @@ class Admin(commands.Cog):
|
||||
await member.remove_roles(role)
|
||||
except discord.Forbidden:
|
||||
if not self.pass_hierarchy_check(ctx, role):
|
||||
await self.complain(ctx, HIERARCHY_ISSUE, role=role, member=member)
|
||||
await self.complain(ctx, T_(HIERARCHY_ISSUE), role=role, member=member)
|
||||
else:
|
||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
||||
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
|
||||
else:
|
||||
await ctx.send(
|
||||
"I successfully removed {role.name} from"
|
||||
" {member.display_name}".format(role=role, member=member)
|
||||
_("I successfully removed {role.name} from {member.display_name}").format(
|
||||
role=role, member=member
|
||||
)
|
||||
)
|
||||
|
||||
@commands.command()
|
||||
@ -127,8 +135,8 @@ class Admin(commands.Cog):
|
||||
async def addrole(
|
||||
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
||||
):
|
||||
"""
|
||||
Adds a role to a user.
|
||||
"""Add a role to a user.
|
||||
|
||||
If user is left blank it defaults to the author of the command.
|
||||
"""
|
||||
if user is None:
|
||||
@ -137,7 +145,7 @@ class Admin(commands.Cog):
|
||||
# noinspection PyTypeChecker
|
||||
await self._addrole(ctx, user, rolename)
|
||||
else:
|
||||
await self.complain(ctx, USER_HIERARCHY_ISSUE, member=ctx.author, role=rolename)
|
||||
await self.complain(ctx, T_(USER_HIERARCHY_ISSUE), member=ctx.author, role=rolename)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@ -145,8 +153,8 @@ class Admin(commands.Cog):
|
||||
async def removerole(
|
||||
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
||||
):
|
||||
"""
|
||||
Removes a role from a user.
|
||||
"""Remove a role from a user.
|
||||
|
||||
If user is left blank it defaults to the author of the command.
|
||||
"""
|
||||
if user is None:
|
||||
@ -155,50 +163,54 @@ class Admin(commands.Cog):
|
||||
# noinspection PyTypeChecker
|
||||
await self._removerole(ctx, user, rolename)
|
||||
else:
|
||||
await self.complain(ctx, USER_HIERARCHY_ISSUE)
|
||||
await self.complain(ctx, T_(USER_HIERARCHY_ISSUE))
|
||||
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@checks.admin_or_permissions(manage_roles=True)
|
||||
async def editrole(self, ctx: commands.Context):
|
||||
"""Edits roles settings"""
|
||||
"""Edit role settings."""
|
||||
pass
|
||||
|
||||
@editrole.command(name="colour", aliases=["color"])
|
||||
async def editrole_colour(
|
||||
self, ctx: commands.Context, role: discord.Role, value: discord.Colour
|
||||
):
|
||||
"""Edits a role's colour
|
||||
"""Edit a role's colour.
|
||||
|
||||
Use double quotes if the role contains spaces.
|
||||
Colour must be in hexadecimal format.
|
||||
\"http://www.w3schools.com/colors/colors_picker.asp\"
|
||||
[Online colour picker](http://www.w3schools.com/colors/colors_picker.asp)
|
||||
|
||||
Examples:
|
||||
!editrole colour \"The Transistor\" #ff0000
|
||||
!editrole colour Test #ff9900"""
|
||||
`[p]editrole colour "The Transistor" #ff0000`
|
||||
`[p]editrole colour Test #ff9900`
|
||||
"""
|
||||
author = ctx.author
|
||||
reason = "{}({}) changed the colour of role '{}'".format(author.name, author.id, role.name)
|
||||
|
||||
if not self.pass_user_hierarchy_check(ctx, role):
|
||||
await self.complain(ctx, USER_HIERARCHY_ISSUE)
|
||||
await self.complain(ctx, T_(USER_HIERARCHY_ISSUE))
|
||||
return
|
||||
|
||||
try:
|
||||
await role.edit(reason=reason, color=value)
|
||||
except discord.Forbidden:
|
||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
||||
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
|
||||
else:
|
||||
log.info(reason)
|
||||
await ctx.send("Done.")
|
||||
await ctx.send(_("Done."))
|
||||
|
||||
@editrole.command(name="name")
|
||||
@checks.admin_or_permissions(administrator=True)
|
||||
async def edit_role_name(self, ctx: commands.Context, role: discord.Role, *, name: str):
|
||||
"""Edits a role's name
|
||||
"""Edit a role's name.
|
||||
|
||||
Use double quotes if the role or the name contain spaces.
|
||||
|
||||
Examples:
|
||||
!editrole name \"The Transistor\" Test"""
|
||||
`[p]editrole name \"The Transistor\" Test`
|
||||
"""
|
||||
author = ctx.message.author
|
||||
old_name = role.name
|
||||
reason = "{}({}) changed the name of role '{}' to '{}'".format(
|
||||
@ -206,73 +218,74 @@ class Admin(commands.Cog):
|
||||
)
|
||||
|
||||
if not self.pass_user_hierarchy_check(ctx, role):
|
||||
await self.complain(ctx, USER_HIERARCHY_ISSUE)
|
||||
await self.complain(ctx, T_(USER_HIERARCHY_ISSUE))
|
||||
return
|
||||
|
||||
try:
|
||||
await role.edit(reason=reason, name=name)
|
||||
except discord.Forbidden:
|
||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
||||
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
|
||||
else:
|
||||
log.info(reason)
|
||||
await ctx.send("Done.")
|
||||
await ctx.send(_("Done."))
|
||||
|
||||
@commands.group(invoke_without_command=True)
|
||||
@checks.is_owner()
|
||||
async def announce(self, ctx: commands.Context, *, message: str):
|
||||
"""
|
||||
Announces a message to all servers the bot is in.
|
||||
"""
|
||||
"""Announce a message to all servers the bot is in."""
|
||||
if not self.is_announcing():
|
||||
announcer = Announcer(ctx, message, config=self.conf)
|
||||
announcer.start()
|
||||
|
||||
self.__current_announcer = announcer
|
||||
|
||||
await ctx.send("The announcement has begun.")
|
||||
await ctx.send(_("The announcement has begun."))
|
||||
else:
|
||||
prefix = ctx.prefix
|
||||
await self.complain(ctx, RUNNING_ANNOUNCEMENT, prefix=prefix)
|
||||
await self.complain(ctx, T_(RUNNING_ANNOUNCEMENT), prefix=prefix)
|
||||
|
||||
@announce.command(name="cancel")
|
||||
@checks.is_owner()
|
||||
async def announce_cancel(self, ctx):
|
||||
"""
|
||||
Cancels a running announce.
|
||||
"""
|
||||
"""Cancel a running announce."""
|
||||
try:
|
||||
self.__current_announcer.cancel()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
await ctx.send("The current announcement has been cancelled.")
|
||||
await ctx.send(_("The current announcement has been cancelled."))
|
||||
|
||||
@announce.command(name="channel")
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def announce_channel(self, ctx, *, channel: discord.TextChannel = None):
|
||||
"""
|
||||
Changes the channel on which the bot makes announcements.
|
||||
"""
|
||||
"""Change the channel to which the bot makes announcements."""
|
||||
if channel is None:
|
||||
channel = ctx.channel
|
||||
await self.conf.guild(ctx.guild).announce_channel.set(channel.id)
|
||||
|
||||
await ctx.send("The announcement channel has been set to {}".format(channel.mention))
|
||||
await ctx.send(
|
||||
_("The announcement channel has been set to {channel.mention}").format(channel=channel)
|
||||
)
|
||||
|
||||
@announce.command(name="ignore")
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def announce_ignore(self, ctx):
|
||||
"""
|
||||
Toggles whether the announcements will ignore the current server.
|
||||
"""
|
||||
"""Toggle announcements being enabled this server."""
|
||||
ignored = await self.conf.guild(ctx.guild).announce_ignore()
|
||||
await self.conf.guild(ctx.guild).announce_ignore.set(not ignored)
|
||||
|
||||
verb = "will" if ignored else "will not"
|
||||
|
||||
await ctx.send(f"The server {ctx.guild.name} {verb} receive announcements.")
|
||||
if ignored: # Keeping original logic....
|
||||
await ctx.send(
|
||||
_("The server {guild.name} will receive announcements.").format(guild=ctx.guild)
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
_("The server {guild.name} will not receive announcements.").format(
|
||||
guild=ctx.guild
|
||||
)
|
||||
)
|
||||
|
||||
async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]:
|
||||
"""
|
||||
@ -295,8 +308,9 @@ class Admin(commands.Cog):
|
||||
@commands.guild_only()
|
||||
@commands.group(invoke_without_command=True)
|
||||
async def selfrole(self, ctx: commands.Context, *, selfrole: SelfRole):
|
||||
"""
|
||||
Add a role to yourself that server admins have configured as user settable.
|
||||
"""Add a role to yourself.
|
||||
|
||||
Server admins must have configured the role as user settable.
|
||||
|
||||
NOTE: The role is case sensitive!
|
||||
"""
|
||||
@ -305,8 +319,7 @@ class Admin(commands.Cog):
|
||||
|
||||
@selfrole.command(name="remove")
|
||||
async def selfrole_remove(self, ctx: commands.Context, *, selfrole: SelfRole):
|
||||
"""
|
||||
Removes a selfrole from yourself.
|
||||
"""Remove a selfrole from yourself.
|
||||
|
||||
NOTE: The role is case sensitive!
|
||||
"""
|
||||
@ -316,8 +329,7 @@ class Admin(commands.Cog):
|
||||
@selfrole.command(name="add")
|
||||
@checks.admin_or_permissions(manage_roles=True)
|
||||
async def selfrole_add(self, ctx: commands.Context, *, role: discord.Role):
|
||||
"""
|
||||
Add a role to the list of available selfroles.
|
||||
"""Add a role to the list of available selfroles.
|
||||
|
||||
NOTE: The role is case sensitive!
|
||||
"""
|
||||
@ -325,20 +337,19 @@ class Admin(commands.Cog):
|
||||
if role.id not in curr_selfroles:
|
||||
curr_selfroles.append(role.id)
|
||||
|
||||
await ctx.send("The selfroles list has been successfully modified.")
|
||||
await ctx.send(_("The selfroles list has been successfully modified."))
|
||||
|
||||
@selfrole.command(name="delete")
|
||||
@checks.admin_or_permissions(manage_roles=True)
|
||||
async def selfrole_delete(self, ctx: commands.Context, *, role: SelfRole):
|
||||
"""
|
||||
Removes a role from the list of available selfroles.
|
||||
"""Remove a role from the list of available selfroles.
|
||||
|
||||
NOTE: The role is case sensitive!
|
||||
"""
|
||||
async with self.conf.guild(ctx.guild).selfroles() as curr_selfroles:
|
||||
curr_selfroles.remove(role.id)
|
||||
|
||||
await ctx.send("The selfroles list has been successfully modified.")
|
||||
await ctx.send(_("The selfroles list has been successfully modified."))
|
||||
|
||||
@selfrole.command(name="list")
|
||||
async def selfrole_list(self, ctx: commands.Context):
|
||||
@ -348,7 +359,7 @@ class Admin(commands.Cog):
|
||||
selfroles = await self._valid_selfroles(ctx.guild)
|
||||
fmt_selfroles = "\n".join(["+ " + r.name for r in selfroles])
|
||||
|
||||
msg = "Available Selfroles:\n{}".format(fmt_selfroles)
|
||||
msg = _("Available Selfroles: {selfroles}").format(selfroles=fmt_selfroles)
|
||||
await ctx.send(box(msg, "diff"))
|
||||
|
||||
async def _serverlock_check(self, guild: discord.Guild) -> bool:
|
||||
@ -365,15 +376,14 @@ class Admin(commands.Cog):
|
||||
@commands.command()
|
||||
@checks.is_owner()
|
||||
async def serverlock(self, ctx: commands.Context):
|
||||
"""
|
||||
Locks a bot to its current servers only.
|
||||
"""
|
||||
"""Lock a bot to its current servers only."""
|
||||
serverlocked = await self.conf.serverlocked()
|
||||
await self.conf.serverlocked.set(not serverlocked)
|
||||
|
||||
verb = "is now" if not serverlocked else "is no longer"
|
||||
|
||||
await ctx.send("The bot {} serverlocked.".format(verb))
|
||||
if serverlocked:
|
||||
await ctx.send(_("The bot is no longer serverlocked."))
|
||||
else:
|
||||
await ctx.send(_("The bot is now serverlocked."))
|
||||
|
||||
# region Event Handlers
|
||||
async def on_guild_join(self, guild: discord.Guild):
|
||||
|
||||
@ -2,6 +2,9 @@ import asyncio
|
||||
|
||||
import discord
|
||||
from redbot.core import commands
|
||||
from redbot.core.i18n import Translator
|
||||
|
||||
_ = Translator("Announcer", __file__)
|
||||
|
||||
|
||||
class Announcer:
|
||||
@ -63,7 +66,9 @@ class Announcer:
|
||||
try:
|
||||
await channel.send(self.message)
|
||||
except discord.Forbidden:
|
||||
await bot_owner.send("I could not announce to server: {}".format(g.id))
|
||||
await bot_owner.send(
|
||||
_("I could not announce to server: {server.id}").format(server=g)
|
||||
)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
self.active = False
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import discord
|
||||
from redbot.core import commands
|
||||
from redbot.core.i18n import Translator
|
||||
|
||||
_ = Translator("AdminConverters", __file__)
|
||||
|
||||
|
||||
class MemberDefaultAuthor(commands.Converter):
|
||||
@ -19,7 +22,7 @@ class SelfRole(commands.Converter):
|
||||
async def convert(self, ctx: commands.Context, arg: str) -> discord.Role:
|
||||
admin = ctx.command.instance
|
||||
if admin is None:
|
||||
raise commands.BadArgument("Admin is not loaded.")
|
||||
raise commands.BadArgument(_("The Admin cog is not loaded."))
|
||||
|
||||
conf = admin.conf
|
||||
selfroles = await conf.guild(ctx.guild).selfroles()
|
||||
@ -28,5 +31,5 @@ class SelfRole(commands.Converter):
|
||||
role = await role_converter.convert(ctx, arg)
|
||||
|
||||
if role.id not in selfroles:
|
||||
raise commands.BadArgument("The provided role is not a valid selfrole.")
|
||||
raise commands.BadArgument(_("The provided role is not a valid selfrole."))
|
||||
return role
|
||||
|
||||
@ -15,15 +15,14 @@ _ = Translator("Alias", __file__)
|
||||
|
||||
@cog_i18n(_)
|
||||
class Alias(commands.Cog):
|
||||
"""
|
||||
Alias
|
||||
|
||||
Aliases are per server shortcuts for commands. They
|
||||
can act as both a lambda (storing arguments for repeated use)
|
||||
or as simply a shortcut to saying "x y z".
|
||||
|
||||
"""Create aliases for commands.
|
||||
|
||||
Aliases are alternative names shortcuts for commands. They
|
||||
can act as both a lambda (storing arguments for repeated use)
|
||||
or as simply a shortcut to saying "x y z".
|
||||
|
||||
When run, aliases will accept any additional arguments
|
||||
and append them to the stored alias
|
||||
and append them to the stored alias.
|
||||
"""
|
||||
|
||||
default_global_settings = {"entries": []}
|
||||
@ -177,32 +176,28 @@ class Alias(commands.Cog):
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
async def alias(self, ctx: commands.Context):
|
||||
"""Manage per-server aliases for commands"""
|
||||
"""Manage command aliases."""
|
||||
pass
|
||||
|
||||
@alias.group(name="global")
|
||||
async def global_(self, ctx: commands.Context):
|
||||
"""
|
||||
Manage global aliases.
|
||||
"""
|
||||
"""Manage global aliases."""
|
||||
pass
|
||||
|
||||
@checks.mod_or_permissions(manage_guild=True)
|
||||
@alias.command(name="add")
|
||||
@commands.guild_only()
|
||||
async def _add_alias(self, ctx: commands.Context, alias_name: str, *, command):
|
||||
"""
|
||||
Add an alias for a command.
|
||||
"""
|
||||
"""Add an alias for a command."""
|
||||
# region Alias Add Validity Checking
|
||||
is_command = self.is_command(alias_name)
|
||||
if is_command:
|
||||
await ctx.send(
|
||||
_(
|
||||
"You attempted to create a new alias"
|
||||
" with the name {} but that"
|
||||
" with the name {name} but that"
|
||||
" name is already a command on this bot."
|
||||
).format(alias_name)
|
||||
).format(name=alias_name)
|
||||
)
|
||||
return
|
||||
|
||||
@ -211,9 +206,9 @@ class Alias(commands.Cog):
|
||||
await ctx.send(
|
||||
_(
|
||||
"You attempted to create a new alias"
|
||||
" with the name {} but that"
|
||||
" with the name {name} but that"
|
||||
" alias already exists on this server."
|
||||
).format(alias_name)
|
||||
).format(name=alias_name)
|
||||
)
|
||||
return
|
||||
|
||||
@ -222,10 +217,10 @@ class Alias(commands.Cog):
|
||||
await ctx.send(
|
||||
_(
|
||||
"You attempted to create a new alias"
|
||||
" with the name {} but that"
|
||||
" with the name {name} but that"
|
||||
" name is an invalid alias name. Alias"
|
||||
" names may not contain spaces."
|
||||
).format(alias_name)
|
||||
).format(name=alias_name)
|
||||
)
|
||||
return
|
||||
# endregion
|
||||
@ -235,23 +230,23 @@ class Alias(commands.Cog):
|
||||
|
||||
await self.add_alias(ctx, alias_name, command)
|
||||
|
||||
await ctx.send(_("A new alias with the trigger `{}` has been created.").format(alias_name))
|
||||
await ctx.send(
|
||||
_("A new alias with the trigger `{name}` has been created.").format(name=alias_name)
|
||||
)
|
||||
|
||||
@checks.is_owner()
|
||||
@global_.command(name="add")
|
||||
async def _add_global_alias(self, ctx: commands.Context, alias_name: str, *, command):
|
||||
"""
|
||||
Add a global alias for a command.
|
||||
"""
|
||||
"""Add a global alias for a command."""
|
||||
# region Alias Add Validity Checking
|
||||
is_command = self.is_command(alias_name)
|
||||
if is_command:
|
||||
await ctx.send(
|
||||
_(
|
||||
"You attempted to create a new global alias"
|
||||
" with the name {} but that"
|
||||
" with the name {name} but that"
|
||||
" name is already a command on this bot."
|
||||
).format(alias_name)
|
||||
).format(name=alias_name)
|
||||
)
|
||||
return
|
||||
|
||||
@ -260,9 +255,9 @@ class Alias(commands.Cog):
|
||||
await ctx.send(
|
||||
_(
|
||||
"You attempted to create a new global alias"
|
||||
" with the name {} but that"
|
||||
" with the name {name} but that"
|
||||
" alias already exists on this server."
|
||||
).format(alias_name)
|
||||
).format(name=alias_name)
|
||||
)
|
||||
return
|
||||
|
||||
@ -271,10 +266,10 @@ class Alias(commands.Cog):
|
||||
await ctx.send(
|
||||
_(
|
||||
"You attempted to create a new global alias"
|
||||
" with the name {} but that"
|
||||
" with the name {name} but that"
|
||||
" name is an invalid alias name. Alias"
|
||||
" names may not contain spaces."
|
||||
).format(alias_name)
|
||||
).format(name=alias_name)
|
||||
)
|
||||
return
|
||||
# endregion
|
||||
@ -282,63 +277,65 @@ class Alias(commands.Cog):
|
||||
await self.add_alias(ctx, alias_name, command, global_=True)
|
||||
|
||||
await ctx.send(
|
||||
_("A new global alias with the trigger `{}` has been created.").format(alias_name)
|
||||
_("A new global alias with the trigger `{name}` has been created.").format(
|
||||
name=alias_name
|
||||
)
|
||||
)
|
||||
|
||||
@alias.command(name="help")
|
||||
@commands.guild_only()
|
||||
async def _help_alias(self, ctx: commands.Context, alias_name: str):
|
||||
"""Tries to execute help for the base command of the alias"""
|
||||
"""Try to execute help for the base command of the alias."""
|
||||
is_alias, alias = await self.is_alias(ctx.guild, alias_name=alias_name)
|
||||
if is_alias:
|
||||
base_cmd = alias.command[0]
|
||||
|
||||
new_msg = copy(ctx.message)
|
||||
new_msg.content = "{}help {}".format(ctx.prefix, base_cmd)
|
||||
new_msg.content = _("{prefix}help {command}").format(
|
||||
prefix=ctx.prefix, command=base_cmd
|
||||
)
|
||||
await self.bot.process_commands(new_msg)
|
||||
else:
|
||||
ctx.send(_("No such alias exists."))
|
||||
await ctx.send(_("No such alias exists."))
|
||||
|
||||
@alias.command(name="show")
|
||||
@commands.guild_only()
|
||||
async def _show_alias(self, ctx: commands.Context, alias_name: str):
|
||||
"""Shows what command the alias executes."""
|
||||
"""Show what command the alias executes."""
|
||||
is_alias, alias = await self.is_alias(ctx.guild, alias_name)
|
||||
|
||||
if is_alias:
|
||||
await ctx.send(
|
||||
_("The `{}` alias will execute the command `{}`").format(alias_name, alias.command)
|
||||
_("The `{alias_name}` alias will execute the command `{command}`").format(
|
||||
alias_name=alias_name, command=alias.command
|
||||
)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("There is no alias with the name `{}`").format(alias_name))
|
||||
await ctx.send(_("There is no alias with the name `{name}`").format(name=alias_name))
|
||||
|
||||
@checks.mod_or_permissions(manage_guild=True)
|
||||
@alias.command(name="del")
|
||||
@commands.guild_only()
|
||||
async def _del_alias(self, ctx: commands.Context, alias_name: str):
|
||||
"""
|
||||
Deletes an existing alias on this server.
|
||||
"""
|
||||
"""Delete an existing alias on this server."""
|
||||
aliases = await self.unloaded_aliases(ctx.guild)
|
||||
try:
|
||||
next(aliases)
|
||||
except StopIteration:
|
||||
await ctx.send(_("There are no aliases on this guild."))
|
||||
await ctx.send(_("There are no aliases on this server."))
|
||||
return
|
||||
|
||||
if await self.delete_alias(ctx, alias_name):
|
||||
await ctx.send(
|
||||
_("Alias with the name `{}` was successfully deleted.").format(alias_name)
|
||||
_("Alias with the name `{name}` was successfully deleted.").format(name=alias_name)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
||||
await ctx.send(_("Alias with name `{name}` was not found.").format(name=alias_name))
|
||||
|
||||
@checks.is_owner()
|
||||
@global_.command(name="del")
|
||||
async def _del_global_alias(self, ctx: commands.Context, alias_name: str):
|
||||
"""
|
||||
Deletes an existing global alias.
|
||||
"""
|
||||
"""Delete an existing global alias."""
|
||||
aliases = await self.unloaded_global_aliases()
|
||||
try:
|
||||
next(aliases)
|
||||
@ -348,17 +345,15 @@ class Alias(commands.Cog):
|
||||
|
||||
if await self.delete_alias(ctx, alias_name, global_=True):
|
||||
await ctx.send(
|
||||
_("Alias with the name `{}` was successfully deleted.").format(alias_name)
|
||||
_("Alias with the name `{name}` was successfully deleted.").format(name=alias_name)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("Alias with name `{}` was not found.").format(alias_name))
|
||||
await ctx.send(_("Alias with name `{name}` was not found.").format(name=alias_name))
|
||||
|
||||
@alias.command(name="list")
|
||||
@commands.guild_only()
|
||||
async def _list_alias(self, ctx: commands.Context):
|
||||
"""
|
||||
Lists the available aliases on this server.
|
||||
"""
|
||||
"""List the available aliases on this server."""
|
||||
names = [_("Aliases:")] + sorted(
|
||||
["+ " + a.name for a in (await self.unloaded_aliases(ctx.guild))]
|
||||
)
|
||||
@ -369,9 +364,7 @@ class Alias(commands.Cog):
|
||||
|
||||
@global_.command(name="list")
|
||||
async def _list_global_alias(self, ctx: commands.Context):
|
||||
"""
|
||||
Lists the available global aliases on this bot.
|
||||
"""
|
||||
"""List the available global aliases on this bot."""
|
||||
names = [_("Aliases:")] + sorted(
|
||||
["+ " + a.name for a in await self.unloaded_global_aliases()]
|
||||
)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -67,7 +67,7 @@ class Bank(commands.Cog):
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
@commands.group(autohelp=True)
|
||||
async def bankset(self, ctx: commands.Context):
|
||||
"""Base command for bank settings"""
|
||||
"""Base command for bank settings."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
if await bank.is_global():
|
||||
bank_name = await bank._conf.bank_name()
|
||||
@ -81,42 +81,47 @@ class Bank(commands.Cog):
|
||||
default_balance = await bank._conf.guild(ctx.guild).default_balance()
|
||||
|
||||
settings = _(
|
||||
"Bank settings:\n\nBank name: {}\nCurrency: {}\nDefault balance: {}"
|
||||
).format(bank_name, currency_name, default_balance)
|
||||
"Bank settings:\n\nBank name: {bank_name}\nCurrency: {currency_name}\n"
|
||||
"Default balance: {default_balance}"
|
||||
).format(
|
||||
bank_name=bank_name, currency_name=currency_name, default_balance=default_balance
|
||||
)
|
||||
await ctx.send(box(settings))
|
||||
|
||||
@bankset.command(name="toggleglobal")
|
||||
@checks.is_owner()
|
||||
async def bankset_toggleglobal(self, ctx: commands.Context, confirm: bool = False):
|
||||
"""Toggles whether the bank is global or not
|
||||
If the bank is global, it will become per-server
|
||||
If the bank is per-server, it will become global"""
|
||||
"""Toggle whether the bank is global or not.
|
||||
|
||||
If the bank is global, it will become per-server.
|
||||
If the bank is per-server, it will become global.
|
||||
"""
|
||||
cur_setting = await bank.is_global()
|
||||
|
||||
word = _("per-server") if cur_setting else _("global")
|
||||
if confirm is False:
|
||||
await ctx.send(
|
||||
_(
|
||||
"This will toggle the bank to be {}, deleting all accounts "
|
||||
"in the process! If you're sure, type `{}`"
|
||||
).format(word, "{}bankset toggleglobal yes".format(ctx.prefix))
|
||||
"This will toggle the bank to be {banktype}, deleting all accounts "
|
||||
"in the process! If you're sure, type `{command}`"
|
||||
).format(banktype=word, command="{}bankset toggleglobal yes".format(ctx.prefix))
|
||||
)
|
||||
else:
|
||||
await bank.set_global(not cur_setting)
|
||||
await ctx.send(_("The bank is now {}.").format(word))
|
||||
await ctx.send(_("The bank is now {banktype}.").format(banktype=word))
|
||||
|
||||
@bankset.command(name="bankname")
|
||||
@check_global_setting_guildowner()
|
||||
async def bankset_bankname(self, ctx: commands.Context, *, name: str):
|
||||
"""Set the bank's name"""
|
||||
"""Set the bank's name."""
|
||||
await bank.set_bank_name(name, ctx.guild)
|
||||
await ctx.send(_("Bank's name has been set to {}").format(name))
|
||||
await ctx.send(_("Bank name has been set to: {name}").format(name=name))
|
||||
|
||||
@bankset.command(name="creditsname")
|
||||
@check_global_setting_guildowner()
|
||||
async def bankset_creditsname(self, ctx: commands.Context, *, name: str):
|
||||
"""Set the name for the bank's currency"""
|
||||
"""Set the name for the bank's currency."""
|
||||
await bank.set_currency_name(name, ctx.guild)
|
||||
await ctx.send(_("Currency name has been set to {}").format(name))
|
||||
await ctx.send(_("Currency name has been set to: {name}").format(name=name))
|
||||
|
||||
# ENDSECTION
|
||||
|
||||
@ -16,7 +16,7 @@ _ = Translator("Cleanup", __file__)
|
||||
|
||||
@cog_i18n(_)
|
||||
class Cleanup(commands.Cog):
|
||||
"""Commands for cleaning messages"""
|
||||
"""Commands for cleaning up messages."""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
super().__init__()
|
||||
@ -33,7 +33,7 @@ class Cleanup(commands.Cog):
|
||||
"""
|
||||
|
||||
prompt = await ctx.send(
|
||||
_("Are you sure you want to delete {} messages? (y/n)").format(number)
|
||||
_("Are you sure you want to delete {number} messages? (y/n)").format(number=number)
|
||||
)
|
||||
response = await ctx.bot.wait_for("message", check=MessagePredicate.same_context(ctx))
|
||||
|
||||
@ -41,7 +41,7 @@ class Cleanup(commands.Cog):
|
||||
await prompt.delete()
|
||||
try:
|
||||
await response.delete()
|
||||
except:
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
return True
|
||||
else:
|
||||
@ -104,25 +104,24 @@ class Cleanup(commands.Cog):
|
||||
@commands.group()
|
||||
@checks.mod_or_permissions(manage_messages=True)
|
||||
async def cleanup(self, ctx: commands.Context):
|
||||
"""Deletes messages."""
|
||||
"""Delete messages."""
|
||||
pass
|
||||
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def text(
|
||||
self, ctx: commands.Context, text: str, number: int, delete_pinned: bool = False
|
||||
):
|
||||
"""Deletes last X messages matching the specified text.
|
||||
"""Delete the last X messages matching the specified text.
|
||||
|
||||
Example:
|
||||
cleanup text \"test\" 5
|
||||
`[p]cleanup text "test" 5`
|
||||
|
||||
Remember to use double quotes."""
|
||||
Remember to use double quotes.
|
||||
"""
|
||||
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
|
||||
author = ctx.author
|
||||
|
||||
@ -156,18 +155,17 @@ class Cleanup(commands.Cog):
|
||||
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def user(
|
||||
self, ctx: commands.Context, user: str, number: int, delete_pinned: bool = False
|
||||
):
|
||||
"""Deletes last X messages from specified user.
|
||||
"""Delete the last X messages from a specified user.
|
||||
|
||||
Examples:
|
||||
cleanup user @\u200bTwentysix 2
|
||||
cleanup user Red 6"""
|
||||
`[p]cleanup user @\u200bTwentysix 2`
|
||||
`[p]cleanup user Red 6`
|
||||
"""
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
|
||||
member = None
|
||||
try:
|
||||
@ -213,8 +211,9 @@ class Cleanup(commands.Cog):
|
||||
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool = False):
|
||||
"""Deletes all messages after specified message.
|
||||
"""Delete all messages after a specified message.
|
||||
|
||||
To get a message id, enable developer mode in Discord's
|
||||
settings, 'appearance' tab. Then right click a message
|
||||
@ -222,9 +221,6 @@ class Cleanup(commands.Cog):
|
||||
"""
|
||||
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
author = ctx.author
|
||||
|
||||
try:
|
||||
@ -245,6 +241,7 @@ class Cleanup(commands.Cog):
|
||||
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def before(
|
||||
self, ctx: commands.Context, message_id: int, number: int, delete_pinned: bool = False
|
||||
):
|
||||
@ -256,9 +253,6 @@ class Cleanup(commands.Cog):
|
||||
"""
|
||||
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
author = ctx.author
|
||||
|
||||
try:
|
||||
@ -279,16 +273,15 @@ class Cleanup(commands.Cog):
|
||||
|
||||
@cleanup.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||
"""Deletes last X messages.
|
||||
"""Delete the last X messages.
|
||||
|
||||
Example:
|
||||
cleanup messages 26"""
|
||||
`[p]cleanup messages 26`
|
||||
"""
|
||||
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
author = ctx.author
|
||||
|
||||
if number > 100:
|
||||
@ -310,13 +303,11 @@ class Cleanup(commands.Cog):
|
||||
|
||||
@cleanup.command(name="bot")
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_messages=True)
|
||||
async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||
"""Cleans up command messages and messages from the bot."""
|
||||
"""Clean up command messages and messages from the bot."""
|
||||
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).manage_messages:
|
||||
await ctx.send("I need the Manage Messages permission to do this.")
|
||||
return
|
||||
author = ctx.message.author
|
||||
|
||||
if number > 100:
|
||||
@ -369,7 +360,7 @@ class Cleanup(commands.Cog):
|
||||
match_pattern: str = None,
|
||||
delete_pinned: bool = False,
|
||||
):
|
||||
"""Cleans up messages owned by the bot.
|
||||
"""Clean up messages owned by the bot.
|
||||
|
||||
By default, all messages are cleaned. If a third argument is specified,
|
||||
it is used for pattern matching: If it begins with r( and ends with ),
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import os
|
||||
import re
|
||||
import random
|
||||
from datetime import datetime, timedelta
|
||||
from inspect import Parameter
|
||||
from collections import OrderedDict
|
||||
from typing import Mapping
|
||||
from typing import Mapping, Tuple, Dict
|
||||
|
||||
import discord
|
||||
|
||||
@ -52,11 +51,11 @@ class CommandObj:
|
||||
|
||||
async def get_responses(self, ctx):
|
||||
intro = _(
|
||||
"Welcome to the interactive random {} maker!\n"
|
||||
"Welcome to the interactive random {cc} maker!\n"
|
||||
"Every message you send will be added as one of the random "
|
||||
"responses to choose from once this {} is "
|
||||
"triggered. To exit this interactive menu, type `{}`"
|
||||
).format("customcommand", "customcommand", "exit()")
|
||||
"responses to choose from once this {cc} is "
|
||||
"triggered. To exit this interactive menu, type `{quit}`"
|
||||
).format(cc="customcommand", quit="exit()")
|
||||
await ctx.send(intro)
|
||||
|
||||
responses = []
|
||||
@ -85,7 +84,7 @@ class CommandObj:
|
||||
# in the ccinfo dict
|
||||
return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow())
|
||||
|
||||
async def get(self, message: discord.Message, command: str) -> str:
|
||||
async def get(self, message: discord.Message, command: str) -> Tuple[str, Dict]:
|
||||
ccinfo = await self.db(message.guild).commands.get_raw(command, default=None)
|
||||
if not ccinfo:
|
||||
raise NotFound()
|
||||
@ -180,9 +179,7 @@ class CommandObj:
|
||||
|
||||
@cog_i18n(_)
|
||||
class CustomCommands(commands.Cog):
|
||||
"""Custom commands
|
||||
|
||||
Creates commands used to display text"""
|
||||
"""Creates commands used to display text."""
|
||||
|
||||
def __init__(self, bot):
|
||||
super().__init__()
|
||||
@ -196,61 +193,55 @@ class CustomCommands(commands.Cog):
|
||||
@commands.group(aliases=["cc"])
|
||||
@commands.guild_only()
|
||||
async def customcom(self, ctx: commands.Context):
|
||||
"""Custom commands management"""
|
||||
"""Custom commands management."""
|
||||
pass
|
||||
|
||||
@customcom.group(name="add")
|
||||
@customcom.group(name="create", aliases=["add"])
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def cc_add(self, ctx: commands.Context):
|
||||
"""
|
||||
Adds a new custom command
|
||||
async def cc_create(self, ctx: commands.Context):
|
||||
"""Create custom commands.
|
||||
|
||||
CCs can be enhanced with arguments:
|
||||
https://red-discordbot.readthedocs.io/en/v3-develop/cog_customcom.html
|
||||
CCs can be enhanced with arguments, see the guide
|
||||
[here](https://red-discordbot.readthedocs.io/en/v3-develop/cog_customcom.html).
|
||||
"""
|
||||
pass
|
||||
|
||||
@cc_add.command(name="random")
|
||||
@cc_create.command(name="random")
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def cc_add_random(self, ctx: commands.Context, command: str.lower):
|
||||
"""
|
||||
Create a CC where it will randomly choose a response!
|
||||
async def cc_create_random(self, ctx: commands.Context, command: str.lower):
|
||||
"""Create a CC where it will randomly choose a response!
|
||||
|
||||
Note: This is interactive
|
||||
Note: This command is interactive.
|
||||
"""
|
||||
responses = []
|
||||
|
||||
responses = await self.commandobj.get_responses(ctx=ctx)
|
||||
try:
|
||||
await self.commandobj.create(ctx=ctx, command=command, response=responses)
|
||||
await ctx.send(_("Custom command successfully added."))
|
||||
except AlreadyExists:
|
||||
await ctx.send(
|
||||
_("This command already exists. Use `{}` to edit it.").format(
|
||||
"{}customcom edit".format(ctx.prefix)
|
||||
_("This command already exists. Use `{command}` to edit it.").format(
|
||||
command="{}customcom edit".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
|
||||
# await ctx.send(str(responses))
|
||||
|
||||
@cc_add.command(name="simple")
|
||||
@cc_create.command(name="simple")
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def cc_add_simple(self, ctx, command: str.lower, *, text: str):
|
||||
"""Adds a simple custom command
|
||||
async def cc_create_simple(self, ctx, command: str.lower, *, text: str):
|
||||
"""Add a simple custom command.
|
||||
|
||||
Example:
|
||||
[p]customcom add simple yourcommand Text you want
|
||||
- `[p]customcom create simple yourcommand Text you want`
|
||||
"""
|
||||
if command in self.bot.all_commands:
|
||||
await ctx.send(_("That command is already a standard command."))
|
||||
await ctx.send(_("There already exists a bot command with the same name."))
|
||||
return
|
||||
try:
|
||||
await self.commandobj.create(ctx=ctx, command=command, response=text)
|
||||
await ctx.send(_("Custom command successfully added."))
|
||||
except AlreadyExists:
|
||||
await ctx.send(
|
||||
_("This command already exists. Use `{}` to edit it.").format(
|
||||
"{}customcom edit".format(ctx.prefix)
|
||||
_("This command already exists. Use `{command}` to edit it.").format(
|
||||
command="{}customcom edit".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
except ArgParseError as e:
|
||||
@ -261,13 +252,14 @@ class CustomCommands(commands.Cog):
|
||||
async def cc_cooldown(
|
||||
self, ctx, command: str.lower, cooldown: int = None, *, per: str.lower = "member"
|
||||
):
|
||||
"""
|
||||
Sets, edits, or views cooldowns for a custom command
|
||||
"""Set, edit, or view the cooldown for a custom command.
|
||||
|
||||
You may set cooldowns per member, channel, or guild. Multiple
|
||||
cooldowns may be set. All cooldowns must be cooled to call the
|
||||
custom command.
|
||||
|
||||
You may set cooldowns per member, channel, or guild.
|
||||
Multiple cooldowns may be set. All cooldowns must be cooled to call the custom command.
|
||||
Example:
|
||||
[p]customcom cooldown yourcommand 30
|
||||
- `[p]customcom cooldown yourcommand 30`
|
||||
"""
|
||||
if cooldown is None:
|
||||
try:
|
||||
@ -293,18 +285,19 @@ class CustomCommands(commands.Cog):
|
||||
await ctx.send(_("Custom command cooldown successfully edited."))
|
||||
except NotFound:
|
||||
await ctx.send(
|
||||
_("That command doesn't exist. Use `{}` to add it.").format(
|
||||
"{}customcom add".format(ctx.prefix)
|
||||
_("That command doesn't exist. Use `{command}` to add it.").format(
|
||||
command="{}customcom create".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
|
||||
@customcom.command(name="delete")
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def cc_delete(self, ctx, command: str.lower):
|
||||
"""Deletes a custom command
|
||||
|
||||
"""Delete a custom command
|
||||
.
|
||||
Example:
|
||||
[p]customcom delete yourcommand"""
|
||||
- `[p]customcom delete yourcommand`
|
||||
"""
|
||||
try:
|
||||
await self.commandobj.delete(ctx=ctx, command=command)
|
||||
await ctx.send(_("Custom command successfully deleted."))
|
||||
@ -314,18 +307,20 @@ class CustomCommands(commands.Cog):
|
||||
@customcom.command(name="edit")
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def cc_edit(self, ctx, command: str.lower, *, text: str = None):
|
||||
"""Edits a custom command's response
|
||||
"""Edit a custom command.
|
||||
|
||||
Example:
|
||||
[p]customcom edit yourcommand Text you want
|
||||
- `[p]customcom edit yourcommand Text you want`
|
||||
"""
|
||||
command = command.lower()
|
||||
|
||||
try:
|
||||
await self.commandobj.edit(ctx=ctx, command=command, response=text)
|
||||
await ctx.send(_("Custom command successfully edited."))
|
||||
except NotFound:
|
||||
await ctx.send(
|
||||
_("That command doesn't exist. Use `{}` to add it.").format(
|
||||
"{}customcom add".format(ctx.prefix)
|
||||
_("That command doesn't exist. Use `{command}` to add it.").format(
|
||||
command="{}customcom create".format(ctx.prefix)
|
||||
)
|
||||
)
|
||||
except ArgParseError as e:
|
||||
@ -333,7 +328,7 @@ class CustomCommands(commands.Cog):
|
||||
|
||||
@customcom.command(name="list")
|
||||
async def cc_list(self, ctx):
|
||||
"""Shows custom commands list"""
|
||||
"""List all available custom commands."""
|
||||
|
||||
response = await CommandObj.get_commands(self.config.guild(ctx.guild))
|
||||
|
||||
@ -341,8 +336,8 @@ class CustomCommands(commands.Cog):
|
||||
await ctx.send(
|
||||
_(
|
||||
"There are no custom commands in this server."
|
||||
" Use `{}` to start adding some."
|
||||
).format("{}customcom add".format(ctx.prefix))
|
||||
" Use `{command}` to start adding some."
|
||||
).format(command="{}customcom create".format(ctx.prefix))
|
||||
)
|
||||
return
|
||||
|
||||
@ -454,9 +449,8 @@ class CustomCommands(commands.Cog):
|
||||
gaps = set(indices).symmetric_difference(range(high + 1))
|
||||
if gaps:
|
||||
raise ArgParseError(
|
||||
_("Arguments must be sequential. Missing arguments: {}.").format(
|
||||
", ".join(str(i + low) for i in gaps)
|
||||
)
|
||||
_("Arguments must be sequential. Missing arguments: ")
|
||||
+ ", ".join(str(i + low) for i in gaps)
|
||||
)
|
||||
fin = [Parameter("_" + str(i), Parameter.POSITIONAL_OR_KEYWORD) for i in range(high + 1)]
|
||||
for arg in args:
|
||||
@ -481,8 +475,12 @@ class CustomCommands(commands.Cog):
|
||||
and anno != fin[index].annotation
|
||||
):
|
||||
raise ArgParseError(
|
||||
_('Conflicting colon notation for argument {}: "{}" and "{}".').format(
|
||||
index + low, fin[index].annotation.__name__, anno.__name__
|
||||
_(
|
||||
'Conflicting colon notation for argument {index}: "{name1}" and "{name2}".'
|
||||
).format(
|
||||
index=index + low,
|
||||
name1=fin[index].annotation.__name__,
|
||||
name2=anno.__name__,
|
||||
)
|
||||
)
|
||||
if anno is not Parameter.empty:
|
||||
@ -511,6 +509,8 @@ class CustomCommands(commands.Cog):
|
||||
key = (command, ctx.guild, ctx.channel)
|
||||
elif per == "member":
|
||||
key = (command, ctx.guild, ctx.author)
|
||||
else:
|
||||
raise ValueError(per)
|
||||
cooldown = self.cooldowns.get(key)
|
||||
if cooldown:
|
||||
cooldown += timedelta(seconds=rate)
|
||||
|
||||
@ -13,9 +13,7 @@ _ = Translator("DataConverter", __file__)
|
||||
|
||||
@cog_i18n(_)
|
||||
class DataConverter(commands.Cog):
|
||||
"""
|
||||
Cog for importing Red v2 Data
|
||||
"""
|
||||
"""Import Red V2 data to your V3 instance."""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
super().__init__()
|
||||
@ -24,13 +22,10 @@ class DataConverter(commands.Cog):
|
||||
@checks.is_owner()
|
||||
@commands.command(name="convertdata")
|
||||
async def dataconversioncommand(self, ctx: commands.Context, v2path: str):
|
||||
"""
|
||||
Interactive prompt for importing data from Red v2
|
||||
"""Interactive prompt for importing data from Red V2.
|
||||
|
||||
Takes the path where the v2 install is
|
||||
|
||||
Overwrites values which have entries in both v2 and v3,
|
||||
use with caution.
|
||||
Takes the path where the V2 install is, and overwrites
|
||||
values which have entries in both V2 and v3; use with caution.
|
||||
"""
|
||||
resolver = SpecResolver(Path(v2path.strip()))
|
||||
|
||||
@ -54,7 +49,7 @@ class DataConverter(commands.Cog):
|
||||
"message", check=MessagePredicate.same_context(ctx), timeout=60
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
return await ctx.send(_("Try this again when you are more ready"))
|
||||
return await ctx.send(_("Try this again when you are ready."))
|
||||
else:
|
||||
if message.content.strip().lower() in ["quit", "exit", "-1", "q", "cancel"]:
|
||||
return await ctx.tick()
|
||||
@ -72,7 +67,7 @@ class DataConverter(commands.Cog):
|
||||
else:
|
||||
return await ctx.send(
|
||||
_(
|
||||
"There isn't anything else I know how to convert here."
|
||||
"\nThere might be more things I can convert in the future."
|
||||
"There isn't anything else I know how to convert here.\n"
|
||||
"There might be more things I can convert in the future."
|
||||
)
|
||||
)
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
import asyncio
|
||||
|
||||
from redbot.core import commands
|
||||
from redbot.core.i18n import Translator
|
||||
from redbot.core.utils.predicates import MessagePredicate
|
||||
|
||||
__all__ = ["do_install_agreement"]
|
||||
|
||||
REPO_INSTALL_MSG = (
|
||||
T_ = Translator("DownloaderChecks", __file__)
|
||||
|
||||
_ = lambda s: s
|
||||
REPO_INSTALL_MSG = _(
|
||||
"You're about to add a 3rd party repository. The creator of Red"
|
||||
" and its community have no responsibility for any potential "
|
||||
"damage that the content of 3rd party repositories might cause."
|
||||
@ -14,6 +18,7 @@ REPO_INSTALL_MSG = (
|
||||
"shown again until the next reboot.\n\nYou have **30** seconds"
|
||||
" to reply to this message."
|
||||
)
|
||||
_ = T_
|
||||
|
||||
|
||||
async def do_install_agreement(ctx: commands.Context):
|
||||
@ -21,14 +26,14 @@ async def do_install_agreement(ctx: commands.Context):
|
||||
if downloader is None or downloader.already_agreed:
|
||||
return True
|
||||
|
||||
await ctx.send(REPO_INSTALL_MSG)
|
||||
await ctx.send(T_(REPO_INSTALL_MSG))
|
||||
|
||||
try:
|
||||
await ctx.bot.wait_for(
|
||||
"message", check=MessagePredicate.lower_equal_to("i agree", ctx), timeout=30
|
||||
)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send("Your response has timed out, please try again.")
|
||||
await ctx.send(_("Your response has timed out, please try again."))
|
||||
return False
|
||||
|
||||
downloader.already_agreed = True
|
||||
|
||||
@ -8,10 +8,10 @@ class InstalledCog(Installable):
|
||||
async def convert(cls, ctx: commands.Context, arg: str) -> Installable:
|
||||
downloader = ctx.bot.get_cog("Downloader")
|
||||
if downloader is None:
|
||||
raise commands.CommandError("Downloader not loaded.")
|
||||
raise commands.CommandError(_("No Downloader cog found."))
|
||||
|
||||
cog = discord.utils.get(await downloader.installed_cogs(), name=arg)
|
||||
if cog is None:
|
||||
raise commands.BadArgument("That cog is not installed")
|
||||
raise commands.BadArgument(_("That cog is not installed"))
|
||||
|
||||
return cog
|
||||
|
||||
@ -193,9 +193,7 @@ class Downloader(commands.Cog):
|
||||
@commands.command()
|
||||
@checks.is_owner()
|
||||
async def pipinstall(self, ctx, *deps: str):
|
||||
"""
|
||||
Installs a group of dependencies using pip.
|
||||
"""
|
||||
"""Install a group of dependencies using pip."""
|
||||
repo = Repo("", "", "", Path.cwd(), loop=ctx.bot.loop)
|
||||
success = await repo.install_raw_requirements(deps, self.LIB_PATH)
|
||||
|
||||
@ -212,18 +210,15 @@ class Downloader(commands.Cog):
|
||||
@commands.group()
|
||||
@checks.is_owner()
|
||||
async def repo(self, ctx):
|
||||
"""
|
||||
Command group for managing Downloader repos.
|
||||
"""
|
||||
"""Repo management commands."""
|
||||
pass
|
||||
|
||||
@repo.command(name="add")
|
||||
async def _repo_add(self, ctx, name: str, repo_url: str, branch: str = None):
|
||||
"""
|
||||
Add a new repo to Downloader.
|
||||
"""Add a new repo.
|
||||
|
||||
Name can only contain characters A-z, numbers and underscore
|
||||
Branch will default to master if not specified
|
||||
The name can only contain characters A-z, numbers and underscores.
|
||||
The branch will be the default branch if not specified.
|
||||
"""
|
||||
agreed = await do_install_agreement(ctx)
|
||||
if not agreed:
|
||||
@ -242,24 +237,22 @@ class Downloader(commands.Cog):
|
||||
exc_info=err,
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("Repo `{}` successfully added.").format(name))
|
||||
await ctx.send(_("Repo `{name}` successfully added.").format(name=name))
|
||||
if repo.install_msg is not None:
|
||||
await ctx.send(repo.install_msg.replace("[p]", ctx.prefix))
|
||||
|
||||
@repo.command(name="delete")
|
||||
async def _repo_del(self, ctx, repo_name: Repo):
|
||||
"""
|
||||
Removes a repo from Downloader and its' files.
|
||||
"""
|
||||
await self._repo_manager.delete_repo(repo_name.name)
|
||||
@repo.command(name="delete", aliases=["remove"], usage="<repo_name>")
|
||||
async def _repo_del(self, ctx, repo: Repo):
|
||||
"""Remove a repo and its files."""
|
||||
await self._repo_manager.delete_repo(repo.name)
|
||||
|
||||
await ctx.send(_("The repo `{}` has been deleted successfully.").format(repo_name.name))
|
||||
await ctx.send(
|
||||
_("The repo `{repo.name}` has been deleted successfully.").format(repo=repo)
|
||||
)
|
||||
|
||||
@repo.command(name="list")
|
||||
async def _repo_list(self, ctx):
|
||||
"""
|
||||
Lists all installed repos.
|
||||
"""
|
||||
"""List all installed repos."""
|
||||
repos = self._repo_manager.get_all_repo_names()
|
||||
repos = sorted(repos, key=str.lower)
|
||||
joined = _("Installed Repos:\n\n")
|
||||
@ -270,94 +263,93 @@ class Downloader(commands.Cog):
|
||||
for page in pagify(joined, ["\n"], shorten_by=16):
|
||||
await ctx.send(box(page.lstrip(" "), lang="diff"))
|
||||
|
||||
@repo.command(name="info")
|
||||
async def _repo_info(self, ctx, repo_name: Repo):
|
||||
"""
|
||||
Lists information about a single repo
|
||||
"""
|
||||
if repo_name is None:
|
||||
await ctx.send(_("There is no repo `{}`").format(repo_name.name))
|
||||
@repo.command(name="info", usage="<repo_name>")
|
||||
async def _repo_info(self, ctx, repo: Repo):
|
||||
"""Show information about a repo."""
|
||||
if repo is None:
|
||||
await ctx.send(_("Repo `{repo.name}` not found.").format(repo=repo))
|
||||
return
|
||||
|
||||
msg = _("Information on {}:\n{}").format(repo_name.name, repo_name.description or "")
|
||||
msg = _("Information on {repo.name}:\n{description}").format(
|
||||
repo=repo, description=repo.description or ""
|
||||
)
|
||||
await ctx.send(box(msg))
|
||||
|
||||
@commands.group()
|
||||
@checks.is_owner()
|
||||
async def cog(self, ctx):
|
||||
"""
|
||||
Command group for managing installable Cogs.
|
||||
"""
|
||||
"""Cog installation management commands."""
|
||||
pass
|
||||
|
||||
@cog.command(name="install")
|
||||
async def _cog_install(self, ctx, repo_name: Repo, cog_name: str):
|
||||
"""
|
||||
Installs a cog from the given repo.
|
||||
"""
|
||||
cog = discord.utils.get(repo_name.available_cogs, name=cog_name) # type: Installable
|
||||
@cog.command(name="install", usage="<repo_name> <cog_name>")
|
||||
async def _cog_install(self, ctx, repo: Repo, cog_name: str):
|
||||
"""Install a cog from the given repo."""
|
||||
cog: Installable = discord.utils.get(repo.available_cogs, name=cog_name)
|
||||
if cog is None:
|
||||
await ctx.send(
|
||||
_("Error, there is no cog by the name of `{}` in the `{}` repo.").format(
|
||||
cog_name, repo_name.name
|
||||
)
|
||||
_(
|
||||
"Error: there is no cog by the name of `{cog_name}` in the `{repo.name}` repo."
|
||||
).format(cog_name=cog_name, repo=repo)
|
||||
)
|
||||
return
|
||||
elif cog.min_python_version > sys.version_info:
|
||||
await ctx.send(
|
||||
_("This cog requires at least python version {}, aborting install.").format(
|
||||
".".join([str(n) for n in cog.min_python_version])
|
||||
_("This cog requires at least python version {version}, aborting install.").format(
|
||||
version=".".join([str(n) for n in cog.min_python_version])
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if not await repo_name.install_requirements(cog, self.LIB_PATH):
|
||||
if not await repo.install_requirements(cog, self.LIB_PATH):
|
||||
await ctx.send(
|
||||
_("Failed to install the required libraries for `{}`: `{}`").format(
|
||||
cog.name, cog.requirements
|
||||
)
|
||||
_(
|
||||
"Failed to install the required libraries for `{cog_name}`: `{libraries}`"
|
||||
).format(cog_name=cog.name, libraries=cog.requirements)
|
||||
)
|
||||
return
|
||||
|
||||
await repo_name.install_cog(cog, await self.cog_install_path())
|
||||
await repo.install_cog(cog, await self.cog_install_path())
|
||||
|
||||
await self._add_to_installed(cog)
|
||||
|
||||
await repo_name.install_libraries(self.SHAREDLIB_PATH)
|
||||
await repo.install_libraries(self.SHAREDLIB_PATH)
|
||||
|
||||
await ctx.send(_("`{}` cog successfully installed.").format(cog_name))
|
||||
await ctx.send(_("Cog `{cog_name}` successfully installed.").format(cog_name=cog_name))
|
||||
if cog.install_msg is not None:
|
||||
await ctx.send(cog.install_msg.replace("[p]", ctx.prefix))
|
||||
|
||||
@cog.command(name="uninstall")
|
||||
async def _cog_uninstall(self, ctx, cog_name: InstalledCog):
|
||||
"""
|
||||
Allows you to uninstall cogs that were previously installed
|
||||
through Downloader.
|
||||
@cog.command(name="uninstall", usage="<cog_name>")
|
||||
async def _cog_uninstall(self, ctx, cog: InstalledCog):
|
||||
"""Uninstall a cog.
|
||||
|
||||
You may only uninstall cogs which were previously installed
|
||||
by Downloader.
|
||||
"""
|
||||
# noinspection PyUnresolvedReferences,PyProtectedMember
|
||||
real_name = cog_name.name
|
||||
real_name = cog.name
|
||||
|
||||
poss_installed_path = (await self.cog_install_path()) / real_name
|
||||
if poss_installed_path.exists():
|
||||
await self._delete_cog(poss_installed_path)
|
||||
# noinspection PyTypeChecker
|
||||
await self._remove_from_installed(cog_name)
|
||||
await ctx.send(_("`{}` was successfully removed.").format(real_name))
|
||||
await self._remove_from_installed(cog)
|
||||
await ctx.send(
|
||||
_("Cog `{cog_name}` was successfully uninstalled.").format(cog_name=real_name)
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
_(
|
||||
"That cog was installed but can no longer"
|
||||
" be located. You may need to remove it's"
|
||||
" files manually if it is still usable."
|
||||
)
|
||||
" Also make sure you've unloaded the cog"
|
||||
" with `{prefix}unload {cog_name}`."
|
||||
).format(cog_name=real_name)
|
||||
)
|
||||
|
||||
@cog.command(name="update")
|
||||
async def _cog_update(self, ctx, cog_name: InstalledCog = None):
|
||||
"""
|
||||
Updates all cogs or one of your choosing.
|
||||
"""
|
||||
"""Update all cogs, or one of your choosing."""
|
||||
installed_cogs = set(await self.installed_cogs())
|
||||
|
||||
async with ctx.typing():
|
||||
@ -418,11 +410,9 @@ class Downloader(commands.Cog):
|
||||
else:
|
||||
await ctx.send(_("OK then."))
|
||||
|
||||
@cog.command(name="list")
|
||||
async def _cog_list(self, ctx, repo_name: Repo):
|
||||
"""
|
||||
Lists all available cogs from a single repo.
|
||||
"""
|
||||
@cog.command(name="list", usage="<repo_name>")
|
||||
async def _cog_list(self, ctx, repo: Repo):
|
||||
"""List all available cogs from a single repo."""
|
||||
installed = await self.installed_cogs()
|
||||
installed_str = ""
|
||||
if installed:
|
||||
@ -430,10 +420,10 @@ class Downloader(commands.Cog):
|
||||
[
|
||||
"- {}{}".format(i.name, ": {}".format(i.short) if i.short else "")
|
||||
for i in installed
|
||||
if i.repo_name == repo_name.name
|
||||
if i.repo_name == repo.name
|
||||
]
|
||||
)
|
||||
cogs = repo_name.available_cogs
|
||||
cogs = repo.available_cogs
|
||||
cogs = _("Available Cogs:\n") + "\n".join(
|
||||
[
|
||||
"+ {}: {}".format(c.name, c.short or "")
|
||||
@ -445,20 +435,24 @@ class Downloader(commands.Cog):
|
||||
for page in pagify(cogs, ["\n"], shorten_by=16):
|
||||
await ctx.send(box(page.lstrip(" "), lang="diff"))
|
||||
|
||||
@cog.command(name="info")
|
||||
async def _cog_info(self, ctx, repo_name: Repo, cog_name: str):
|
||||
"""
|
||||
Lists information about a single cog.
|
||||
"""
|
||||
cog = discord.utils.get(repo_name.available_cogs, name=cog_name)
|
||||
@cog.command(name="info", usage="<repo_name> <cog_name>")
|
||||
async def _cog_info(self, ctx, repo: Repo, cog_name: str):
|
||||
"""List information about a single cog."""
|
||||
cog = discord.utils.get(repo.available_cogs, name=cog_name)
|
||||
if cog is None:
|
||||
await ctx.send(
|
||||
_("There is no cog `{}` in the repo `{}`").format(cog_name, repo_name.name)
|
||||
_("There is no cog `{cog_name}` in the repo `{repo.name}`").format(
|
||||
cog_name=cog_name, repo=repo
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
msg = _("Information on {}:\n{}\n\nRequirements: {}").format(
|
||||
cog.name, cog.description or "", ", ".join(cog.requirements) or "None"
|
||||
msg = _(
|
||||
"Information on {cog_name}:\n{description}\n\nRequirements: {requirements}"
|
||||
).format(
|
||||
cog_name=cog.name,
|
||||
description=cog.description or "",
|
||||
requirements=", ".join(cog.requirements) or "None",
|
||||
)
|
||||
await ctx.send(box(msg))
|
||||
|
||||
@ -512,9 +506,9 @@ class Downloader(commands.Cog):
|
||||
repo_url = "https://github.com/Cog-Creators/Red-DiscordBot"
|
||||
cog_name = cog_installable.__class__.__name__
|
||||
|
||||
msg = _("Command: {}\nMade by: {}\nRepo: {}\nCog name: {}")
|
||||
msg = _("Command: {command}\nMade by: {author}\nRepo: {repo}\nCog name: {cog}")
|
||||
|
||||
return msg.format(command_name, made_by, repo_url, cog_name)
|
||||
return msg.format(command=command_name, author=made_by, repo=repo_url, cog=cog_name)
|
||||
|
||||
def cog_name_from_instance(self, instance: object) -> str:
|
||||
"""Determines the cog name that Downloader knows from the cog instance.
|
||||
@ -537,9 +531,9 @@ class Downloader(commands.Cog):
|
||||
|
||||
@commands.command()
|
||||
async def findcog(self, ctx: commands.Context, command_name: str):
|
||||
"""
|
||||
Figures out which cog a command comes from. Only works with loaded
|
||||
cogs.
|
||||
"""Find which cog a command comes from.
|
||||
|
||||
This will only work with loaded cogs.
|
||||
"""
|
||||
command = ctx.bot.all_commands.get(command_name)
|
||||
|
||||
|
||||
@ -12,11 +12,15 @@ from typing import Tuple, MutableMapping, Union, Optional
|
||||
|
||||
from redbot.core import data_manager, commands
|
||||
from redbot.core.utils import safe_delete
|
||||
from redbot.core.i18n import Translator
|
||||
|
||||
from . import errors
|
||||
from .installable import Installable, InstallableType
|
||||
from .json_mixins import RepoJSONMixin
|
||||
from .log import log
|
||||
|
||||
_ = Translator("RepoManager", __file__)
|
||||
|
||||
|
||||
class Repo(RepoJSONMixin):
|
||||
GIT_CLONE = "git clone --recurse-submodules -b {branch} {url} {folder}"
|
||||
@ -64,13 +68,15 @@ class Repo(RepoJSONMixin):
|
||||
async def convert(cls, ctx: commands.Context, argument: str):
|
||||
downloader_cog = ctx.bot.get_cog("Downloader")
|
||||
if downloader_cog is None:
|
||||
raise commands.CommandError("No Downloader cog found.")
|
||||
raise commands.CommandError(_("No Downloader cog found."))
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
repo_manager = downloader_cog._repo_manager
|
||||
poss_repo = repo_manager.get_repo(argument)
|
||||
if poss_repo is None:
|
||||
raise commands.BadArgument("Repo by the name {} does not exist.".format(argument))
|
||||
raise commands.BadArgument(
|
||||
_('Repo by the name "{repo_name}" does not exist.').format(repo_name=argument)
|
||||
)
|
||||
return poss_repo
|
||||
|
||||
def _existing_git_repo(self) -> (bool, Path):
|
||||
|
||||
@ -3,6 +3,7 @@ import logging
|
||||
import random
|
||||
from collections import defaultdict, deque
|
||||
from enum import Enum
|
||||
from typing import cast, Iterable
|
||||
|
||||
import discord
|
||||
|
||||
@ -14,7 +15,7 @@ from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||
|
||||
from redbot.core.bot import Red
|
||||
|
||||
_ = Translator("Economy", __file__)
|
||||
T_ = Translator("Economy", __file__)
|
||||
|
||||
logger = logging.getLogger("red.economy")
|
||||
|
||||
@ -34,6 +35,7 @@ class SMReel(Enum):
|
||||
snowflake = "\N{SNOWFLAKE}"
|
||||
|
||||
|
||||
_ = lambda s: s
|
||||
PAYOUTS = {
|
||||
(SMReel.two, SMReel.two, SMReel.six): {
|
||||
"payout": lambda x: x * 2500 + x,
|
||||
@ -72,6 +74,7 @@ SLOT_PAYOUTS_MSG = _(
|
||||
"Three symbols: +500\n"
|
||||
"Two symbols: Bet * 2"
|
||||
).format(**SMReel.__dict__)
|
||||
_ = T_
|
||||
|
||||
|
||||
def guild_only_check():
|
||||
@ -106,9 +109,7 @@ class SetParser:
|
||||
|
||||
@cog_i18n(_)
|
||||
class Economy(commands.Cog):
|
||||
"""Economy
|
||||
|
||||
Get rich and have fun with imaginary currency!"""
|
||||
"""Get rich and have fun with imaginary currency!"""
|
||||
|
||||
default_guild_settings = {
|
||||
"PAYDAY_TIME": 300,
|
||||
@ -142,12 +143,12 @@ class Economy(commands.Cog):
|
||||
@guild_only_check()
|
||||
@commands.group(name="bank")
|
||||
async def _bank(self, ctx: commands.Context):
|
||||
"""Bank operations"""
|
||||
"""Manage the bank."""
|
||||
pass
|
||||
|
||||
@_bank.command()
|
||||
async def balance(self, ctx: commands.Context, user: discord.Member = None):
|
||||
"""Shows balance of user.
|
||||
"""Show the user's account balance.
|
||||
|
||||
Defaults to yours."""
|
||||
if user is None:
|
||||
@ -156,11 +157,15 @@ class Economy(commands.Cog):
|
||||
bal = await bank.get_balance(user)
|
||||
currency = await bank.get_currency_name(ctx.guild)
|
||||
|
||||
await ctx.send(_("{}'s balance is {} {}").format(user.display_name, bal, currency))
|
||||
await ctx.send(
|
||||
_("{user}'s balance is {num} {currency}").format(
|
||||
user=user.display_name, num=bal, currency=currency
|
||||
)
|
||||
)
|
||||
|
||||
@_bank.command()
|
||||
async def transfer(self, ctx: commands.Context, to: discord.Member, amount: int):
|
||||
"""Transfer currency to other users"""
|
||||
"""Transfer currency to other users."""
|
||||
from_ = ctx.author
|
||||
currency = await bank.get_currency_name(ctx.guild)
|
||||
|
||||
@ -170,72 +175,83 @@ class Economy(commands.Cog):
|
||||
return await ctx.send(str(e))
|
||||
|
||||
await ctx.send(
|
||||
_("{} transferred {} {} to {}").format(
|
||||
from_.display_name, amount, currency, to.display_name
|
||||
_("{user} transferred {num} {currency} to {other_user}").format(
|
||||
user=from_.display_name, num=amount, currency=currency, other_user=to.display_name
|
||||
)
|
||||
)
|
||||
|
||||
@_bank.command(name="set")
|
||||
@check_global_setting_admin()
|
||||
async def _set(self, ctx: commands.Context, to: discord.Member, creds: SetParser):
|
||||
"""Sets balance of user's bank account. See help for more operations
|
||||
"""Set the balance of user's bank account.
|
||||
|
||||
Passing positive and negative values will add/remove currency instead
|
||||
Passing positive and negative values will add/remove currency instead.
|
||||
|
||||
Examples:
|
||||
bank set @Twentysix 26 - Sets balance to 26
|
||||
bank set @Twentysix +2 - Increases balance by 2
|
||||
bank set @Twentysix -6 - Decreases balance by 6"""
|
||||
- `[p]bank set @Twentysix 26` - Sets balance to 26
|
||||
- `[p]bank set @Twentysix +2` - Increases balance by 2
|
||||
- `[p]bank set @Twentysix -6` - Decreases balance by 6
|
||||
"""
|
||||
author = ctx.author
|
||||
currency = await bank.get_currency_name(ctx.guild)
|
||||
|
||||
if creds.operation == "deposit":
|
||||
await bank.deposit_credits(to, creds.sum)
|
||||
await ctx.send(
|
||||
_("{} added {} {} to {}'s account.").format(
|
||||
author.display_name, creds.sum, currency, to.display_name
|
||||
_("{author} added {num} {currency} to {user}'s account.").format(
|
||||
author=author.display_name,
|
||||
num=creds.sum,
|
||||
currency=currency,
|
||||
user=to.display_name,
|
||||
)
|
||||
)
|
||||
elif creds.operation == "withdraw":
|
||||
await bank.withdraw_credits(to, creds.sum)
|
||||
await ctx.send(
|
||||
_("{} removed {} {} from {}'s account.").format(
|
||||
author.display_name, creds.sum, currency, to.display_name
|
||||
_("{author} removed {num} {currency} from {user}'s account.").format(
|
||||
author=author.display_name,
|
||||
num=creds.sum,
|
||||
currency=currency,
|
||||
user=to.display_name,
|
||||
)
|
||||
)
|
||||
else:
|
||||
await bank.set_balance(to, creds.sum)
|
||||
await ctx.send(
|
||||
_("{} set {}'s account to {} {}.").format(
|
||||
author.display_name, to.display_name, creds.sum, currency
|
||||
_("{author} set {users}'s account balance to {num} {currency}.").format(
|
||||
author=author.display_name,
|
||||
num=creds.sum,
|
||||
currency=currency,
|
||||
user=to.display_name,
|
||||
)
|
||||
)
|
||||
|
||||
@_bank.command()
|
||||
@check_global_setting_guildowner()
|
||||
async def reset(self, ctx, confirmation: bool = False):
|
||||
"""Deletes bank accounts"""
|
||||
"""Delete all bank accounts."""
|
||||
if confirmation is False:
|
||||
await ctx.send(
|
||||
_(
|
||||
"This will delete all bank accounts for {}.\nIf you're sure, type "
|
||||
"`{}bank reset yes`"
|
||||
"This will delete all bank accounts for {scope}.\nIf you're sure, type "
|
||||
"`{prefix}bank reset yes`"
|
||||
).format(
|
||||
self.bot.user.name if await bank.is_global() else "this server", ctx.prefix
|
||||
scope=self.bot.user.name if await bank.is_global() else _("this server"),
|
||||
prefix=ctx.prefix,
|
||||
)
|
||||
)
|
||||
else:
|
||||
await bank.wipe_bank()
|
||||
await bank.wipe_bank(guild=ctx.guild)
|
||||
await ctx.send(
|
||||
_("All bank accounts for {} have been deleted.").format(
|
||||
self.bot.user.name if await bank.is_global() else "this server"
|
||||
_("All bank accounts for {scope} have been deleted.").format(
|
||||
scope=self.bot.user.name if await bank.is_global() else _("this server")
|
||||
)
|
||||
)
|
||||
|
||||
@guild_only_check()
|
||||
@commands.command()
|
||||
async def payday(self, ctx: commands.Context):
|
||||
"""Get some free currency"""
|
||||
"""Get some free currency."""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
@ -251,24 +267,25 @@ class Economy(commands.Cog):
|
||||
pos = await bank.get_leaderboard_position(author)
|
||||
await ctx.send(
|
||||
_(
|
||||
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
||||
"You currently have {3} {1}.\n\n"
|
||||
"You are currently #{4} on the global leaderboard!"
|
||||
"{author.mention} Here, take some {currency}. "
|
||||
"Enjoy! (+{amount} {new_balance}!)\n\n"
|
||||
"You currently have {new_balance} {currency}.\n\n"
|
||||
"You are currently #{pos} on the global leaderboard!"
|
||||
).format(
|
||||
author,
|
||||
credits_name,
|
||||
str(await self.config.PAYDAY_CREDITS()),
|
||||
str(await bank.get_balance(author)),
|
||||
pos,
|
||||
author=author,
|
||||
currency=credits_name,
|
||||
amount=await self.config.PAYDAY_CREDITS(),
|
||||
new_balance=await bank.get_balance(author),
|
||||
pos=pos,
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
dtime = self.display_time(next_payday - cur_time)
|
||||
await ctx.send(
|
||||
_("{} Too soon. For your next payday you have to wait {}.").format(
|
||||
author.mention, dtime
|
||||
)
|
||||
_(
|
||||
"{author.mention} Too soon. For your next payday you have to wait {time}."
|
||||
).format(author=author, time=dtime)
|
||||
)
|
||||
else:
|
||||
next_payday = await self.config.member(author).next_payday()
|
||||
@ -286,31 +303,33 @@ class Economy(commands.Cog):
|
||||
pos = await bank.get_leaderboard_position(author)
|
||||
await ctx.send(
|
||||
_(
|
||||
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
||||
"You currently have {3} {1}.\n\n"
|
||||
"You are currently #{4} on the leaderboard!"
|
||||
"{author.mention} Here, take some {currency}. "
|
||||
"Enjoy! (+{amount} {new_balance}!)\n\n"
|
||||
"You currently have {new_balance} {currency}.\n\n"
|
||||
"You are currently #{pos} on the global leaderboard!"
|
||||
).format(
|
||||
author,
|
||||
credits_name,
|
||||
credit_amount,
|
||||
str(await bank.get_balance(author)),
|
||||
pos,
|
||||
author=author,
|
||||
currency=credits_name,
|
||||
amount=credit_amount,
|
||||
new_balance=await bank.get_balance(author),
|
||||
pos=pos,
|
||||
)
|
||||
)
|
||||
else:
|
||||
dtime = self.display_time(next_payday - cur_time)
|
||||
await ctx.send(
|
||||
_("{} Too soon. For your next payday you have to wait {}.").format(
|
||||
author.mention, dtime
|
||||
)
|
||||
_(
|
||||
"{author.mention} Too soon. For your next payday you have to wait {time}."
|
||||
).format(author=author, time=dtime)
|
||||
)
|
||||
|
||||
@commands.command()
|
||||
@guild_only_check()
|
||||
async def leaderboard(self, ctx: commands.Context, top: int = 10, show_global: bool = False):
|
||||
"""Prints out the leaderboard
|
||||
"""Print the leaderboard.
|
||||
|
||||
Defaults to top 10"""
|
||||
Defaults to top 10.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
if top < 1:
|
||||
@ -320,9 +339,9 @@ class Economy(commands.Cog):
|
||||
): # show_global is only applicable if bank is global
|
||||
guild = None
|
||||
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
|
||||
if len(bank_sorted) < top:
|
||||
top = len(bank_sorted)
|
||||
header = f"{f'#':4}{f'Name':36}{f'Score':2}\n"
|
||||
header = "{pound:4}{name:36}{score:2}\n".format(
|
||||
pound="#", name=_("Name"), score=_("Score")
|
||||
)
|
||||
highscores = [
|
||||
(
|
||||
f"{f'{pos}.': <{3 if pos < 10 else 2}} {acc[1]['name']: <{35}s} "
|
||||
@ -347,13 +366,13 @@ class Economy(commands.Cog):
|
||||
@commands.command()
|
||||
@guild_only_check()
|
||||
async def payouts(self, ctx: commands.Context):
|
||||
"""Shows slot machine payouts"""
|
||||
await ctx.author.send(SLOT_PAYOUTS_MSG)
|
||||
"""Show the payouts for the slot machine."""
|
||||
await ctx.author.send(SLOT_PAYOUTS_MSG())
|
||||
|
||||
@commands.command()
|
||||
@guild_only_check()
|
||||
async def slot(self, ctx: commands.Context, bid: int):
|
||||
"""Play the slot machine"""
|
||||
"""Use the slot machine."""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
channel = ctx.channel
|
||||
@ -386,8 +405,9 @@ class Economy(commands.Cog):
|
||||
await self.config.member(author).last_slot.set(now)
|
||||
await self.slot_machine(author, channel, bid)
|
||||
|
||||
async def slot_machine(self, author, channel, bid):
|
||||
default_reel = deque(SMReel)
|
||||
@staticmethod
|
||||
async def slot_machine(author, channel, bid):
|
||||
default_reel = deque(cast(Iterable, SMReel))
|
||||
reels = []
|
||||
for i in range(3):
|
||||
default_reel.rotate(random.randint(-999, 999)) # weeeeee
|
||||
@ -425,60 +445,62 @@ class Economy(commands.Cog):
|
||||
pay = payout["payout"](bid)
|
||||
now = then - bid + pay
|
||||
await bank.set_balance(author, now)
|
||||
await channel.send(
|
||||
_("{}\n{} {}\n\nYour bid: {}\n{} → {}!").format(
|
||||
slot, author.mention, payout["phrase"], bid, then, now
|
||||
)
|
||||
)
|
||||
phrase = T_(payout["phrase"])
|
||||
else:
|
||||
then = await bank.get_balance(author)
|
||||
await bank.withdraw_credits(author, bid)
|
||||
now = then - bid
|
||||
await channel.send(
|
||||
_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!").format(
|
||||
slot, author.mention, bid, then, now
|
||||
)
|
||||
phrase = _("Nothing!")
|
||||
await channel.send(
|
||||
(
|
||||
"{slot}\n{author.mention} {phrase}\n\n"
|
||||
+ _("Your bid: {amount}")
|
||||
+ "\n{old_balance} → {new_balance}!"
|
||||
).format(
|
||||
slot=slot,
|
||||
author=author,
|
||||
phrase=phrase,
|
||||
amount=bid,
|
||||
old_balance=then,
|
||||
new_balance=now,
|
||||
)
|
||||
)
|
||||
|
||||
@commands.group()
|
||||
@guild_only_check()
|
||||
@check_global_setting_admin()
|
||||
async def economyset(self, ctx: commands.Context):
|
||||
"""Changes economy module settings"""
|
||||
"""Manage Economy settings."""
|
||||
guild = ctx.guild
|
||||
if ctx.invoked_subcommand is None:
|
||||
if await bank.is_global():
|
||||
slot_min = await self.config.SLOT_MIN()
|
||||
slot_max = await self.config.SLOT_MAX()
|
||||
slot_time = await self.config.SLOT_TIME()
|
||||
payday_time = await self.config.PAYDAY_TIME()
|
||||
payday_amount = await self.config.PAYDAY_CREDITS()
|
||||
conf = self.config
|
||||
else:
|
||||
slot_min = await self.config.guild(guild).SLOT_MIN()
|
||||
slot_max = await self.config.guild(guild).SLOT_MAX()
|
||||
slot_time = await self.config.guild(guild).SLOT_TIME()
|
||||
payday_time = await self.config.guild(guild).PAYDAY_TIME()
|
||||
payday_amount = await self.config.guild(guild).PAYDAY_CREDITS()
|
||||
register_amount = await bank.get_default_balance(guild)
|
||||
msg = box(
|
||||
_(
|
||||
"Minimum slot bid: {}\n"
|
||||
"Maximum slot bid: {}\n"
|
||||
"Slot cooldown: {}\n"
|
||||
"Payday amount: {}\n"
|
||||
"Payday cooldown: {}\n"
|
||||
"Amount given at account registration: {}"
|
||||
""
|
||||
).format(
|
||||
slot_min, slot_max, slot_time, payday_amount, payday_time, register_amount
|
||||
),
|
||||
_("Current Economy settings:"),
|
||||
conf = self.config.guild(ctx.guild)
|
||||
await ctx.send(
|
||||
box(
|
||||
_(
|
||||
"----Economy Settings---\n"
|
||||
"Minimum slot bid: {slot_min}\n"
|
||||
"Maximum slot bid: {slot_max}\n"
|
||||
"Slot cooldown: {slot_time}\n"
|
||||
"Payday amount: {payday_amount}\n"
|
||||
"Payday cooldown: {payday_time}\n"
|
||||
"Amount given at account registration: {register_amount}"
|
||||
).format(
|
||||
slot_min=await conf.SLOT_MIN(),
|
||||
slot_max=await conf.SLOT_MAX(),
|
||||
slot_time=await conf.SLOT_TIME(),
|
||||
payday_time=await conf.PAYDAY_TIME(),
|
||||
payday_amount=await conf.PAYDAY_CREDITS(),
|
||||
register_amount=await bank.get_default_balance(guild),
|
||||
)
|
||||
)
|
||||
)
|
||||
await ctx.send(msg)
|
||||
|
||||
@economyset.command()
|
||||
async def slotmin(self, ctx: commands.Context, bid: int):
|
||||
"""Minimum slot machine bid"""
|
||||
"""Set the minimum slot machine bid."""
|
||||
if bid < 1:
|
||||
await ctx.send(_("Invalid min bid amount."))
|
||||
return
|
||||
@ -488,14 +510,18 @@ class Economy(commands.Cog):
|
||||
else:
|
||||
await self.config.guild(guild).SLOT_MIN.set(bid)
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
await ctx.send(_("Minimum bid is now {} {}.").format(bid, credits_name))
|
||||
await ctx.send(
|
||||
_("Minimum bid is now {bid} {currency}.").format(bid=bid, currency=credits_name)
|
||||
)
|
||||
|
||||
@economyset.command()
|
||||
async def slotmax(self, ctx: commands.Context, bid: int):
|
||||
"""Maximum slot machine bid"""
|
||||
"""Set the maximum slot machine bid."""
|
||||
slot_min = await self.config.SLOT_MIN()
|
||||
if bid < 1 or bid < slot_min:
|
||||
await ctx.send(_("Invalid slotmax bid amount. Must be greater than slotmin."))
|
||||
await ctx.send(
|
||||
_("Invalid maximum bid amount. Must be greater than the minimum amount.")
|
||||
)
|
||||
return
|
||||
guild = ctx.guild
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
@ -503,33 +529,37 @@ class Economy(commands.Cog):
|
||||
await self.config.SLOT_MAX.set(bid)
|
||||
else:
|
||||
await self.config.guild(guild).SLOT_MAX.set(bid)
|
||||
await ctx.send(_("Maximum bid is now {} {}.").format(bid, credits_name))
|
||||
await ctx.send(
|
||||
_("Maximum bid is now {bid} {currency}.").format(bid=bid, currency=credits_name)
|
||||
)
|
||||
|
||||
@economyset.command()
|
||||
async def slottime(self, ctx: commands.Context, seconds: int):
|
||||
"""Seconds between each slots use"""
|
||||
"""Set the cooldown for the slot machine."""
|
||||
guild = ctx.guild
|
||||
if await bank.is_global():
|
||||
await self.config.SLOT_TIME.set(seconds)
|
||||
else:
|
||||
await self.config.guild(guild).SLOT_TIME.set(seconds)
|
||||
await ctx.send(_("Cooldown is now {} seconds.").format(seconds))
|
||||
await ctx.send(_("Cooldown is now {num} seconds.").format(num=seconds))
|
||||
|
||||
@economyset.command()
|
||||
async def paydaytime(self, ctx: commands.Context, seconds: int):
|
||||
"""Seconds between each payday"""
|
||||
"""Set the cooldown for payday."""
|
||||
guild = ctx.guild
|
||||
if await bank.is_global():
|
||||
await self.config.PAYDAY_TIME.set(seconds)
|
||||
else:
|
||||
await self.config.guild(guild).PAYDAY_TIME.set(seconds)
|
||||
await ctx.send(
|
||||
_("Value modified. At least {} seconds must pass between each payday.").format(seconds)
|
||||
_("Value modified. At least {num} seconds must pass between each payday.").format(
|
||||
num=seconds
|
||||
)
|
||||
)
|
||||
|
||||
@economyset.command()
|
||||
async def paydayamount(self, ctx: commands.Context, creds: int):
|
||||
"""Amount earned each payday"""
|
||||
"""Set the amount earned each payday."""
|
||||
guild = ctx.guild
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
if creds <= 0:
|
||||
@ -539,37 +569,45 @@ class Economy(commands.Cog):
|
||||
await self.config.PAYDAY_CREDITS.set(creds)
|
||||
else:
|
||||
await self.config.guild(guild).PAYDAY_CREDITS.set(creds)
|
||||
await ctx.send(_("Every payday will now give {} {}.").format(creds, credits_name))
|
||||
await ctx.send(
|
||||
_("Every payday will now give {num} {currency}.").format(
|
||||
num=creds, currency=credits_name
|
||||
)
|
||||
)
|
||||
|
||||
@economyset.command()
|
||||
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
||||
"""Amount earned each payday for a role"""
|
||||
"""Set the amount earned each payday for a role."""
|
||||
guild = ctx.guild
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
if await bank.is_global():
|
||||
await ctx.send("The bank must be per-server for per-role paydays to work.")
|
||||
await ctx.send(_("The bank must be per-server for per-role paydays to work."))
|
||||
else:
|
||||
await self.config.role(role).PAYDAY_CREDITS.set(creds)
|
||||
await ctx.send(
|
||||
_("Every payday will now give {} {} to people with the role {}.").format(
|
||||
creds, credits_name, role.name
|
||||
)
|
||||
_(
|
||||
"Every payday will now give {num} {currency} "
|
||||
"to people with the role {role_name}."
|
||||
).format(num=creds, currency=credits_name, role_name=role.name)
|
||||
)
|
||||
|
||||
@economyset.command()
|
||||
async def registeramount(self, ctx: commands.Context, creds: int):
|
||||
"""Amount given on registering an account"""
|
||||
"""Set the initial balance for new bank accounts."""
|
||||
guild = ctx.guild
|
||||
if creds < 0:
|
||||
creds = 0
|
||||
credits_name = await bank.get_currency_name(guild)
|
||||
await bank.set_default_balance(creds, guild)
|
||||
await ctx.send(
|
||||
_("Registering an account will now give {} {}.").format(creds, credits_name)
|
||||
_("Registering an account will now give {num} {currency}.").format(
|
||||
num=creds, currency=credits_name
|
||||
)
|
||||
)
|
||||
|
||||
# What would I ever do without stackoverflow?
|
||||
def display_time(self, seconds, granularity=2):
|
||||
@staticmethod
|
||||
def display_time(seconds, granularity=2):
|
||||
intervals = ( # Source: http://stackoverflow.com/a/24542445
|
||||
(_("weeks"), 604800), # 60 * 60 * 24 * 7
|
||||
(_("days"), 86400), # 60 * 60 * 24
|
||||
|
||||
@ -5,14 +5,13 @@ from redbot.core import checks, Config, modlog, commands
|
||||
from redbot.core.bot import Red
|
||||
from redbot.core.i18n import Translator, cog_i18n
|
||||
from redbot.core.utils.chat_formatting import pagify
|
||||
from redbot.core.utils.mod import is_mod_or_superior
|
||||
|
||||
_ = Translator("Filter", __file__)
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
class Filter(commands.Cog):
|
||||
"""Filter-related commands"""
|
||||
"""Filter unwanted words and phrases from text channels."""
|
||||
|
||||
def __init__(self, bot: Red):
|
||||
super().__init__()
|
||||
@ -35,7 +34,8 @@ class Filter(commands.Cog):
|
||||
def __unload(self):
|
||||
self.register_task.cancel()
|
||||
|
||||
async def register_filterban(self):
|
||||
@staticmethod
|
||||
async def register_filterban():
|
||||
try:
|
||||
await modlog.register_casetype(
|
||||
"filterban", False, ":filing_cabinet: :hammer:", "Filter ban", "ban"
|
||||
@ -47,18 +47,17 @@ class Filter(commands.Cog):
|
||||
@commands.guild_only()
|
||||
@checks.admin_or_permissions(manage_guild=True)
|
||||
async def filterset(self, ctx: commands.Context):
|
||||
"""
|
||||
Filter settings
|
||||
"""
|
||||
"""Manage filter settings."""
|
||||
pass
|
||||
|
||||
@filterset.command(name="defaultname")
|
||||
async def filter_default_name(self, ctx: commands.Context, name: str):
|
||||
"""Sets the default name to use if filtering names is enabled
|
||||
"""Set the nickname for users with a filtered name.
|
||||
|
||||
Note that this has no effect if filtering names is disabled
|
||||
(to toggle, run `[p]filter names`).
|
||||
|
||||
The default name used is John Doe
|
||||
The default name used is *John Doe*.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
await self.settings.guild(guild).filter_default_name.set(name)
|
||||
@ -66,9 +65,12 @@ class Filter(commands.Cog):
|
||||
|
||||
@filterset.command(name="ban")
|
||||
async def filter_ban(self, ctx: commands.Context, count: int, timeframe: int):
|
||||
"""Autobans if the specified number of messages are filtered in the timeframe
|
||||
"""Set the filter's autoban conditions.
|
||||
|
||||
The timeframe is represented by seconds.
|
||||
Users will be banned if they send `<count>` filtered words in
|
||||
`<timeframe>` seconds.
|
||||
|
||||
Set both to zero to disable autoban.
|
||||
"""
|
||||
if (count <= 0) != (timeframe <= 0):
|
||||
await ctx.send(
|
||||
@ -91,11 +93,13 @@ class Filter(commands.Cog):
|
||||
@commands.guild_only()
|
||||
@checks.mod_or_permissions(manage_messages=True)
|
||||
async def _filter(self, ctx: commands.Context):
|
||||
"""Adds/removes words from server filter
|
||||
"""Add or remove words from server filter.
|
||||
|
||||
Use double quotes to add/remove sentences
|
||||
Using this command with no subcommands will send
|
||||
the list of the server's filtered words."""
|
||||
Use double quotes to add or remove sentences.
|
||||
|
||||
Using this command with no subcommands will send the list of
|
||||
the server's filtered words.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
server = ctx.guild
|
||||
author = ctx.author
|
||||
@ -111,11 +115,13 @@ class Filter(commands.Cog):
|
||||
|
||||
@_filter.group(name="channel")
|
||||
async def _filter_channel(self, ctx: commands.Context):
|
||||
"""Adds/removes words from channel filter
|
||||
"""Add or remove words from channel filter.
|
||||
|
||||
Use double quotes to add/remove sentences
|
||||
Using this command with no subcommands will send
|
||||
the list of the channel's filtered words."""
|
||||
Use double quotes to add or remove sentences.
|
||||
|
||||
Using this command with no subcommands will send the list of
|
||||
the channel's filtered words.
|
||||
"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
channel = ctx.channel
|
||||
author = ctx.author
|
||||
@ -131,12 +137,14 @@ class Filter(commands.Cog):
|
||||
|
||||
@_filter_channel.command("add")
|
||||
async def filter_channel_add(self, ctx: commands.Context, *, words: str):
|
||||
"""Adds words to the filter
|
||||
"""Add words to the filter.
|
||||
|
||||
Use double quotes to add sentences.
|
||||
|
||||
Use double quotes to add sentences
|
||||
Examples:
|
||||
filter add word1 word2 word3
|
||||
filter add \"This is a sentence\""""
|
||||
- `[p]filter channel add word1 word2 word3`
|
||||
- `[p]filter channel add "This is a sentence"`
|
||||
"""
|
||||
channel = ctx.channel
|
||||
split_words = words.split()
|
||||
word_list = []
|
||||
@ -161,12 +169,14 @@ class Filter(commands.Cog):
|
||||
|
||||
@_filter_channel.command("remove")
|
||||
async def filter_channel_remove(self, ctx: commands.Context, *, words: str):
|
||||
"""Remove words from the filter
|
||||
"""Remove words from the filter.
|
||||
|
||||
Use double quotes to remove sentences.
|
||||
|
||||
Use double quotes to remove sentences
|
||||
Examples:
|
||||
filter remove word1 word2 word3
|
||||
filter remove \"This is a sentence\""""
|
||||
- `[p]filter channel remove word1 word2 word3`
|
||||
- `[p]filter channel remove "This is a sentence"`
|
||||
"""
|
||||
channel = ctx.channel
|
||||
split_words = words.split()
|
||||
word_list = []
|
||||
@ -191,12 +201,14 @@ class Filter(commands.Cog):
|
||||
|
||||
@_filter.command(name="add")
|
||||
async def filter_add(self, ctx: commands.Context, *, words: str):
|
||||
"""Adds words to the filter
|
||||
"""Add words to the filter.
|
||||
|
||||
Use double quotes to add sentences.
|
||||
|
||||
Use double quotes to add sentences
|
||||
Examples:
|
||||
filter add word1 word2 word3
|
||||
filter add \"This is a sentence\""""
|
||||
- `[p]filter add word1 word2 word3`
|
||||
- `[p]filter add "This is a sentence"`
|
||||
"""
|
||||
server = ctx.guild
|
||||
split_words = words.split()
|
||||
word_list = []
|
||||
@ -215,18 +227,20 @@ class Filter(commands.Cog):
|
||||
tmp += word + " "
|
||||
added = await self.add_to_filter(server, word_list)
|
||||
if added:
|
||||
await ctx.send(_("Words added to filter."))
|
||||
await ctx.send(_("Words successfully added to filter."))
|
||||
else:
|
||||
await ctx.send(_("Words already in the filter."))
|
||||
await ctx.send(_("Those words were already in the filter."))
|
||||
|
||||
@_filter.command(name="remove")
|
||||
async def filter_remove(self, ctx: commands.Context, *, words: str):
|
||||
"""Remove words from the filter
|
||||
"""Remove words from the filter.
|
||||
|
||||
Use double quotes to remove sentences.
|
||||
|
||||
Use double quotes to remove sentences
|
||||
Examples:
|
||||
filter remove word1 word2 word3
|
||||
filter remove \"This is a sentence\""""
|
||||
- `[p]filter remove word1 word2 word3`
|
||||
- `[p]filter remove "This is a sentence"`
|
||||
"""
|
||||
server = ctx.guild
|
||||
split_words = words.split()
|
||||
word_list = []
|
||||
@ -245,23 +259,23 @@ class Filter(commands.Cog):
|
||||
tmp += word + " "
|
||||
removed = await self.remove_from_filter(server, word_list)
|
||||
if removed:
|
||||
await ctx.send(_("Words removed from filter."))
|
||||
await ctx.send(_("Words successfully removed from filter."))
|
||||
else:
|
||||
await ctx.send(_("Those words weren't in the filter."))
|
||||
|
||||
@_filter.command(name="names")
|
||||
async def filter_names(self, ctx: commands.Context):
|
||||
"""Toggles whether or not to check names and nicknames against the filter
|
||||
"""Toggle name and nickname filtering.
|
||||
|
||||
This is disabled by default
|
||||
This is disabled by default.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
current_setting = await self.settings.guild(guild).filter_names()
|
||||
await self.settings.guild(guild).filter_names.set(not current_setting)
|
||||
if current_setting:
|
||||
await ctx.send(_("Names and nicknames will no longer be checked against the filter."))
|
||||
await ctx.send(_("Names and nicknames will no longer be filtered."))
|
||||
else:
|
||||
await ctx.send(_("Names and nicknames will now be checked against the filter."))
|
||||
await ctx.send(_("Names and nicknames will now be filtered."))
|
||||
|
||||
async def add_to_filter(
|
||||
self, server_or_channel: Union[discord.Guild, discord.TextChannel], words: list
|
||||
@ -327,7 +341,7 @@ class Filter(commands.Cog):
|
||||
if w in message.content.lower():
|
||||
try:
|
||||
await message.delete()
|
||||
except:
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
else:
|
||||
if filter_count > 0 and filter_time > 0:
|
||||
@ -337,10 +351,10 @@ class Filter(commands.Cog):
|
||||
user_count >= filter_count
|
||||
and message.created_at.timestamp() < next_reset_time
|
||||
):
|
||||
reason = "Autoban (too many filtered messages.)"
|
||||
reason = _("Autoban (too many filtered messages.)")
|
||||
try:
|
||||
await server.ban(author, reason=reason)
|
||||
except:
|
||||
except discord.HTTPException:
|
||||
pass
|
||||
else:
|
||||
await modlog.create_case(
|
||||
@ -366,20 +380,6 @@ class Filter(commands.Cog):
|
||||
|
||||
await self.check_filter(message)
|
||||
|
||||
async def on_message_edit(self, _, message):
|
||||
author = message.author
|
||||
if message.guild is None or self.bot.user == author:
|
||||
return
|
||||
valid_user = isinstance(author, discord.Member) and not author.bot
|
||||
if not valid_user:
|
||||
return
|
||||
|
||||
# As is anyone configured to be
|
||||
if await self.bot.is_automod_immune(message):
|
||||
return
|
||||
|
||||
await self.check_filter(message)
|
||||
|
||||
async def on_message_edit(self, _prior, message):
|
||||
# message content has to change for non-bot's currently.
|
||||
# if this changes, we should compare before passing it.
|
||||
@ -399,14 +399,14 @@ class Filter(commands.Cog):
|
||||
return # Discord Hierarchy applies to nicks
|
||||
if await self.bot.is_automod_immune(member):
|
||||
return
|
||||
word_list = await self.settings.guild(member.guild).filter()
|
||||
if not await self.settings.guild(member.guild).filter_names():
|
||||
return
|
||||
|
||||
word_list = await self.settings.guild(member.guild).filter()
|
||||
for w in word_list:
|
||||
if w in member.display_name.lower():
|
||||
name_to_use = await self.settings.guild(member.guild).filter_default_name()
|
||||
reason = "Filtered nick" if member.nick else "Filtered name"
|
||||
reason = _("Filtered nickname") if member.nick else _("Filtered name")
|
||||
try:
|
||||
await member.edit(nick=name_to_use, reason=reason)
|
||||
except discord.HTTPException:
|
||||
|
||||
@ -2,15 +2,14 @@ import datetime
|
||||
import time
|
||||
from enum import Enum
|
||||
from random import randint, choice
|
||||
from urllib.parse import quote_plus
|
||||
import aiohttp
|
||||
import discord
|
||||
from redbot.core import commands
|
||||
from redbot.core.i18n import Translator, cog_i18n
|
||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||
from redbot.core.utils.chat_formatting import escape, italics, pagify
|
||||
from redbot.core.utils.chat_formatting import escape, italics
|
||||
|
||||
_ = Translator("General", __file__)
|
||||
_ = T_ = Translator("General", __file__)
|
||||
|
||||
|
||||
class RPS(Enum):
|
||||
@ -29,71 +28,78 @@ class RPSParser:
|
||||
elif argument == "scissors":
|
||||
self.choice = RPS.scissors
|
||||
else:
|
||||
raise
|
||||
raise ValueError
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
class General(commands.Cog):
|
||||
"""General commands."""
|
||||
|
||||
global _
|
||||
_ = lambda s: s
|
||||
ball = [
|
||||
_("As I see it, yes"),
|
||||
_("It is certain"),
|
||||
_("It is decidedly so"),
|
||||
_("Most likely"),
|
||||
_("Outlook good"),
|
||||
_("Signs point to yes"),
|
||||
_("Without a doubt"),
|
||||
_("Yes"),
|
||||
_("Yes – definitely"),
|
||||
_("You may rely on it"),
|
||||
_("Reply hazy, try again"),
|
||||
_("Ask again later"),
|
||||
_("Better not tell you now"),
|
||||
_("Cannot predict now"),
|
||||
_("Concentrate and ask again"),
|
||||
_("Don't count on it"),
|
||||
_("My reply is no"),
|
||||
_("My sources say no"),
|
||||
_("Outlook not so good"),
|
||||
_("Very doubtful"),
|
||||
]
|
||||
_ = T_
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.stopwatches = {}
|
||||
self.ball = [
|
||||
_("As I see it, yes"),
|
||||
_("It is certain"),
|
||||
_("It is decidedly so"),
|
||||
_("Most likely"),
|
||||
_("Outlook good"),
|
||||
_("Signs point to yes"),
|
||||
_("Without a doubt"),
|
||||
_("Yes"),
|
||||
_("Yes – definitely"),
|
||||
_("You may rely on it"),
|
||||
_("Reply hazy, try again"),
|
||||
_("Ask again later"),
|
||||
_("Better not tell you now"),
|
||||
_("Cannot predict now"),
|
||||
_("Concentrate and ask again"),
|
||||
_("Don't count on it"),
|
||||
_("My reply is no"),
|
||||
_("My sources say no"),
|
||||
_("Outlook not so good"),
|
||||
_("Very doubtful"),
|
||||
]
|
||||
|
||||
@commands.command()
|
||||
async def choose(self, ctx, *choices):
|
||||
"""Chooses between multiple choices.
|
||||
"""Choose between multiple options.
|
||||
|
||||
To denote multiple choices, you should use double quotes.
|
||||
To denote options which include whitespace, you should use
|
||||
double quotes.
|
||||
"""
|
||||
choices = [escape(c, mass_mentions=True) for c in choices]
|
||||
if len(choices) < 2:
|
||||
await ctx.send(_("Not enough choices to pick from."))
|
||||
await ctx.send(_("Not enough options to pick from."))
|
||||
else:
|
||||
await ctx.send(choice(choices))
|
||||
|
||||
@commands.command()
|
||||
async def roll(self, ctx, number: int = 100):
|
||||
"""Rolls random number (between 1 and user choice)
|
||||
"""Roll a random number.
|
||||
|
||||
Defaults to 100.
|
||||
The result will be between 1 and `<number>`.
|
||||
|
||||
`<number>` defaults to 100.
|
||||
"""
|
||||
author = ctx.author
|
||||
if number > 1:
|
||||
n = randint(1, number)
|
||||
await ctx.send(_("{} :game_die: {} :game_die:").format(author.mention, n))
|
||||
await ctx.send("{author.mention} :game_die: {n} :game_die:".format(author=author, n=n))
|
||||
else:
|
||||
await ctx.send(_("{} Maybe higher than 1? ;P").format(author.mention))
|
||||
await ctx.send(_("{author.mention} Maybe higher than 1? ;P").format(author=author))
|
||||
|
||||
@commands.command()
|
||||
async def flip(self, ctx, user: discord.Member = None):
|
||||
"""Flips a coin... or a user.
|
||||
"""Flip a coin... or a user.
|
||||
|
||||
Defaults to coin.
|
||||
Defaults to a coin.
|
||||
"""
|
||||
if user != None:
|
||||
if user is not None:
|
||||
msg = ""
|
||||
if user.id == ctx.bot.user.id:
|
||||
user = ctx.author
|
||||
@ -112,7 +118,7 @@ class General(commands.Cog):
|
||||
|
||||
@commands.command()
|
||||
async def rps(self, ctx, your_choice: RPSParser):
|
||||
"""Play rock paper scissors"""
|
||||
"""Play Rock Paper Scissors."""
|
||||
author = ctx.author
|
||||
player_choice = your_choice.choice
|
||||
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
|
||||
@ -131,39 +137,53 @@ class General(commands.Cog):
|
||||
outcome = cond[(player_choice, red_choice)]
|
||||
|
||||
if outcome is True:
|
||||
await ctx.send(_("{} You win {}!").format(red_choice.value, author.mention))
|
||||
await ctx.send(
|
||||
_("{choice} You win {author.mention}!").format(
|
||||
choice=red_choice.value, author=author
|
||||
)
|
||||
)
|
||||
elif outcome is False:
|
||||
await ctx.send(_("{} You lose {}!").format(red_choice.value, author.mention))
|
||||
await ctx.send(
|
||||
_("{choice} You lose {author.mention}!").format(
|
||||
choice=red_choice.value, author=author
|
||||
)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("{} We're square {}!").format(red_choice.value, author.mention))
|
||||
await ctx.send(
|
||||
_("{choice} We're square {author.mention}!").format(
|
||||
choice=red_choice.value, author=author
|
||||
)
|
||||
)
|
||||
|
||||
@commands.command(name="8", aliases=["8ball"])
|
||||
async def _8ball(self, ctx, *, question: str):
|
||||
"""Ask 8 ball a question
|
||||
"""Ask 8 ball a question.
|
||||
|
||||
Question must end with a question mark.
|
||||
"""
|
||||
if question.endswith("?") and question != "?":
|
||||
await ctx.send("`" + choice(self.ball) + "`")
|
||||
await ctx.send("`" + T_(choice(self.ball)) + "`")
|
||||
else:
|
||||
await ctx.send(_("That doesn't look like a question."))
|
||||
|
||||
@commands.command(aliases=["sw"])
|
||||
async def stopwatch(self, ctx):
|
||||
"""Starts/stops stopwatch"""
|
||||
"""Start or stop the stopwatch."""
|
||||
author = ctx.author
|
||||
if not author.id in self.stopwatches:
|
||||
if author.id not in self.stopwatches:
|
||||
self.stopwatches[author.id] = int(time.perf_counter())
|
||||
await ctx.send(author.mention + _(" Stopwatch started!"))
|
||||
else:
|
||||
tmp = abs(self.stopwatches[author.id] - int(time.perf_counter()))
|
||||
tmp = str(datetime.timedelta(seconds=tmp))
|
||||
await ctx.send(author.mention + _(" Stopwatch stopped! Time: **") + tmp + "**")
|
||||
await ctx.send(
|
||||
author.mention + _(" Stopwatch stopped! Time: **{seconds}**").format(seconds=tmp)
|
||||
)
|
||||
self.stopwatches.pop(author.id, None)
|
||||
|
||||
@commands.command()
|
||||
async def lmgtfy(self, ctx, *, search_terms: str):
|
||||
"""Creates a lmgtfy link"""
|
||||
"""Create a lmgtfy link."""
|
||||
search_terms = escape(
|
||||
search_terms.replace("+", "%2B").replace(" ", "+"), mass_mentions=True
|
||||
)
|
||||
@ -172,9 +192,10 @@ class General(commands.Cog):
|
||||
@commands.command(hidden=True)
|
||||
@commands.guild_only()
|
||||
async def hug(self, ctx, user: discord.Member, intensity: int = 1):
|
||||
"""Because everyone likes hugs
|
||||
"""Because everyone likes hugs!
|
||||
|
||||
Up to 10 intensity levels."""
|
||||
Up to 10 intensity levels.
|
||||
"""
|
||||
name = italics(user.display_name)
|
||||
if intensity <= 0:
|
||||
msg = "(っ˘̩╭╮˘̩)っ" + name
|
||||
@ -186,24 +207,27 @@ class General(commands.Cog):
|
||||
msg = "(つ≧▽≦)つ" + name
|
||||
elif intensity >= 10:
|
||||
msg = "(づ ̄ ³ ̄)づ{} ⊂(´・ω・`⊂)".format(name)
|
||||
else:
|
||||
# For the purposes of "msg might not be defined" linter errors
|
||||
raise RuntimeError
|
||||
await ctx.send(msg)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
async def serverinfo(self, ctx):
|
||||
"""Shows server's informations"""
|
||||
"""Show server information."""
|
||||
guild = ctx.guild
|
||||
online = len([m.status for m in guild.members if m.status != discord.Status.offline])
|
||||
total_users = len(guild.members)
|
||||
text_channels = len(guild.text_channels)
|
||||
voice_channels = len(guild.voice_channels)
|
||||
passed = (ctx.message.created_at - guild.created_at).days
|
||||
created_at = _("Since {}. That's over {} days ago!").format(
|
||||
guild.created_at.strftime("%d %b %Y %H:%M"), passed
|
||||
created_at = _("Since {date}. That's over {num} days ago!").format(
|
||||
date=guild.created_at.strftime("%d %b %Y %H:%M"), num=passed
|
||||
)
|
||||
data = discord.Embed(description=created_at, colour=(await ctx.embed_colour()))
|
||||
data.add_field(name=_("Region"), value=str(guild.region))
|
||||
data.add_field(name=_("Users"), value="{}/{}".format(online, total_users))
|
||||
data.add_field(name=_("Users"), value=f"{online}/{total_users}")
|
||||
data.add_field(name=_("Text Channels"), value=str(text_channels))
|
||||
data.add_field(name=_("Voice Channels"), value=str(voice_channels))
|
||||
data.add_field(name=_("Roles"), value=str(len(guild.roles)))
|
||||
@ -218,12 +242,15 @@ class General(commands.Cog):
|
||||
|
||||
try:
|
||||
await ctx.send(embed=data)
|
||||
except discord.HTTPException:
|
||||
except discord.Forbidden:
|
||||
await ctx.send(_("I need the `Embed links` permission to send this."))
|
||||
|
||||
@commands.command()
|
||||
async def urban(self, ctx, *, word):
|
||||
"""Searches urban dictionary entries using the unofficial api"""
|
||||
"""Search the Urban Dictionary.
|
||||
|
||||
This uses the unofficial Urban Dictionary API.
|
||||
"""
|
||||
|
||||
try:
|
||||
url = "https://api.urbandictionary.com/v0/define?term=" + str(word).lower()
|
||||
@ -234,10 +261,11 @@ class General(commands.Cog):
|
||||
async with session.get(url, headers=headers) as response:
|
||||
data = await response.json()
|
||||
|
||||
except:
|
||||
except aiohttp.ClientError:
|
||||
await ctx.send(
|
||||
_("No Urban dictionary entries were found or there was an error in the process")
|
||||
_("No Urban dictionary entries were found, or there was an error in the process")
|
||||
)
|
||||
return
|
||||
|
||||
if data.get("error") != 404:
|
||||
|
||||
@ -246,20 +274,20 @@ class General(commands.Cog):
|
||||
embeds = []
|
||||
for ud in data["list"]:
|
||||
embed = discord.Embed()
|
||||
embed.title = _("{} by {}").format(ud["word"].capitalize(), ud["author"])
|
||||
embed.title = _("{word} by {author}").format(
|
||||
word=ud["word"].capitalize(), author=ud["author"]
|
||||
)
|
||||
embed.url = ud["permalink"]
|
||||
|
||||
description = "{} \n \n **Example : ** {}".format(
|
||||
ud["definition"], ud.get("example", "N/A")
|
||||
)
|
||||
description = _("{definition}\n\n**Example:** {example}").format(**ud)
|
||||
if len(description) > 2048:
|
||||
description = "{}...".format(description[:2045])
|
||||
embed.description = description
|
||||
|
||||
embed.set_footer(
|
||||
text=_("{} Down / {} Up , Powered by urban dictionary").format(
|
||||
ud["thumbs_down"], ud["thumbs_up"]
|
||||
)
|
||||
text=_(
|
||||
"{thumbs_down} Down / {thumbs_up} Up, Powered by Urban Dictionary."
|
||||
).format(**ud)
|
||||
)
|
||||
embeds.append(embed)
|
||||
|
||||
@ -275,24 +303,15 @@ class General(commands.Cog):
|
||||
else:
|
||||
messages = []
|
||||
for ud in data["list"]:
|
||||
description = _("{} \n \n **Example : ** {}").format(
|
||||
ud["definition"], ud.get("example", "N/A")
|
||||
)
|
||||
ud.set_default("example", "N/A")
|
||||
description = _("{definition}\n\n**Example:** {example}").format(**ud)
|
||||
if len(description) > 2048:
|
||||
description = "{}...".format(description[:2045])
|
||||
description = description
|
||||
|
||||
message = _(
|
||||
"<{}> \n {} by {} \n \n {} \n \n {} Down / {} Up, Powered by urban "
|
||||
"dictionary"
|
||||
).format(
|
||||
ud["permalink"],
|
||||
ud["word"].capitalize(),
|
||||
ud["author"],
|
||||
description,
|
||||
ud["thumbs_down"],
|
||||
ud["thumbs_up"],
|
||||
)
|
||||
"<{permalink}>\n {word} by {author}\n\n{description}\n\n"
|
||||
"{thumbs_down} Down / {thumbs_up} Up, Powered by urban dictionary"
|
||||
).format(word=ud.pop("word").capitalize(), description=description, **ud)
|
||||
messages.append(message)
|
||||
|
||||
if messages is not None and len(messages) > 0:
|
||||
@ -306,6 +325,6 @@ class General(commands.Cog):
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
_("No Urban dictionary entries were found or there was an error in the process")
|
||||
_("No Urban dictionary entries were found, or there was an error in the process.")
|
||||
)
|
||||
return
|
||||
|
||||
@ -29,23 +29,26 @@ class Image(commands.Cog):
|
||||
|
||||
@commands.group(name="imgur")
|
||||
async def _imgur(self, ctx):
|
||||
"""Retrieves pictures from imgur
|
||||
"""Retrieve pictures from Imgur.
|
||||
|
||||
Make sure to set the client ID using
|
||||
[p]imgurcreds"""
|
||||
Make sure to set the Client ID using `[p]imgurcreds`.
|
||||
"""
|
||||
pass
|
||||
|
||||
@_imgur.command(name="search")
|
||||
async def imgur_search(self, ctx, *, term: str):
|
||||
"""Searches Imgur for the specified term and returns up to 3 results"""
|
||||
"""Search Imgur for the specified term.
|
||||
|
||||
Returns up to 3 results.
|
||||
"""
|
||||
url = self.imgur_base_url + "gallery/search/time/all/0"
|
||||
params = {"q": term}
|
||||
imgur_client_id = await self.settings.imgur_client_id()
|
||||
if not imgur_client_id:
|
||||
await ctx.send(
|
||||
_("A client ID has not been set! Please set one with {}.").format(
|
||||
"`{}imgurcreds`".format(ctx.prefix)
|
||||
)
|
||||
_(
|
||||
"A Client ID has not been set! Please set one with `{prefix}imgurcreds`."
|
||||
).format(prefix=ctx.prefix)
|
||||
)
|
||||
return
|
||||
headers = {"Authorization": "Client-ID {}".format(imgur_client_id)}
|
||||
@ -64,37 +67,41 @@ class Image(commands.Cog):
|
||||
msg += "\n"
|
||||
await ctx.send(msg)
|
||||
else:
|
||||
await ctx.send(_("Something went wrong. Error code is {}.").format(data["status"]))
|
||||
await ctx.send(
|
||||
_("Something went wrong. Error code is {code}.").format(code=data["status"])
|
||||
)
|
||||
|
||||
@_imgur.command(name="subreddit")
|
||||
async def imgur_subreddit(
|
||||
self, ctx, subreddit: str, sort_type: str = "top", window: str = "day"
|
||||
):
|
||||
"""Gets images from the specified subreddit section
|
||||
"""Get images from a subreddit.
|
||||
|
||||
Sort types: new, top
|
||||
Time windows: day, week, month, year, all"""
|
||||
You can customize the search with the following options:
|
||||
- `<sort_type>`: new, top
|
||||
- `<window>`: day, week, month, year, all
|
||||
"""
|
||||
sort_type = sort_type.lower()
|
||||
window = window.lower()
|
||||
|
||||
if sort_type not in ("new", "top"):
|
||||
await ctx.send(_("Only 'new' and 'top' are a valid sort type."))
|
||||
return
|
||||
elif window not in ("day", "week", "month", "year", "all"):
|
||||
await ctx.send_help()
|
||||
return
|
||||
|
||||
if sort_type == "new":
|
||||
sort = "time"
|
||||
elif sort_type == "top":
|
||||
sort = "top"
|
||||
else:
|
||||
await ctx.send(_("Only 'new' and 'top' are a valid sort type."))
|
||||
return
|
||||
|
||||
if window not in ("day", "week", "month", "year", "all"):
|
||||
await ctx.send_help()
|
||||
return
|
||||
|
||||
imgur_client_id = await self.settings.imgur_client_id()
|
||||
if not imgur_client_id:
|
||||
await ctx.send(
|
||||
_("A client ID has not been set! Please set one with {}.").format(
|
||||
"`{}imgurcreds`".format(ctx.prefix)
|
||||
)
|
||||
_(
|
||||
"A Client ID has not been set! Please set one with `{prefix}imgurcreds`."
|
||||
).format(prefix=ctx.prefix)
|
||||
)
|
||||
return
|
||||
|
||||
@ -117,29 +124,33 @@ class Image(commands.Cog):
|
||||
else:
|
||||
await ctx.send(_("No results found."))
|
||||
else:
|
||||
await ctx.send(_("Something went wrong. Error code is {}.").format(data["status"]))
|
||||
await ctx.send(
|
||||
_("Something went wrong. Error code is {code}.").format(code=data["status"])
|
||||
)
|
||||
|
||||
@checks.is_owner()
|
||||
@commands.command()
|
||||
async def imgurcreds(self, ctx, imgur_client_id: str):
|
||||
"""Sets the imgur client id
|
||||
"""Set the Imgur Client ID.
|
||||
|
||||
You will need an account on Imgur to get this
|
||||
|
||||
You can get these by visiting https://api.imgur.com/oauth2/addclient
|
||||
and filling out the form. Enter a name for the application, select
|
||||
'Anonymous usage without user authorization' for the auth type,
|
||||
set the authorization callback url to 'https://localhost'
|
||||
leave the app website blank, enter a valid email address, and
|
||||
enter a description. Check the box for the captcha, then click Next.
|
||||
Your client ID will be on the page that loads."""
|
||||
To get an Imgur Client ID:
|
||||
1. Login to an Imgur account.
|
||||
2. Visit [this](https://api.imgur.com/oauth2/addclient) page
|
||||
3. Enter a name for your application
|
||||
4. Select *Anonymous usage without user authorization* for the auth type
|
||||
5. Set the authorization callback URL to `https://localhost`
|
||||
6. Leave the app website blank
|
||||
7. Enter a valid email address and a description
|
||||
8. Check the captcha box and click next
|
||||
9. Your Client ID will be on the next page.
|
||||
"""
|
||||
await self.settings.imgur_client_id.set(imgur_client_id)
|
||||
await ctx.send(_("Set the imgur client id!"))
|
||||
await ctx.send(_("The Imgur Client ID has been set!"))
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.command()
|
||||
async def gif(self, ctx, *keywords):
|
||||
"""Retrieves first search result from giphy"""
|
||||
"""Retrieve the first search result from Giphy."""
|
||||
if keywords:
|
||||
keywords = "+".join(keywords)
|
||||
else:
|
||||
@ -158,12 +169,12 @@ class Image(commands.Cog):
|
||||
else:
|
||||
await ctx.send(_("No results found."))
|
||||
else:
|
||||
await ctx.send(_("Error contacting the API."))
|
||||
await ctx.send(_("Error contacting the Giphy API."))
|
||||
|
||||
@commands.guild_only()
|
||||
@commands.command()
|
||||
async def gifr(self, ctx, *keywords):
|
||||
"""Retrieves a random gif from a giphy search"""
|
||||
"""Retrieve a random GIF from a Giphy search."""
|
||||
if keywords:
|
||||
keywords = "+".join(keywords)
|
||||
else:
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
from redbot.core import commands
|
||||
import discord
|
||||
|
||||
|
||||
def mod_or_voice_permissions(**perms):
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import asyncio
|
||||
import contextlib
|
||||
from datetime import datetime, timedelta
|
||||
from collections import deque, defaultdict, namedtuple
|
||||
from typing import cast
|
||||
|
||||
import discord
|
||||
|
||||
@ -14,7 +16,7 @@ from .log import log
|
||||
|
||||
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
|
||||
|
||||
_ = Translator("Mod", __file__)
|
||||
_ = T_ = Translator("Mod", __file__)
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
@ -58,7 +60,8 @@ class Mod(commands.Cog):
|
||||
self.registration_task.cancel()
|
||||
self.tban_expiry_task.cancel()
|
||||
|
||||
async def _casetype_registration(self):
|
||||
@staticmethod
|
||||
async def _casetype_registration():
|
||||
casetypes_to_register = [
|
||||
{
|
||||
"name": "ban",
|
||||
@ -168,7 +171,7 @@ class Mod(commands.Cog):
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(administrator=True)
|
||||
async def modset(self, ctx: commands.Context):
|
||||
"""Manages server administration settings."""
|
||||
"""Manage server administration settings."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
guild = ctx.guild
|
||||
# Display current settings
|
||||
@ -178,23 +181,37 @@ class Mod(commands.Cog):
|
||||
delete_delay = await self.settings.guild(guild).delete_delay()
|
||||
reinvite_on_unban = await self.settings.guild(guild).reinvite_on_unban()
|
||||
msg = ""
|
||||
msg += "Delete repeats: {}\n".format("Yes" if delete_repeats else "No")
|
||||
msg += "Ban mention spam: {}\n".format(
|
||||
"{} mentions".format(ban_mention_spam)
|
||||
if isinstance(ban_mention_spam, int)
|
||||
else "No"
|
||||
msg += _("Delete repeats: {yes_or_no}\n").format(
|
||||
yes_or_no=_("Yes") if delete_repeats else _("No")
|
||||
)
|
||||
msg += "Respects hierarchy: {}\n".format("Yes" if respect_hierarchy else "No")
|
||||
msg += "Delete delay: {}\n".format(
|
||||
"{} seconds".format(delete_delay) if delete_delay != -1 else "None"
|
||||
msg += _("Ban mention spam: {num_mentions}\n").format(
|
||||
num_mentions=_("{num} mentions").format(num=ban_mention_spam)
|
||||
if ban_mention_spam
|
||||
else _("No")
|
||||
)
|
||||
msg += _("Respects hierarchy: {yes_or_no}\n").format(
|
||||
yes_or_no=_("Yes") if respect_hierarchy else _("No")
|
||||
)
|
||||
msg += _("Delete delay: {num_seconds}\n").format(
|
||||
num_seconds=_("{num} seconds").format(delete_delay)
|
||||
if delete_delay != -1
|
||||
else _("None")
|
||||
)
|
||||
msg += _("Reinvite on unban: {yes_or_no}\n").format(
|
||||
yes_or_no=_("Yes") if reinvite_on_unban else _("No")
|
||||
)
|
||||
msg += "Reinvite on unban: {}".format("Yes" if reinvite_on_unban else "No")
|
||||
await ctx.send(box(msg))
|
||||
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def hierarchy(self, ctx: commands.Context):
|
||||
"""Toggles role hierarchy check for mods / admins"""
|
||||
"""Toggle role hierarchy check for mods and admins.
|
||||
|
||||
**WARNING**: Disabling this setting will allow mods to take
|
||||
actions on users above them in the role hierarchy!
|
||||
|
||||
This is enabled by default.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
toggled = await self.settings.guild(guild).respect_hierarchy()
|
||||
if not toggled:
|
||||
@ -210,10 +227,14 @@ class Mod(commands.Cog):
|
||||
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def banmentionspam(self, ctx: commands.Context, max_mentions: int = False):
|
||||
"""Enables auto ban for messages mentioning X different people
|
||||
async def banmentionspam(self, ctx: commands.Context, max_mentions: int = 0):
|
||||
"""Set the autoban conditions for mention spam.
|
||||
|
||||
Accepted values: 5 or superior"""
|
||||
Users will be banned if they send any message which contains more than
|
||||
`<max_mentions>` mentions.
|
||||
|
||||
`<max_mentions>` must be at least 5. Set to 0 to disable.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
if max_mentions:
|
||||
if max_mentions < 5:
|
||||
@ -222,13 +243,13 @@ class Mod(commands.Cog):
|
||||
await ctx.send(
|
||||
_(
|
||||
"Autoban for mention spam enabled. "
|
||||
"Anyone mentioning {} or more different people "
|
||||
"Anyone mentioning {max_mentions} or more different people "
|
||||
"in a single message will be autobanned."
|
||||
).format(max_mentions)
|
||||
).format(max_mentions=max_mentions)
|
||||
)
|
||||
else:
|
||||
cur_setting = await self.settings.guild(guild).ban_mention_spam()
|
||||
if cur_setting is False:
|
||||
if not cur_setting:
|
||||
await ctx.send_help()
|
||||
return
|
||||
await self.settings.guild(guild).ban_mention_spam.set(False)
|
||||
@ -237,7 +258,7 @@ class Mod(commands.Cog):
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def deleterepeats(self, ctx: commands.Context):
|
||||
"""Enables auto deletion of repeated messages"""
|
||||
"""Enable auto-deletion of repeated messages."""
|
||||
guild = ctx.guild
|
||||
cur_setting = await self.settings.guild(guild).delete_repeats()
|
||||
if not cur_setting:
|
||||
@ -250,11 +271,12 @@ class Mod(commands.Cog):
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def deletedelay(self, ctx: commands.Context, time: int = None):
|
||||
"""Sets the delay until the bot removes the command message.
|
||||
"""Set the delay until the bot removes the command message.
|
||||
|
||||
Must be between -1 and 60.
|
||||
|
||||
A delay of -1 means the bot will not remove the message."""
|
||||
Set to -1 to disable this feature.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
if time is not None:
|
||||
time = min(max(time, -1), 60) # Enforces the time limits
|
||||
@ -262,16 +284,16 @@ class Mod(commands.Cog):
|
||||
if time == -1:
|
||||
await ctx.send(_("Command deleting disabled."))
|
||||
else:
|
||||
await ctx.send(_("Delete delay set to {} seconds.").format(time))
|
||||
await ctx.send(_("Delete delay set to {num} seconds.").format(num=time))
|
||||
else:
|
||||
delay = await self.settings.guild(guild).delete_delay()
|
||||
if delay != -1:
|
||||
await ctx.send(
|
||||
_(
|
||||
"Bot will delete command messages after"
|
||||
" {} seconds. Set this value to -1 to"
|
||||
" {num} seconds. Set this value to -1 to"
|
||||
" stop deleting messages"
|
||||
).format(delay)
|
||||
).format(num=delay)
|
||||
)
|
||||
else:
|
||||
await ctx.send(_("I will not delete command messages."))
|
||||
@ -279,33 +301,44 @@ class Mod(commands.Cog):
|
||||
@modset.command()
|
||||
@commands.guild_only()
|
||||
async def reinvite(self, ctx: commands.Context):
|
||||
"""Toggles whether an invite will be sent when a user is unbanned via [p]unban.
|
||||
"""Toggle whether an invite will be sent to a user when unbanned.
|
||||
|
||||
If this is True, the bot will attempt to create and send a single-use invite
|
||||
to the newly-unbanned user"""
|
||||
to the newly-unbanned user.
|
||||
"""
|
||||
guild = ctx.guild
|
||||
cur_setting = await self.settings.guild(guild).reinvite_on_unban()
|
||||
if not cur_setting:
|
||||
await self.settings.guild(guild).reinvite_on_unban.set(True)
|
||||
await ctx.send(_("Users unbanned with {} will be reinvited.").format("[p]unban"))
|
||||
await ctx.send(
|
||||
_("Users unbanned with {command} will be reinvited.").format(f"{ctx.prefix}unban")
|
||||
)
|
||||
else:
|
||||
await self.settings.guild(guild).reinvite_on_unban.set(False)
|
||||
await ctx.send(_("Users unbanned with {} will not be reinvited.").format("[p]unban"))
|
||||
await ctx.send(
|
||||
_("Users unbanned with {command} will not be reinvited.").format(
|
||||
f"{ctx.prefix}unban"
|
||||
)
|
||||
)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(kick_members=True)
|
||||
@checks.admin_or_permissions(kick_members=True)
|
||||
async def kick(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||
"""Kicks user.
|
||||
"""Kick a user.
|
||||
|
||||
If a reason is specified, it will be the reason that shows up
|
||||
in the audit log"""
|
||||
in the audit log.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
if author == user:
|
||||
await ctx.send(
|
||||
_("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}")
|
||||
_("I cannot let you do that. Self-harm is bad {emoji}").format(
|
||||
emoji="\N{PENSIVE FACE}"
|
||||
)
|
||||
)
|
||||
return
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||
@ -348,14 +381,18 @@ class Mod(commands.Cog):
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def ban(
|
||||
self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None
|
||||
):
|
||||
"""Bans user and deletes last X days worth of messages.
|
||||
"""Ban a user from this server.
|
||||
|
||||
If days is not a number, it's treated as the first word of the reason.
|
||||
Minimum 0 days, maximum 7. Defaults to 0."""
|
||||
Deletes `<days>` worth of messages.
|
||||
|
||||
If `<days>` is not a number, it's treated as the first word of
|
||||
the reason. Minimum 0 days, maximum 7. Defaults to 0.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
|
||||
@ -429,16 +466,16 @@ class Mod(commands.Cog):
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@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):
|
||||
"""Preemptively bans user from the server
|
||||
"""Pre-emptively ban a user from this server.
|
||||
|
||||
A user ID needs to be provided in order to ban
|
||||
using this command"""
|
||||
using this command.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
if not guild.me.guild_permissions.ban_members:
|
||||
return await ctx.send(_("I lack the permissions to do this."))
|
||||
is_banned = False
|
||||
ban_list = await guild.bans()
|
||||
for entry in ban_list:
|
||||
@ -489,75 +526,77 @@ class Mod(commands.Cog):
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def tempban(
|
||||
self, ctx: commands.Context, user: discord.Member, days: int = 1, *, reason: str = None
|
||||
):
|
||||
"""Tempbans the user for the specified number of days"""
|
||||
"""Temporarily ban a user from this server."""
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
days_delta = timedelta(days=int(days))
|
||||
unban_time = datetime.utcnow() + days_delta
|
||||
channel = ctx.channel
|
||||
can_ban = channel.permissions_for(guild.me).ban_members
|
||||
|
||||
invite = await self.get_invite_for_reinvite(ctx, int(days_delta.total_seconds() + 86400))
|
||||
if invite is None:
|
||||
invite = ""
|
||||
|
||||
if can_ban:
|
||||
queue_entry = (guild.id, user.id)
|
||||
await self.settings.member(user).banned_until.set(unban_time.timestamp())
|
||||
cur_tbans = await self.settings.guild(guild).current_tempbans()
|
||||
cur_tbans.append(user.id)
|
||||
await self.settings.guild(guild).current_tempbans.set(cur_tbans)
|
||||
queue_entry = (guild.id, user.id)
|
||||
await self.settings.member(user).banned_until.set(unban_time.timestamp())
|
||||
cur_tbans = await self.settings.guild(guild).current_tempbans()
|
||||
cur_tbans.append(user.id)
|
||||
await self.settings.guild(guild).current_tempbans.set(cur_tbans)
|
||||
|
||||
try: # We don't want blocked DMs preventing us from banning
|
||||
msg = await user.send(
|
||||
_(
|
||||
"You have been temporarily banned from {} until {}. "
|
||||
"Here is an invite for when your ban expires: {}"
|
||||
).format(guild.name, unban_time.strftime("%m-%d-%Y %H:%M:%S"), invite)
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
# We don't want blocked DMs preventing us from banning
|
||||
await user.send(
|
||||
_(
|
||||
"You have been temporarily banned from {server_name} until {date}. "
|
||||
"Here is an invite for when your ban expires: {invite_link}"
|
||||
).format(
|
||||
server_name=guild.name,
|
||||
date=unban_time.strftime("%m-%d-%Y %H:%M:%S"),
|
||||
invite_link=invite,
|
||||
)
|
||||
except discord.HTTPException:
|
||||
msg = None
|
||||
self.ban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.ban(user)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(_("I can't do that for some reason."))
|
||||
except discord.HTTPException:
|
||||
await ctx.send(_("Something went wrong while banning"))
|
||||
else:
|
||||
await ctx.send(_("Done. Enough chaos for now"))
|
||||
)
|
||||
self.ban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.ban(user)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(_("I can't do that for some reason."))
|
||||
except discord.HTTPException:
|
||||
await ctx.send(_("Something went wrong while banning"))
|
||||
else:
|
||||
await ctx.send(_("Done. Enough chaos for now"))
|
||||
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"tempban",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
unban_time,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"tempban",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
unban_time,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(ban_members=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def softban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||
"""Kicks the user, deleting 1 day worth of messages."""
|
||||
"""Kick a user and delete 1 day's worth of their messages."""
|
||||
guild = ctx.guild
|
||||
channel = ctx.channel
|
||||
can_ban = channel.permissions_for(guild.me).ban_members
|
||||
author = ctx.author
|
||||
|
||||
if author == user:
|
||||
await ctx.send(
|
||||
_("I cannot let you do that. Self-harm is bad {}").format("\N{PENSIVE FACE}")
|
||||
_("I cannot let you do that. Self-harm is bad {emoji}").format(
|
||||
emoji="\N{PENSIVE FACE}"
|
||||
)
|
||||
)
|
||||
return
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||
@ -576,75 +615,69 @@ class Mod(commands.Cog):
|
||||
if invite is None:
|
||||
invite = ""
|
||||
|
||||
if can_ban:
|
||||
queue_entry = (guild.id, user.id)
|
||||
try: # We don't want blocked DMs preventing us from banning
|
||||
msg = await user.send(
|
||||
_(
|
||||
"You have been banned and "
|
||||
"then unbanned as a quick way to delete your messages.\n"
|
||||
"You can now join the server again. {}"
|
||||
).format(invite)
|
||||
)
|
||||
except discord.HTTPException:
|
||||
msg = None
|
||||
self.ban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.ban(user, reason=audit_reason, delete_message_days=1)
|
||||
except discord.errors.Forbidden:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
await ctx.send(_("My role is not high enough to softban that user."))
|
||||
if msg is not None:
|
||||
await msg.delete()
|
||||
return
|
||||
except discord.HTTPException as e:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
print(e)
|
||||
return
|
||||
self.unban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.unban(user)
|
||||
except discord.HTTPException as e:
|
||||
self.unban_queue.remove(queue_entry)
|
||||
print(e)
|
||||
return
|
||||
else:
|
||||
await ctx.send(_("Done. Enough chaos."))
|
||||
log.info(
|
||||
"{}({}) softbanned {}({}), deleting 1 day worth "
|
||||
"of messages".format(author.name, author.id, user.name, user.id)
|
||||
)
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"softban",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=None,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
queue_entry = (guild.id, user.id)
|
||||
try: # We don't want blocked DMs preventing us from banning
|
||||
msg = await user.send(
|
||||
_(
|
||||
"You have been banned and "
|
||||
"then unbanned as a quick way to delete your messages.\n"
|
||||
"You can now join the server again. {invite_link}"
|
||||
).format(invite_link=invite)
|
||||
)
|
||||
except discord.HTTPException:
|
||||
msg = None
|
||||
self.ban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.ban(user, reason=audit_reason, delete_message_days=1)
|
||||
except discord.errors.Forbidden:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
await ctx.send(_("My role is not high enough to softban that user."))
|
||||
if msg is not None:
|
||||
await msg.delete()
|
||||
return
|
||||
except discord.HTTPException as e:
|
||||
self.ban_queue.remove(queue_entry)
|
||||
print(e)
|
||||
return
|
||||
self.unban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.unban(user)
|
||||
except discord.HTTPException as e:
|
||||
self.unban_queue.remove(queue_entry)
|
||||
print(e)
|
||||
return
|
||||
else:
|
||||
await ctx.send(_("I'm not allowed to do that."))
|
||||
await ctx.send(_("Done. Enough chaos."))
|
||||
log.info(
|
||||
"{}({}) softbanned {}({}), deleting 1 day worth "
|
||||
"of messages".format(author.name, author.id, user.name, user.id)
|
||||
)
|
||||
try:
|
||||
await modlog.create_case(
|
||||
self.bot,
|
||||
guild,
|
||||
ctx.message.created_at,
|
||||
"softban",
|
||||
user,
|
||||
author,
|
||||
reason,
|
||||
until=None,
|
||||
channel=None,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
await ctx.send(e)
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@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):
|
||||
"""Unbans the target user.
|
||||
"""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
|
||||
2. enable developer mode, go to Bans in this server's settings, right-
|
||||
click the user and select 'Copy ID'."""
|
||||
channel = ctx.channel
|
||||
if not channel.permissions_for(ctx.guild.me).ban_members:
|
||||
await ctx.send("I need the Ban Members permission to do this.")
|
||||
return
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
user = await self.bot.get_user_info(user_id)
|
||||
@ -687,26 +720,26 @@ class Mod(commands.Cog):
|
||||
invite = await self.get_invite_for_reinvite(ctx)
|
||||
if invite:
|
||||
try:
|
||||
user.send(
|
||||
await user.send(
|
||||
_(
|
||||
"You've been unbanned from {}.\n"
|
||||
"Here is an invite for that server: {}"
|
||||
).format(guild.name, invite.url)
|
||||
"You've been unbanned from {server}.\n"
|
||||
"Here is an invite for that server: {invite_link}"
|
||||
).format(server=guild.name, invite_link=invite.url)
|
||||
)
|
||||
except discord.Forbidden:
|
||||
await ctx.send(
|
||||
_(
|
||||
"I failed to send an invite to that user. "
|
||||
"Perhaps you may be able to send it for me?\n"
|
||||
"Here's the invite link: {}"
|
||||
).format(invite.url)
|
||||
"Here's the invite link: {invite_link}"
|
||||
).format(invite_link=invite.url)
|
||||
)
|
||||
except discord.HTTPException:
|
||||
await ctx.send(
|
||||
_(
|
||||
"Something went wrong when attempting to send that user"
|
||||
"an invite. Here's the link so you can try: {}"
|
||||
).format(invite.url)
|
||||
"an invite. Here's the link so you can try: {invite_link}"
|
||||
).format(invite_link=invite.url)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@ -750,7 +783,7 @@ class Mod(commands.Cog):
|
||||
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
||||
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
|
||||
async def voiceban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||
"""Bans the target user from speaking and listening in voice channels in the server"""
|
||||
"""Ban a user from speaking and listening in the server's voice channels."""
|
||||
user_voice_state = user.voice
|
||||
if user_voice_state is None:
|
||||
await ctx.send(_("No voice state for that user!"))
|
||||
@ -791,7 +824,7 @@ class Mod(commands.Cog):
|
||||
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
||||
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
|
||||
async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||
"""Unbans the user from speaking/listening in the server's voice channels"""
|
||||
"""Unban a the user from speaking and listening in the server's voice channels."""
|
||||
user_voice_state = user.voice
|
||||
if user_voice_state is None:
|
||||
await ctx.send(_("No voice state for that user!"))
|
||||
@ -828,27 +861,24 @@ class Mod(commands.Cog):
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_nicknames=True)
|
||||
@checks.admin_or_permissions(manage_nicknames=True)
|
||||
async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname=""):
|
||||
"""Changes user's nickname
|
||||
"""Change a user's nickname.
|
||||
|
||||
Leaving the nickname empty will remove it."""
|
||||
Leaving the nickname empty will remove it.
|
||||
"""
|
||||
nickname = nickname.strip()
|
||||
if nickname == "":
|
||||
nickname = None
|
||||
try:
|
||||
await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
|
||||
await ctx.send("Done.")
|
||||
except discord.Forbidden:
|
||||
await ctx.send(
|
||||
_("I cannot do that, I lack the '{}' permission.").format("Manage Nicknames")
|
||||
)
|
||||
await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
|
||||
await ctx.send("Done.")
|
||||
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@checks.mod_or_permissions(manage_channel=True)
|
||||
async def mute(self, ctx: commands.Context):
|
||||
"""Mutes user in the channel/server"""
|
||||
"""Mute users."""
|
||||
pass
|
||||
|
||||
@mute.command(name="voice")
|
||||
@ -856,7 +886,7 @@ class Mod(commands.Cog):
|
||||
@mod_or_voice_permissions(mute_members=True)
|
||||
@bot_has_voice_permissions(mute_members=True)
|
||||
async def voice_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||
"""Mutes the user in a voice channel"""
|
||||
"""Mute a user in their current voice channel."""
|
||||
user_voice_state = user.voice
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
@ -868,9 +898,7 @@ class Mod(commands.Cog):
|
||||
audit_reason = get_audit_reason(ctx.author, reason)
|
||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
||||
await ctx.send(
|
||||
_("Muted {}#{} in channel {}").format(
|
||||
user.name, user.discriminator, channel.name
|
||||
)
|
||||
_("Muted {user} in channel {channel.name}").format(user, channel=channel)
|
||||
)
|
||||
try:
|
||||
await modlog.create_case(
|
||||
@ -888,7 +916,9 @@ class Mod(commands.Cog):
|
||||
await ctx.send(e)
|
||||
return
|
||||
elif channel.permissions_for(user).speak is False:
|
||||
await ctx.send(_("That user is already muted in {}!").format(channel.name))
|
||||
await ctx.send(
|
||||
_("That user is already muted in {channel}!").format(channel=channel.name)
|
||||
)
|
||||
return
|
||||
else:
|
||||
await ctx.send(_("That user is not in a voice channel right now!"))
|
||||
@ -896,22 +926,23 @@ class Mod(commands.Cog):
|
||||
await ctx.send(_("No voice state for the target!"))
|
||||
return
|
||||
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
@mute.command(name="channel")
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_roles=True)
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def channel_mute(
|
||||
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Mutes user in the current channel"""
|
||||
"""Mute a user in the current text channel."""
|
||||
author = ctx.message.author
|
||||
channel = ctx.message.channel
|
||||
guild = ctx.guild
|
||||
|
||||
if reason is None:
|
||||
audit_reason = "Channel mute requested by {} (ID {})".format(author, author.id)
|
||||
audit_reason = "Channel mute requested by {a} (ID {a.id})".format(a=author)
|
||||
else:
|
||||
audit_reason = "Channel mute requested by {} (ID {}). Reason: {}".format(
|
||||
author, author.id, reason
|
||||
audit_reason = "Channel mute requested by {a} (ID {a.id}). Reason: {r}".format(
|
||||
a=author, r=reason
|
||||
)
|
||||
|
||||
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||
@ -935,20 +966,22 @@ class Mod(commands.Cog):
|
||||
else:
|
||||
await channel.send(issue)
|
||||
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
@mute.command(name="server", aliases=["guild"])
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_roles=True)
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def guild_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||
"""Mutes user in the server"""
|
||||
author = ctx.message.author
|
||||
guild = ctx.guild
|
||||
user_voice_state = user.voice
|
||||
if reason is None:
|
||||
audit_reason = "server mute requested by {} (ID {})".format(author, author.id)
|
||||
else:
|
||||
audit_reason = "server mute requested by {} (ID {}). Reason: {}".format(
|
||||
author, author.id, reason
|
||||
audit_reason = "server mute requested by {author} (ID {author.id})".format(
|
||||
author=author
|
||||
)
|
||||
else:
|
||||
audit_reason = (
|
||||
"server mute requested by {author} (ID {author.id}). Reason: {reason}"
|
||||
).format(author=author, reason=reason)
|
||||
|
||||
mute_success = []
|
||||
for channel in guild.channels:
|
||||
@ -992,10 +1025,10 @@ class Mod(commands.Cog):
|
||||
perms_cache = await self.settings.member(user).perms_cache()
|
||||
|
||||
if overwrites.send_messages is False or permissions.send_messages is False:
|
||||
return False, mute_unmute_issues["already_muted"]
|
||||
return False, T_(mute_unmute_issues["already_muted"])
|
||||
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||
return False, mute_unmute_issues["hierarchy_problem"]
|
||||
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
||||
|
||||
perms_cache[str(channel.id)] = {
|
||||
"send_messages": overwrites.send_messages,
|
||||
@ -1005,28 +1038,27 @@ class Mod(commands.Cog):
|
||||
try:
|
||||
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
|
||||
except discord.Forbidden:
|
||||
return False, mute_unmute_issues["permissions_issue"]
|
||||
return False, T_(mute_unmute_issues["permissions_issue"])
|
||||
else:
|
||||
await self.settings.member(user).perms_cache.set(perms_cache)
|
||||
return True, None
|
||||
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_roles=True)
|
||||
@checks.mod_or_permissions(manage_channel=True)
|
||||
async def unmute(self, ctx: commands.Context):
|
||||
"""Unmutes user in the channel/server
|
||||
|
||||
Defaults to channel"""
|
||||
"""Unmute users."""
|
||||
pass
|
||||
|
||||
@unmute.command(name="voice")
|
||||
@commands.guild_only()
|
||||
@mod_or_voice_permissions(mute_members=True)
|
||||
@bot_has_voice_permissions(mute_members=True)
|
||||
async def voice_unmute(
|
||||
async def unmute_voice(
|
||||
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Unmutes the user in a voice channel"""
|
||||
"""Unmute a user in their current voice channel."""
|
||||
user_voice_state = user.voice
|
||||
if user_voice_state:
|
||||
channel = user_voice_state.channel
|
||||
@ -1067,11 +1099,12 @@ class Mod(commands.Cog):
|
||||
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
@unmute.command(name="channel")
|
||||
@commands.bot_has_permissions(manage_roles=True)
|
||||
@commands.guild_only()
|
||||
async def channel_unmute(
|
||||
async def unmute_channel(
|
||||
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Unmutes user in the current channel"""
|
||||
"""Unmute a user in this channel."""
|
||||
channel = ctx.channel
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
@ -1099,14 +1132,14 @@ class Mod(commands.Cog):
|
||||
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
@unmute.command(name="server", aliases=["guild"])
|
||||
@commands.bot_has_permissions(manage_roles=True)
|
||||
@commands.guild_only()
|
||||
async def guild_unmute(
|
||||
async def unmute_guild(
|
||||
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
||||
):
|
||||
"""Unmutes user in the server"""
|
||||
"""Unmute a user in this server."""
|
||||
guild = ctx.guild
|
||||
author = ctx.author
|
||||
channel = ctx.channel
|
||||
|
||||
unmute_success = []
|
||||
for channel in guild.channels:
|
||||
@ -1146,10 +1179,10 @@ class Mod(commands.Cog):
|
||||
perms_cache = await self.settings.member(user).perms_cache()
|
||||
|
||||
if overwrites.send_messages or permissions.send_messages:
|
||||
return False, mute_unmute_issues["already_unmuted"]
|
||||
return False, T_(mute_unmute_issues["already_unmuted"])
|
||||
|
||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
||||
return False, mute_unmute_issues["hierarchy_problem"]
|
||||
return False, T_(mute_unmute_issues["hierarchy_problem"])
|
||||
|
||||
if channel.id in perms_cache:
|
||||
old_values = perms_cache[channel.id]
|
||||
@ -1164,9 +1197,11 @@ class Mod(commands.Cog):
|
||||
if not is_empty:
|
||||
await channel.set_permissions(user, overwrite=overwrites)
|
||||
else:
|
||||
await channel.set_permissions(user, overwrite=None)
|
||||
await channel.set_permissions(
|
||||
user, overwrite=cast(discord.PermissionOverwrite, None)
|
||||
)
|
||||
except discord.Forbidden:
|
||||
return False, mute_unmute_issues["permissions_issue"]
|
||||
return False, T_(mute_unmute_issues["permissions_issue"])
|
||||
else:
|
||||
try:
|
||||
del perms_cache[channel.id]
|
||||
@ -1180,15 +1215,16 @@ class Mod(commands.Cog):
|
||||
@commands.guild_only()
|
||||
@checks.admin_or_permissions(manage_channels=True)
|
||||
async def ignore(self, ctx: commands.Context):
|
||||
"""Adds servers/channels to ignorelist"""
|
||||
"""Add servers or channels to the ignore list."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send(await self.count_ignored())
|
||||
|
||||
@ignore.command(name="channel")
|
||||
async def ignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None):
|
||||
"""Ignores channel
|
||||
"""Ignore commands in the channel.
|
||||
|
||||
Defaults to current one"""
|
||||
Defaults to the current channel.
|
||||
"""
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
if not await self.settings.channel(channel).ignored():
|
||||
@ -1200,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):
|
||||
"""Ignores 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)
|
||||
@ -1212,15 +1248,16 @@ class Mod(commands.Cog):
|
||||
@commands.guild_only()
|
||||
@checks.admin_or_permissions(manage_channels=True)
|
||||
async def unignore(self, ctx: commands.Context):
|
||||
"""Removes servers/channels from ignorelist"""
|
||||
"""Remove servers or channels from the ignore list."""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.send(await self.count_ignored())
|
||||
|
||||
@unignore.command(name="channel")
|
||||
async def unignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None):
|
||||
"""Removes channel from ignore list
|
||||
"""Remove a channel from ignore the list.
|
||||
|
||||
Defaults to current one"""
|
||||
Defaults to the current channel.
|
||||
"""
|
||||
if not channel:
|
||||
channel = ctx.channel
|
||||
|
||||
@ -1233,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):
|
||||
"""Removes current guild from 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)
|
||||
@ -1258,7 +1295,8 @@ class Mod(commands.Cog):
|
||||
"""Global check to see if a channel or server is ignored.
|
||||
|
||||
Any users who have permission to use the `ignore` or `unignore` commands
|
||||
surpass the check."""
|
||||
surpass the check.
|
||||
"""
|
||||
perms = ctx.channel.permissions_for(ctx.author)
|
||||
surpass_ignore = (
|
||||
isinstance(ctx.channel, discord.abc.PrivateChannel)
|
||||
@ -1274,14 +1312,15 @@ class Mod(commands.Cog):
|
||||
|
||||
@commands.command()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(embed_links=True)
|
||||
async def userinfo(self, ctx, *, user: discord.Member = None):
|
||||
"""Shows information for a user.
|
||||
"""Show information about a user.
|
||||
|
||||
This includes fields for status, discord join date, server
|
||||
join date, voice state and previous names/nicknames.
|
||||
|
||||
If the user has none of roles, previous names or previous
|
||||
nicknames, these fields will be omitted.
|
||||
If the user has no roles, previous names or previous nicknames,
|
||||
these fields will be omitted.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
@ -1357,14 +1396,11 @@ class Mod(commands.Cog):
|
||||
else:
|
||||
data.set_author(name=name)
|
||||
|
||||
try:
|
||||
await ctx.send(embed=data)
|
||||
except discord.HTTPException:
|
||||
await ctx.send(_("I need the `Embed links` permission to send this."))
|
||||
await ctx.send(embed=data)
|
||||
|
||||
@commands.command()
|
||||
async def names(self, ctx: commands.Context, user: discord.Member):
|
||||
"""Show previous names/nicknames of a user"""
|
||||
"""Show previous names and nicknames of a user."""
|
||||
names, nicks = await self.get_names_and_nicks(user)
|
||||
msg = ""
|
||||
if names:
|
||||
@ -1407,7 +1443,7 @@ class Mod(commands.Cog):
|
||||
queue_entry = (guild.id, user.id)
|
||||
self.unban_queue.append(queue_entry)
|
||||
try:
|
||||
await guild.unban(user, reason="Tempban finished")
|
||||
await guild.unban(user, reason=_("Tempban finished"))
|
||||
guild_tempbans.remove(uid)
|
||||
except discord.Forbidden:
|
||||
self.unban_queue.remove(queue_entry)
|
||||
@ -1437,12 +1473,12 @@ class Mod(commands.Cog):
|
||||
guild = message.guild
|
||||
author = message.author
|
||||
|
||||
if await self.settings.guild(guild).ban_mention_spam():
|
||||
max_mentions = await self.settings.guild(guild).ban_mention_spam()
|
||||
max_mentions = await self.settings.guild(guild).ban_mention_spam()
|
||||
if max_mentions:
|
||||
mentions = set(message.mentions)
|
||||
if len(mentions) >= max_mentions:
|
||||
try:
|
||||
await guild.ban(author, reason="Mention spam (Autoban)")
|
||||
await guild.ban(author, reason=_("Mention spam (Autoban)"))
|
||||
except discord.HTTPException:
|
||||
log.info(
|
||||
"Failed to ban member for mention spam in server {}.".format(guild.id)
|
||||
@ -1456,7 +1492,7 @@ class Mod(commands.Cog):
|
||||
"ban",
|
||||
author,
|
||||
guild.me,
|
||||
"Mention spam (Autoban)",
|
||||
_("Mention spam (Autoban)"),
|
||||
until=None,
|
||||
channel=None,
|
||||
)
|
||||
@ -1469,6 +1505,7 @@ class Mod(commands.Cog):
|
||||
async def on_command_completion(self, ctx: commands.Context):
|
||||
await self._delete_delay(ctx)
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
async def on_command_error(self, ctx: commands.Context, error):
|
||||
await self._delete_delay(ctx)
|
||||
|
||||
@ -1485,11 +1522,9 @@ class Mod(commands.Cog):
|
||||
return
|
||||
|
||||
async def _delete_helper(m):
|
||||
try:
|
||||
with contextlib.suppress(discord.HTTPException):
|
||||
await m.delete()
|
||||
log.debug("Deleted command msg {}".format(m.id))
|
||||
except:
|
||||
pass # We don't really care if it fails or not
|
||||
|
||||
await asyncio.sleep(delay)
|
||||
await _delete_helper(message)
|
||||
@ -1511,7 +1546,7 @@ class Mod(commands.Cog):
|
||||
return
|
||||
deleted = await self.check_duplicates(message)
|
||||
if not deleted:
|
||||
deleted = await self.check_mention_spam(message)
|
||||
await self.check_mention_spam(message)
|
||||
|
||||
async def on_member_ban(self, guild: discord.Guild, member: discord.Member):
|
||||
if (guild.id, member.id) in self.ban_queue:
|
||||
@ -1551,7 +1586,8 @@ class Mod(commands.Cog):
|
||||
except RuntimeError as e:
|
||||
print(e)
|
||||
|
||||
async def on_modlog_case_create(self, case: modlog.Case):
|
||||
@staticmethod
|
||||
async def on_modlog_case_create(case: modlog.Case):
|
||||
"""
|
||||
An event for modlog case creation
|
||||
"""
|
||||
@ -1566,7 +1602,8 @@ class Mod(commands.Cog):
|
||||
msg = await mod_channel.send(case_content)
|
||||
await case.edit({"message": msg})
|
||||
|
||||
async def on_modlog_case_edit(self, case: modlog.Case):
|
||||
@staticmethod
|
||||
async def on_modlog_case_edit(case: modlog.Case):
|
||||
"""
|
||||
Event for modlog case edits
|
||||
"""
|
||||
@ -1579,7 +1616,10 @@ class Mod(commands.Cog):
|
||||
else:
|
||||
await case.message.edit(content=case_content)
|
||||
|
||||
async def get_audit_entry_info(self, guild: discord.Guild, action: int, target):
|
||||
@classmethod
|
||||
async def get_audit_entry_info(
|
||||
cls, guild: discord.Guild, action: discord.AuditLogAction, target
|
||||
):
|
||||
"""Get info about an audit log entry.
|
||||
|
||||
Parameters
|
||||
@ -1599,14 +1639,15 @@ class Mod(commands.Cog):
|
||||
if the audit log entry could not be found.
|
||||
"""
|
||||
try:
|
||||
entry = await self.get_audit_log_entry(guild, action=action, target=target)
|
||||
entry = await cls.get_audit_log_entry(guild, action=action, target=target)
|
||||
except discord.HTTPException:
|
||||
entry = None
|
||||
if entry is None:
|
||||
return None, None, None
|
||||
return entry.user, entry.reason, entry.created_at
|
||||
|
||||
async def get_audit_log_entry(self, guild: discord.Guild, action: int, target):
|
||||
@staticmethod
|
||||
async def get_audit_log_entry(guild: discord.Guild, action: discord.AuditLogAction, target):
|
||||
"""Get an audit log entry.
|
||||
|
||||
Any exceptions encountered when looking through the audit log will be
|
||||
@ -1660,12 +1701,16 @@ class Mod(commands.Cog):
|
||||
return [p for p in iter(overwrites)] == [p for p in iter(discord.PermissionOverwrite())]
|
||||
|
||||
|
||||
_ = lambda s: s
|
||||
mute_unmute_issues = {
|
||||
"already_muted": "That user can't send messages in this channel.",
|
||||
"already_unmuted": "That user isn't muted in this channel!",
|
||||
"hierarchy_problem": "I cannot let you do that. You are not higher than "
|
||||
"the user in the role hierarchy.",
|
||||
"permissions_issue": "Failed to mute user. I need the manage roles "
|
||||
"permission and the user I'm muting must be "
|
||||
"lower than myself in the role hierarchy.",
|
||||
"already_muted": _("That user can't send messages in this channel."),
|
||||
"already_unmuted": _("That user isn't muted in this channel!"),
|
||||
"hierarchy_problem": _(
|
||||
"I cannot let you do that. You are not higher than " "the user in the role hierarchy."
|
||||
),
|
||||
"permissions_issue": _(
|
||||
"Failed to mute user. I need the manage roles "
|
||||
"permission and the user I'm muting must be "
|
||||
"lower than myself in the role hierarchy."
|
||||
),
|
||||
}
|
||||
|
||||
@ -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.
|
||||
|
||||
If no number is specified, the latest case will be used."""
|
||||
the owner of unless you are a mod, admin or server owner.
|
||||
|
||||
If no case number is specified, the latest case will be used.
|
||||
"""
|
||||
author = ctx.author
|
||||
guild = ctx.guild
|
||||
potential_case = reason.split()[0]
|
||||
if potential_case.isdigit():
|
||||
case = int(potential_case)
|
||||
reason = reason.replace(potential_case, "")
|
||||
else:
|
||||
case = str(int(await modlog.get_next_case_number(guild)) - 1)
|
||||
# latest case
|
||||
if case is None:
|
||||
# get the latest case
|
||||
case = int(await modlog.get_next_case_number(guild)) - 1
|
||||
try:
|
||||
case_before = await modlog.get_case(case, guild, self.bot)
|
||||
except RuntimeError:
|
||||
|
||||
@ -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,9 +319,8 @@ class Reports(commands.Cog):
|
||||
@checks.mod_or_permissions(manage_members=True)
|
||||
@report.command(name="interact")
|
||||
async def response(self, ctx, ticket_number: int):
|
||||
"""
|
||||
Open a message tunnel.
|
||||
|
||||
"""Open a message tunnel.
|
||||
|
||||
This tunnel will forward things you say in this channel
|
||||
to the ticket opener's direct messages.
|
||||
|
||||
@ -354,8 +349,7 @@ class Reports(commands.Cog):
|
||||
)
|
||||
|
||||
big_topic = _(
|
||||
"{who} opened a 2-way communication "
|
||||
"about ticket number {ticketnum}. Anything you say or upload here "
|
||||
" Anything you say or upload here "
|
||||
"(8MB file size limitation on uploads) "
|
||||
"will be forwarded to them until the communication is closed.\n"
|
||||
"You can close a communication at any point by reacting with "
|
||||
@ -364,8 +358,12 @@ class Reports(commands.Cog):
|
||||
"\N{WHITE HEAVY CHECK MARK}.\n"
|
||||
"Tunnels are not persistent across bot restarts."
|
||||
)
|
||||
topic = big_topic.format(
|
||||
ticketnum=ticket_number, who=_("A moderator in `{guild.name}` has").format(guild=guild)
|
||||
topic = (
|
||||
_(
|
||||
"A moderator in the server `{guild.name}` has opened a 2-way communication about "
|
||||
"ticket number {ticket_number}."
|
||||
).format(guild=guild, ticket_number=ticket_number)
|
||||
+ big_topic
|
||||
)
|
||||
try:
|
||||
m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True)
|
||||
@ -373,4 +371,9 @@ class Reports(commands.Cog):
|
||||
await ctx.send(_("That user has DMs disabled."))
|
||||
else:
|
||||
self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m}
|
||||
await ctx.send(big_topic.format(who=_("You have"), ticketnum=ticket_number))
|
||||
await ctx.send(
|
||||
_(
|
||||
"You have opened a 2-way communication about ticket number {ticket_number}."
|
||||
).format(ticket_number=ticket_number)
|
||||
+ big_topic
|
||||
)
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import datetime
|
||||
import os
|
||||
from typing import Union, List
|
||||
from typing import Union, List, Optional
|
||||
|
||||
import discord
|
||||
|
||||
@ -296,12 +296,20 @@ async def transfer_credits(from_: discord.Member, to: discord.Member, amount: in
|
||||
return await deposit_credits(to, amount)
|
||||
|
||||
|
||||
async def wipe_bank():
|
||||
"""Delete all accounts from the bank."""
|
||||
async def wipe_bank(guild: Optional[discord.Guild] = None) -> None:
|
||||
"""Delete all accounts from the bank.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
guild : discord.Guild
|
||||
The guild to clear accounts for. If unsupplied and the bank is
|
||||
per-server, all accounts in every guild will be wiped.
|
||||
|
||||
"""
|
||||
if await is_global():
|
||||
await _conf.clear_all_users()
|
||||
else:
|
||||
await _conf.clear_all_members()
|
||||
await _conf.clear_all_members(guild)
|
||||
|
||||
|
||||
async def get_leaderboard(positions: int = None, guild: discord.Guild = None) -> List[tuple]:
|
||||
|
||||
@ -838,7 +838,7 @@ class Config:
|
||||
"""
|
||||
return self._get_base_group(self.ROLE, role.id)
|
||||
|
||||
def user(self, user: discord.User) -> Group:
|
||||
def user(self, user: discord.abc.User) -> Group:
|
||||
"""Returns a `Group` for the given user.
|
||||
|
||||
Parameters
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Callable, Union
|
||||
|
||||
from . import commands
|
||||
|
||||
@ -113,9 +115,9 @@ def _normalize(string, remove_newline=False):
|
||||
ends_with_space = s[-1] in " \n\t\r"
|
||||
if remove_newline:
|
||||
newline_re = re.compile("[\r\n]+")
|
||||
s = " ".join(filter(bool, newline_re.split(s)))
|
||||
s = " ".join(filter(bool, s.split("\t")))
|
||||
s = " ".join(filter(bool, s.split(" ")))
|
||||
s = " ".join(filter(None, newline_re.split(s)))
|
||||
s = " ".join(filter(None, s.split("\t")))
|
||||
s = " ".join(filter(None, s.split(" ")))
|
||||
if starts_with_space:
|
||||
s = " " + s
|
||||
if ends_with_space:
|
||||
@ -149,10 +151,10 @@ def get_locale_path(cog_folder: Path, extension: str) -> Path:
|
||||
return cog_folder / "locales" / "{}.{}".format(get_locale(), extension)
|
||||
|
||||
|
||||
class Translator:
|
||||
class Translator(Callable[[str], str]):
|
||||
"""Function to get translated strings at runtime."""
|
||||
|
||||
def __init__(self, name, file_location):
|
||||
def __init__(self, name: str, file_location: Union[str, Path, os.PathLike]):
|
||||
"""
|
||||
Initializes an internationalization object.
|
||||
|
||||
@ -173,7 +175,7 @@ class Translator:
|
||||
|
||||
self.load_translations()
|
||||
|
||||
def __call__(self, untranslated: str):
|
||||
def __call__(self, untranslated: str) -> str:
|
||||
"""Translate the given string.
|
||||
|
||||
This will look for the string in the translator's :code:`.pot` file,
|
||||
|
||||
@ -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