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
|
from typing import Tuple
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
from redbot.core import Config, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
import logging
|
|
||||||
|
|
||||||
from redbot.core.utils.chat_formatting import box
|
from redbot.core.utils.chat_formatting import box
|
||||||
from .announcer import Announcer
|
from .announcer import Announcer
|
||||||
from .converters import MemberDefaultAuthor, SelfRole
|
from .converters import MemberDefaultAuthor, SelfRole
|
||||||
|
|
||||||
log = logging.getLogger("red.admin")
|
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."
|
"I attempted to do something that Discord denied me permissions for."
|
||||||
" Your command failed to successfully complete."
|
" Your command failed to successfully complete."
|
||||||
)
|
)
|
||||||
|
|
||||||
HIERARCHY_ISSUE = (
|
HIERARCHY_ISSUE = _(
|
||||||
"I tried to add {role.name} to {member.display_name} but that role"
|
"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"
|
" 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 "
|
" unable to successfully add it. Please give me a higher role and "
|
||||||
"try again."
|
"try again."
|
||||||
)
|
)
|
||||||
|
|
||||||
USER_HIERARCHY_ISSUE = (
|
USER_HIERARCHY_ISSUE = _(
|
||||||
"I tried to add {role.name} to {member.display_name} but that role"
|
"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"
|
" is higher than your highest role in the Discord hierarchy so I was"
|
||||||
" unable to successfully add it. Please get a higher role and "
|
" unable to successfully add it. Please get a higher role and "
|
||||||
"try again."
|
"try again."
|
||||||
)
|
)
|
||||||
|
|
||||||
RUNNING_ANNOUNCEMENT = (
|
RUNNING_ANNOUNCEMENT = _(
|
||||||
"I am already announcing something. If you would like to make a"
|
"I am already announcing something. If you would like to make a"
|
||||||
" different announcement please use `{prefix}announce cancel`"
|
" different announcement please use `{prefix}announce cancel`"
|
||||||
" first."
|
" first."
|
||||||
)
|
)
|
||||||
|
_ = T_
|
||||||
|
|
||||||
|
|
||||||
|
@cog_i18n(_)
|
||||||
class Admin(commands.Cog):
|
class Admin(commands.Cog):
|
||||||
|
"""A collection of server administration utilities."""
|
||||||
|
|
||||||
def __init__(self, config=Config):
|
def __init__(self, config=Config):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.conf = config.get_conf(self, 8237492837454039, force_registration=True)
|
self.conf = config.get_conf(self, 8237492837454039, force_registration=True)
|
||||||
@ -98,13 +104,14 @@ class Admin(commands.Cog):
|
|||||||
await member.add_roles(role)
|
await member.add_roles(role)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
if not self.pass_hierarchy_check(ctx, role):
|
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:
|
else:
|
||||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"I successfully added {role.name} to"
|
_("I successfully added {role.name} to {member.display_name}").format(
|
||||||
" {member.display_name}".format(role=role, member=member)
|
role=role, member=member
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _removerole(self, ctx: commands.Context, member: discord.Member, role: discord.Role):
|
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)
|
await member.remove_roles(role)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
if not self.pass_hierarchy_check(ctx, role):
|
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:
|
else:
|
||||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"I successfully removed {role.name} from"
|
_("I successfully removed {role.name} from {member.display_name}").format(
|
||||||
" {member.display_name}".format(role=role, member=member)
|
role=role, member=member
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@ -127,8 +135,8 @@ class Admin(commands.Cog):
|
|||||||
async def addrole(
|
async def addrole(
|
||||||
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
||||||
):
|
):
|
||||||
"""
|
"""Add a role to a user.
|
||||||
Adds a role to a user.
|
|
||||||
If user is left blank it defaults to the author of the command.
|
If user is left blank it defaults to the author of the command.
|
||||||
"""
|
"""
|
||||||
if user is None:
|
if user is None:
|
||||||
@ -137,7 +145,7 @@ class Admin(commands.Cog):
|
|||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
await self._addrole(ctx, user, rolename)
|
await self._addrole(ctx, user, rolename)
|
||||||
else:
|
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.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -145,8 +153,8 @@ class Admin(commands.Cog):
|
|||||||
async def removerole(
|
async def removerole(
|
||||||
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
self, ctx: commands.Context, rolename: discord.Role, *, user: MemberDefaultAuthor = None
|
||||||
):
|
):
|
||||||
"""
|
"""Remove a role from a user.
|
||||||
Removes a role from a user.
|
|
||||||
If user is left blank it defaults to the author of the command.
|
If user is left blank it defaults to the author of the command.
|
||||||
"""
|
"""
|
||||||
if user is None:
|
if user is None:
|
||||||
@ -155,50 +163,54 @@ class Admin(commands.Cog):
|
|||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
await self._removerole(ctx, user, rolename)
|
await self._removerole(ctx, user, rolename)
|
||||||
else:
|
else:
|
||||||
await self.complain(ctx, USER_HIERARCHY_ISSUE)
|
await self.complain(ctx, T_(USER_HIERARCHY_ISSUE))
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(manage_roles=True)
|
@checks.admin_or_permissions(manage_roles=True)
|
||||||
async def editrole(self, ctx: commands.Context):
|
async def editrole(self, ctx: commands.Context):
|
||||||
"""Edits roles settings"""
|
"""Edit role settings."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@editrole.command(name="colour", aliases=["color"])
|
@editrole.command(name="colour", aliases=["color"])
|
||||||
async def editrole_colour(
|
async def editrole_colour(
|
||||||
self, ctx: commands.Context, role: discord.Role, value: discord.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.
|
Use double quotes if the role contains spaces.
|
||||||
Colour must be in hexadecimal format.
|
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:
|
Examples:
|
||||||
!editrole colour \"The Transistor\" #ff0000
|
`[p]editrole colour "The Transistor" #ff0000`
|
||||||
!editrole colour Test #ff9900"""
|
`[p]editrole colour Test #ff9900`
|
||||||
|
"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
reason = "{}({}) changed the colour of role '{}'".format(author.name, author.id, role.name)
|
reason = "{}({}) changed the colour of role '{}'".format(author.name, author.id, role.name)
|
||||||
|
|
||||||
if not self.pass_user_hierarchy_check(ctx, role):
|
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
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await role.edit(reason=reason, color=value)
|
await role.edit(reason=reason, color=value)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
|
||||||
else:
|
else:
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
await ctx.send("Done.")
|
await ctx.send(_("Done."))
|
||||||
|
|
||||||
@editrole.command(name="name")
|
@editrole.command(name="name")
|
||||||
@checks.admin_or_permissions(administrator=True)
|
@checks.admin_or_permissions(administrator=True)
|
||||||
async def edit_role_name(self, ctx: commands.Context, role: discord.Role, *, name: str):
|
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.
|
Use double quotes if the role or the name contain spaces.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
!editrole name \"The Transistor\" Test"""
|
`[p]editrole name \"The Transistor\" Test`
|
||||||
|
"""
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
old_name = role.name
|
old_name = role.name
|
||||||
reason = "{}({}) changed the name of role '{}' to '{}'".format(
|
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):
|
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
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await role.edit(reason=reason, name=name)
|
await role.edit(reason=reason, name=name)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await self.complain(ctx, GENERIC_FORBIDDEN)
|
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
|
||||||
else:
|
else:
|
||||||
log.info(reason)
|
log.info(reason)
|
||||||
await ctx.send("Done.")
|
await ctx.send(_("Done."))
|
||||||
|
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def announce(self, ctx: commands.Context, *, message: str):
|
async def announce(self, ctx: commands.Context, *, message: str):
|
||||||
"""
|
"""Announce a message to all servers the bot is in."""
|
||||||
Announces a message to all servers the bot is in.
|
|
||||||
"""
|
|
||||||
if not self.is_announcing():
|
if not self.is_announcing():
|
||||||
announcer = Announcer(ctx, message, config=self.conf)
|
announcer = Announcer(ctx, message, config=self.conf)
|
||||||
announcer.start()
|
announcer.start()
|
||||||
|
|
||||||
self.__current_announcer = announcer
|
self.__current_announcer = announcer
|
||||||
|
|
||||||
await ctx.send("The announcement has begun.")
|
await ctx.send(_("The announcement has begun."))
|
||||||
else:
|
else:
|
||||||
prefix = ctx.prefix
|
prefix = ctx.prefix
|
||||||
await self.complain(ctx, RUNNING_ANNOUNCEMENT, prefix=prefix)
|
await self.complain(ctx, T_(RUNNING_ANNOUNCEMENT), prefix=prefix)
|
||||||
|
|
||||||
@announce.command(name="cancel")
|
@announce.command(name="cancel")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def announce_cancel(self, ctx):
|
async def announce_cancel(self, ctx):
|
||||||
"""
|
"""Cancel a running announce."""
|
||||||
Cancels a running announce.
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
self.__current_announcer.cancel()
|
self.__current_announcer.cancel()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
await ctx.send("The current announcement has been cancelled.")
|
await ctx.send(_("The current announcement has been cancelled."))
|
||||||
|
|
||||||
@announce.command(name="channel")
|
@announce.command(name="channel")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def announce_channel(self, ctx, *, channel: discord.TextChannel = None):
|
async def announce_channel(self, ctx, *, channel: discord.TextChannel = None):
|
||||||
"""
|
"""Change the channel to which the bot makes announcements."""
|
||||||
Changes the channel on which the bot makes announcements.
|
|
||||||
"""
|
|
||||||
if channel is None:
|
if channel is None:
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
await self.conf.guild(ctx.guild).announce_channel.set(channel.id)
|
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")
|
@announce.command(name="ignore")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def announce_ignore(self, ctx):
|
async def announce_ignore(self, ctx):
|
||||||
"""
|
"""Toggle announcements being enabled this server."""
|
||||||
Toggles whether the announcements will ignore the current server.
|
|
||||||
"""
|
|
||||||
ignored = await self.conf.guild(ctx.guild).announce_ignore()
|
ignored = await self.conf.guild(ctx.guild).announce_ignore()
|
||||||
await self.conf.guild(ctx.guild).announce_ignore.set(not ignored)
|
await self.conf.guild(ctx.guild).announce_ignore.set(not ignored)
|
||||||
|
|
||||||
verb = "will" if ignored else "will not"
|
if ignored: # Keeping original logic....
|
||||||
|
await ctx.send(
|
||||||
await ctx.send(f"The server {ctx.guild.name} {verb} receive announcements.")
|
_("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]:
|
async def _valid_selfroles(self, guild: discord.Guild) -> Tuple[discord.Role]:
|
||||||
"""
|
"""
|
||||||
@ -295,8 +308,9 @@ class Admin(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
async def selfrole(self, ctx: commands.Context, *, selfrole: SelfRole):
|
async def selfrole(self, ctx: commands.Context, *, selfrole: SelfRole):
|
||||||
"""
|
"""Add a role to yourself.
|
||||||
Add a role to yourself that server admins have configured as user settable.
|
|
||||||
|
Server admins must have configured the role as user settable.
|
||||||
|
|
||||||
NOTE: The role is case sensitive!
|
NOTE: The role is case sensitive!
|
||||||
"""
|
"""
|
||||||
@ -305,8 +319,7 @@ class Admin(commands.Cog):
|
|||||||
|
|
||||||
@selfrole.command(name="remove")
|
@selfrole.command(name="remove")
|
||||||
async def selfrole_remove(self, ctx: commands.Context, *, selfrole: SelfRole):
|
async def selfrole_remove(self, ctx: commands.Context, *, selfrole: SelfRole):
|
||||||
"""
|
"""Remove a selfrole from yourself.
|
||||||
Removes a selfrole from yourself.
|
|
||||||
|
|
||||||
NOTE: The role is case sensitive!
|
NOTE: The role is case sensitive!
|
||||||
"""
|
"""
|
||||||
@ -316,8 +329,7 @@ class Admin(commands.Cog):
|
|||||||
@selfrole.command(name="add")
|
@selfrole.command(name="add")
|
||||||
@checks.admin_or_permissions(manage_roles=True)
|
@checks.admin_or_permissions(manage_roles=True)
|
||||||
async def selfrole_add(self, ctx: commands.Context, *, role: discord.Role):
|
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!
|
NOTE: The role is case sensitive!
|
||||||
"""
|
"""
|
||||||
@ -325,20 +337,19 @@ class Admin(commands.Cog):
|
|||||||
if role.id not in curr_selfroles:
|
if role.id not in curr_selfroles:
|
||||||
curr_selfroles.append(role.id)
|
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")
|
@selfrole.command(name="delete")
|
||||||
@checks.admin_or_permissions(manage_roles=True)
|
@checks.admin_or_permissions(manage_roles=True)
|
||||||
async def selfrole_delete(self, ctx: commands.Context, *, role: SelfRole):
|
async def selfrole_delete(self, ctx: commands.Context, *, role: SelfRole):
|
||||||
"""
|
"""Remove a role from the list of available selfroles.
|
||||||
Removes a role from the list of available selfroles.
|
|
||||||
|
|
||||||
NOTE: The role is case sensitive!
|
NOTE: The role is case sensitive!
|
||||||
"""
|
"""
|
||||||
async with self.conf.guild(ctx.guild).selfroles() as curr_selfroles:
|
async with self.conf.guild(ctx.guild).selfroles() as curr_selfroles:
|
||||||
curr_selfroles.remove(role.id)
|
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")
|
@selfrole.command(name="list")
|
||||||
async def selfrole_list(self, ctx: commands.Context):
|
async def selfrole_list(self, ctx: commands.Context):
|
||||||
@ -348,7 +359,7 @@ class Admin(commands.Cog):
|
|||||||
selfroles = await self._valid_selfroles(ctx.guild)
|
selfroles = await self._valid_selfroles(ctx.guild)
|
||||||
fmt_selfroles = "\n".join(["+ " + r.name for r in selfroles])
|
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"))
|
await ctx.send(box(msg, "diff"))
|
||||||
|
|
||||||
async def _serverlock_check(self, guild: discord.Guild) -> bool:
|
async def _serverlock_check(self, guild: discord.Guild) -> bool:
|
||||||
@ -365,15 +376,14 @@ class Admin(commands.Cog):
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def serverlock(self, ctx: commands.Context):
|
async def serverlock(self, ctx: commands.Context):
|
||||||
"""
|
"""Lock a bot to its current servers only."""
|
||||||
Locks a bot to its current servers only.
|
|
||||||
"""
|
|
||||||
serverlocked = await self.conf.serverlocked()
|
serverlocked = await self.conf.serverlocked()
|
||||||
await self.conf.serverlocked.set(not serverlocked)
|
await self.conf.serverlocked.set(not serverlocked)
|
||||||
|
|
||||||
verb = "is now" if not serverlocked else "is no longer"
|
if serverlocked:
|
||||||
|
await ctx.send(_("The bot is no longer serverlocked."))
|
||||||
await ctx.send("The bot {} serverlocked.".format(verb))
|
else:
|
||||||
|
await ctx.send(_("The bot is now serverlocked."))
|
||||||
|
|
||||||
# region Event Handlers
|
# region Event Handlers
|
||||||
async def on_guild_join(self, guild: discord.Guild):
|
async def on_guild_join(self, guild: discord.Guild):
|
||||||
|
|||||||
@ -2,6 +2,9 @@ import asyncio
|
|||||||
|
|
||||||
import discord
|
import discord
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.i18n import Translator
|
||||||
|
|
||||||
|
_ = Translator("Announcer", __file__)
|
||||||
|
|
||||||
|
|
||||||
class Announcer:
|
class Announcer:
|
||||||
@ -63,7 +66,9 @@ class Announcer:
|
|||||||
try:
|
try:
|
||||||
await channel.send(self.message)
|
await channel.send(self.message)
|
||||||
except discord.Forbidden:
|
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)
|
await asyncio.sleep(0.5)
|
||||||
|
|
||||||
self.active = False
|
self.active = False
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import discord
|
import discord
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.i18n import Translator
|
||||||
|
|
||||||
|
_ = Translator("AdminConverters", __file__)
|
||||||
|
|
||||||
|
|
||||||
class MemberDefaultAuthor(commands.Converter):
|
class MemberDefaultAuthor(commands.Converter):
|
||||||
@ -19,7 +22,7 @@ class SelfRole(commands.Converter):
|
|||||||
async def convert(self, ctx: commands.Context, arg: str) -> discord.Role:
|
async def convert(self, ctx: commands.Context, arg: str) -> discord.Role:
|
||||||
admin = ctx.command.instance
|
admin = ctx.command.instance
|
||||||
if admin is None:
|
if admin is None:
|
||||||
raise commands.BadArgument("Admin is not loaded.")
|
raise commands.BadArgument(_("The Admin cog is not loaded."))
|
||||||
|
|
||||||
conf = admin.conf
|
conf = admin.conf
|
||||||
selfroles = await conf.guild(ctx.guild).selfroles()
|
selfroles = await conf.guild(ctx.guild).selfroles()
|
||||||
@ -28,5 +31,5 @@ class SelfRole(commands.Converter):
|
|||||||
role = await role_converter.convert(ctx, arg)
|
role = await role_converter.convert(ctx, arg)
|
||||||
|
|
||||||
if role.id not in selfroles:
|
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
|
return role
|
||||||
|
|||||||
@ -15,15 +15,14 @@ _ = Translator("Alias", __file__)
|
|||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Alias(commands.Cog):
|
class Alias(commands.Cog):
|
||||||
"""
|
"""Create aliases for commands.
|
||||||
Alias
|
|
||||||
|
|
||||||
Aliases are per server shortcuts for commands. They
|
Aliases are alternative names shortcuts for commands. They
|
||||||
can act as both a lambda (storing arguments for repeated use)
|
can act as both a lambda (storing arguments for repeated use)
|
||||||
or as simply a shortcut to saying "x y z".
|
or as simply a shortcut to saying "x y z".
|
||||||
|
|
||||||
When run, aliases will accept any additional arguments
|
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": []}
|
default_global_settings = {"entries": []}
|
||||||
@ -177,32 +176,28 @@ class Alias(commands.Cog):
|
|||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def alias(self, ctx: commands.Context):
|
async def alias(self, ctx: commands.Context):
|
||||||
"""Manage per-server aliases for commands"""
|
"""Manage command aliases."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@alias.group(name="global")
|
@alias.group(name="global")
|
||||||
async def global_(self, ctx: commands.Context):
|
async def global_(self, ctx: commands.Context):
|
||||||
"""
|
"""Manage global aliases."""
|
||||||
Manage global aliases.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@checks.mod_or_permissions(manage_guild=True)
|
@checks.mod_or_permissions(manage_guild=True)
|
||||||
@alias.command(name="add")
|
@alias.command(name="add")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _add_alias(self, ctx: commands.Context, alias_name: str, *, command):
|
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
|
# region Alias Add Validity Checking
|
||||||
is_command = self.is_command(alias_name)
|
is_command = self.is_command(alias_name)
|
||||||
if is_command:
|
if is_command:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"You attempted to create a new alias"
|
"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."
|
" name is already a command on this bot."
|
||||||
).format(alias_name)
|
).format(name=alias_name)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -211,9 +206,9 @@ class Alias(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"You attempted to create a new alias"
|
"You attempted to create a new alias"
|
||||||
" with the name {} but that"
|
" with the name {name} but that"
|
||||||
" alias already exists on this server."
|
" alias already exists on this server."
|
||||||
).format(alias_name)
|
).format(name=alias_name)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -222,10 +217,10 @@ class Alias(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"You attempted to create a new alias"
|
"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"
|
" name is an invalid alias name. Alias"
|
||||||
" names may not contain spaces."
|
" names may not contain spaces."
|
||||||
).format(alias_name)
|
).format(name=alias_name)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
# endregion
|
# endregion
|
||||||
@ -235,23 +230,23 @@ class Alias(commands.Cog):
|
|||||||
|
|
||||||
await self.add_alias(ctx, alias_name, command)
|
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()
|
@checks.is_owner()
|
||||||
@global_.command(name="add")
|
@global_.command(name="add")
|
||||||
async def _add_global_alias(self, ctx: commands.Context, alias_name: str, *, command):
|
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
|
# region Alias Add Validity Checking
|
||||||
is_command = self.is_command(alias_name)
|
is_command = self.is_command(alias_name)
|
||||||
if is_command:
|
if is_command:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"You attempted to create a new global alias"
|
"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."
|
" name is already a command on this bot."
|
||||||
).format(alias_name)
|
).format(name=alias_name)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -260,9 +255,9 @@ class Alias(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"You attempted to create a new global alias"
|
"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."
|
" alias already exists on this server."
|
||||||
).format(alias_name)
|
).format(name=alias_name)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -271,10 +266,10 @@ class Alias(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"You attempted to create a new global alias"
|
"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"
|
" name is an invalid alias name. Alias"
|
||||||
" names may not contain spaces."
|
" names may not contain spaces."
|
||||||
).format(alias_name)
|
).format(name=alias_name)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
# endregion
|
# endregion
|
||||||
@ -282,63 +277,65 @@ class Alias(commands.Cog):
|
|||||||
await self.add_alias(ctx, alias_name, command, global_=True)
|
await self.add_alias(ctx, alias_name, command, global_=True)
|
||||||
|
|
||||||
await ctx.send(
|
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")
|
@alias.command(name="help")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _help_alias(self, ctx: commands.Context, alias_name: str):
|
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)
|
is_alias, alias = await self.is_alias(ctx.guild, alias_name=alias_name)
|
||||||
if is_alias:
|
if is_alias:
|
||||||
base_cmd = alias.command[0]
|
base_cmd = alias.command[0]
|
||||||
|
|
||||||
new_msg = copy(ctx.message)
|
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)
|
await self.bot.process_commands(new_msg)
|
||||||
else:
|
else:
|
||||||
ctx.send(_("No such alias exists."))
|
await ctx.send(_("No such alias exists."))
|
||||||
|
|
||||||
@alias.command(name="show")
|
@alias.command(name="show")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _show_alias(self, ctx: commands.Context, alias_name: str):
|
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)
|
is_alias, alias = await self.is_alias(ctx.guild, alias_name)
|
||||||
|
|
||||||
if is_alias:
|
if is_alias:
|
||||||
await ctx.send(
|
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:
|
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)
|
@checks.mod_or_permissions(manage_guild=True)
|
||||||
@alias.command(name="del")
|
@alias.command(name="del")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _del_alias(self, ctx: commands.Context, alias_name: str):
|
async def _del_alias(self, ctx: commands.Context, alias_name: str):
|
||||||
"""
|
"""Delete an existing alias on this server."""
|
||||||
Deletes an existing alias on this server.
|
|
||||||
"""
|
|
||||||
aliases = await self.unloaded_aliases(ctx.guild)
|
aliases = await self.unloaded_aliases(ctx.guild)
|
||||||
try:
|
try:
|
||||||
next(aliases)
|
next(aliases)
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
await ctx.send(_("There are no aliases on this guild."))
|
await ctx.send(_("There are no aliases on this server."))
|
||||||
return
|
return
|
||||||
|
|
||||||
if await self.delete_alias(ctx, alias_name):
|
if await self.delete_alias(ctx, alias_name):
|
||||||
await ctx.send(
|
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:
|
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()
|
@checks.is_owner()
|
||||||
@global_.command(name="del")
|
@global_.command(name="del")
|
||||||
async def _del_global_alias(self, ctx: commands.Context, alias_name: str):
|
async def _del_global_alias(self, ctx: commands.Context, alias_name: str):
|
||||||
"""
|
"""Delete an existing global alias."""
|
||||||
Deletes an existing global alias.
|
|
||||||
"""
|
|
||||||
aliases = await self.unloaded_global_aliases()
|
aliases = await self.unloaded_global_aliases()
|
||||||
try:
|
try:
|
||||||
next(aliases)
|
next(aliases)
|
||||||
@ -348,17 +345,15 @@ class Alias(commands.Cog):
|
|||||||
|
|
||||||
if await self.delete_alias(ctx, alias_name, global_=True):
|
if await self.delete_alias(ctx, alias_name, global_=True):
|
||||||
await ctx.send(
|
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:
|
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")
|
@alias.command(name="list")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def _list_alias(self, ctx: commands.Context):
|
async def _list_alias(self, ctx: commands.Context):
|
||||||
"""
|
"""List the available aliases on this server."""
|
||||||
Lists the available aliases on this server.
|
|
||||||
"""
|
|
||||||
names = [_("Aliases:")] + sorted(
|
names = [_("Aliases:")] + sorted(
|
||||||
["+ " + a.name for a in (await self.unloaded_aliases(ctx.guild))]
|
["+ " + a.name for a in (await self.unloaded_aliases(ctx.guild))]
|
||||||
)
|
)
|
||||||
@ -369,9 +364,7 @@ class Alias(commands.Cog):
|
|||||||
|
|
||||||
@global_.command(name="list")
|
@global_.command(name="list")
|
||||||
async def _list_global_alias(self, ctx: commands.Context):
|
async def _list_global_alias(self, ctx: commands.Context):
|
||||||
"""
|
"""List the available global aliases on this bot."""
|
||||||
Lists the available global aliases on this bot.
|
|
||||||
"""
|
|
||||||
names = [_("Aliases:")] + sorted(
|
names = [_("Aliases:")] + sorted(
|
||||||
["+ " + a.name for a in await self.unloaded_global_aliases()]
|
["+ " + 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)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
@commands.group(autohelp=True)
|
@commands.group(autohelp=True)
|
||||||
async def bankset(self, ctx: commands.Context):
|
async def bankset(self, ctx: commands.Context):
|
||||||
"""Base command for bank settings"""
|
"""Base command for bank settings."""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
bank_name = await bank._conf.bank_name()
|
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()
|
default_balance = await bank._conf.guild(ctx.guild).default_balance()
|
||||||
|
|
||||||
settings = _(
|
settings = _(
|
||||||
"Bank settings:\n\nBank name: {}\nCurrency: {}\nDefault balance: {}"
|
"Bank settings:\n\nBank name: {bank_name}\nCurrency: {currency_name}\n"
|
||||||
).format(bank_name, currency_name, default_balance)
|
"Default balance: {default_balance}"
|
||||||
|
).format(
|
||||||
|
bank_name=bank_name, currency_name=currency_name, default_balance=default_balance
|
||||||
|
)
|
||||||
await ctx.send(box(settings))
|
await ctx.send(box(settings))
|
||||||
|
|
||||||
@bankset.command(name="toggleglobal")
|
@bankset.command(name="toggleglobal")
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def bankset_toggleglobal(self, ctx: commands.Context, confirm: bool = False):
|
async def bankset_toggleglobal(self, ctx: commands.Context, confirm: bool = False):
|
||||||
"""Toggles whether the bank is global or not
|
"""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"""
|
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()
|
cur_setting = await bank.is_global()
|
||||||
|
|
||||||
word = _("per-server") if cur_setting else _("global")
|
word = _("per-server") if cur_setting else _("global")
|
||||||
if confirm is False:
|
if confirm is False:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"This will toggle the bank to be {}, deleting all accounts "
|
"This will toggle the bank to be {banktype}, deleting all accounts "
|
||||||
"in the process! If you're sure, type `{}`"
|
"in the process! If you're sure, type `{command}`"
|
||||||
).format(word, "{}bankset toggleglobal yes".format(ctx.prefix))
|
).format(banktype=word, command="{}bankset toggleglobal yes".format(ctx.prefix))
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await bank.set_global(not cur_setting)
|
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")
|
@bankset.command(name="bankname")
|
||||||
@check_global_setting_guildowner()
|
@check_global_setting_guildowner()
|
||||||
async def bankset_bankname(self, ctx: commands.Context, *, name: str):
|
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 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")
|
@bankset.command(name="creditsname")
|
||||||
@check_global_setting_guildowner()
|
@check_global_setting_guildowner()
|
||||||
async def bankset_creditsname(self, ctx: commands.Context, *, name: str):
|
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 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
|
# ENDSECTION
|
||||||
|
|||||||
@ -16,7 +16,7 @@ _ = Translator("Cleanup", __file__)
|
|||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Cleanup(commands.Cog):
|
class Cleanup(commands.Cog):
|
||||||
"""Commands for cleaning messages"""
|
"""Commands for cleaning up messages."""
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -33,7 +33,7 @@ class Cleanup(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
prompt = await ctx.send(
|
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))
|
response = await ctx.bot.wait_for("message", check=MessagePredicate.same_context(ctx))
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ class Cleanup(commands.Cog):
|
|||||||
await prompt.delete()
|
await prompt.delete()
|
||||||
try:
|
try:
|
||||||
await response.delete()
|
await response.delete()
|
||||||
except:
|
except discord.HTTPException:
|
||||||
pass
|
pass
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@ -104,25 +104,24 @@ class Cleanup(commands.Cog):
|
|||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.mod_or_permissions(manage_messages=True)
|
@checks.mod_or_permissions(manage_messages=True)
|
||||||
async def cleanup(self, ctx: commands.Context):
|
async def cleanup(self, ctx: commands.Context):
|
||||||
"""Deletes messages."""
|
"""Delete messages."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def text(
|
async def text(
|
||||||
self, ctx: commands.Context, text: str, number: int, delete_pinned: bool = False
|
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:
|
Example:
|
||||||
cleanup text \"test\" 5
|
`[p]cleanup text "test" 5`
|
||||||
|
|
||||||
Remember to use double quotes."""
|
Remember to use double quotes.
|
||||||
|
"""
|
||||||
|
|
||||||
channel = ctx.channel
|
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
|
author = ctx.author
|
||||||
|
|
||||||
@ -156,18 +155,17 @@ class Cleanup(commands.Cog):
|
|||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def user(
|
async def user(
|
||||||
self, ctx: commands.Context, user: str, number: int, delete_pinned: bool = False
|
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:
|
Examples:
|
||||||
cleanup user @\u200bTwentysix 2
|
`[p]cleanup user @\u200bTwentysix 2`
|
||||||
cleanup user Red 6"""
|
`[p]cleanup user Red 6`
|
||||||
|
"""
|
||||||
channel = ctx.channel
|
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
|
member = None
|
||||||
try:
|
try:
|
||||||
@ -213,8 +211,9 @@ class Cleanup(commands.Cog):
|
|||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool = False):
|
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
|
To get a message id, enable developer mode in Discord's
|
||||||
settings, 'appearance' tab. Then right click a message
|
settings, 'appearance' tab. Then right click a message
|
||||||
@ -222,9 +221,6 @@ class Cleanup(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
channel = ctx.channel
|
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
|
author = ctx.author
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -245,6 +241,7 @@ class Cleanup(commands.Cog):
|
|||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def before(
|
async def before(
|
||||||
self, ctx: commands.Context, message_id: int, number: int, delete_pinned: bool = False
|
self, ctx: commands.Context, message_id: int, number: int, delete_pinned: bool = False
|
||||||
):
|
):
|
||||||
@ -256,9 +253,6 @@ class Cleanup(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
channel = ctx.channel
|
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
|
author = ctx.author
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -279,16 +273,15 @@ class Cleanup(commands.Cog):
|
|||||||
|
|
||||||
@cleanup.command()
|
@cleanup.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
||||||
"""Deletes last X messages.
|
"""Delete the last X messages.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
cleanup messages 26"""
|
`[p]cleanup messages 26`
|
||||||
|
"""
|
||||||
|
|
||||||
channel = ctx.channel
|
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
|
author = ctx.author
|
||||||
|
|
||||||
if number > 100:
|
if number > 100:
|
||||||
@ -310,13 +303,11 @@ class Cleanup(commands.Cog):
|
|||||||
|
|
||||||
@cleanup.command(name="bot")
|
@cleanup.command(name="bot")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(manage_messages=True)
|
||||||
async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
|
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
|
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
|
author = ctx.message.author
|
||||||
|
|
||||||
if number > 100:
|
if number > 100:
|
||||||
@ -369,7 +360,7 @@ class Cleanup(commands.Cog):
|
|||||||
match_pattern: str = None,
|
match_pattern: str = None,
|
||||||
delete_pinned: bool = False,
|
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,
|
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 ),
|
it is used for pattern matching: If it begins with r( and ends with ),
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import random
|
import random
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from inspect import Parameter
|
from inspect import Parameter
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Mapping
|
from typing import Mapping, Tuple, Dict
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@ -52,11 +51,11 @@ class CommandObj:
|
|||||||
|
|
||||||
async def get_responses(self, ctx):
|
async def get_responses(self, ctx):
|
||||||
intro = _(
|
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 "
|
"Every message you send will be added as one of the random "
|
||||||
"responses to choose from once this {} is "
|
"responses to choose from once this {cc} is "
|
||||||
"triggered. To exit this interactive menu, type `{}`"
|
"triggered. To exit this interactive menu, type `{quit}`"
|
||||||
).format("customcommand", "customcommand", "exit()")
|
).format(cc="customcommand", quit="exit()")
|
||||||
await ctx.send(intro)
|
await ctx.send(intro)
|
||||||
|
|
||||||
responses = []
|
responses = []
|
||||||
@ -85,7 +84,7 @@ class CommandObj:
|
|||||||
# in the ccinfo dict
|
# in the ccinfo dict
|
||||||
return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow())
|
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)
|
ccinfo = await self.db(message.guild).commands.get_raw(command, default=None)
|
||||||
if not ccinfo:
|
if not ccinfo:
|
||||||
raise NotFound()
|
raise NotFound()
|
||||||
@ -180,9 +179,7 @@ class CommandObj:
|
|||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class CustomCommands(commands.Cog):
|
class CustomCommands(commands.Cog):
|
||||||
"""Custom commands
|
"""Creates commands used to display text."""
|
||||||
|
|
||||||
Creates commands used to display text"""
|
|
||||||
|
|
||||||
def __init__(self, bot):
|
def __init__(self, bot):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -196,61 +193,55 @@ class CustomCommands(commands.Cog):
|
|||||||
@commands.group(aliases=["cc"])
|
@commands.group(aliases=["cc"])
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def customcom(self, ctx: commands.Context):
|
async def customcom(self, ctx: commands.Context):
|
||||||
"""Custom commands management"""
|
"""Custom commands management."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@customcom.group(name="add")
|
@customcom.group(name="create", aliases=["add"])
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_add(self, ctx: commands.Context):
|
async def cc_create(self, ctx: commands.Context):
|
||||||
"""
|
"""Create custom commands.
|
||||||
Adds a new custom command
|
|
||||||
|
|
||||||
CCs can be enhanced with arguments:
|
CCs can be enhanced with arguments, see the guide
|
||||||
https://red-discordbot.readthedocs.io/en/v3-develop/cog_customcom.html
|
[here](https://red-discordbot.readthedocs.io/en/v3-develop/cog_customcom.html).
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@cc_add.command(name="random")
|
@cc_create.command(name="random")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_add_random(self, ctx: commands.Context, command: str.lower):
|
async def cc_create_random(self, ctx: commands.Context, command: str.lower):
|
||||||
"""
|
"""Create a CC where it will randomly choose a response!
|
||||||
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)
|
responses = await self.commandobj.get_responses(ctx=ctx)
|
||||||
try:
|
try:
|
||||||
await self.commandobj.create(ctx=ctx, command=command, response=responses)
|
await self.commandobj.create(ctx=ctx, command=command, response=responses)
|
||||||
await ctx.send(_("Custom command successfully added."))
|
await ctx.send(_("Custom command successfully added."))
|
||||||
except AlreadyExists:
|
except AlreadyExists:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("This command already exists. Use `{}` to edit it.").format(
|
_("This command already exists. Use `{command}` to edit it.").format(
|
||||||
"{}customcom edit".format(ctx.prefix)
|
command="{}customcom edit".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# await ctx.send(str(responses))
|
@cc_create.command(name="simple")
|
||||||
|
|
||||||
@cc_add.command(name="simple")
|
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_add_simple(self, ctx, command: str.lower, *, text: str):
|
async def cc_create_simple(self, ctx, command: str.lower, *, text: str):
|
||||||
"""Adds a simple custom command
|
"""Add a simple custom command.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
[p]customcom add simple yourcommand Text you want
|
- `[p]customcom create simple yourcommand Text you want`
|
||||||
"""
|
"""
|
||||||
if command in self.bot.all_commands:
|
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
|
return
|
||||||
try:
|
try:
|
||||||
await self.commandobj.create(ctx=ctx, command=command, response=text)
|
await self.commandobj.create(ctx=ctx, command=command, response=text)
|
||||||
await ctx.send(_("Custom command successfully added."))
|
await ctx.send(_("Custom command successfully added."))
|
||||||
except AlreadyExists:
|
except AlreadyExists:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("This command already exists. Use `{}` to edit it.").format(
|
_("This command already exists. Use `{command}` to edit it.").format(
|
||||||
"{}customcom edit".format(ctx.prefix)
|
command="{}customcom edit".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except ArgParseError as e:
|
except ArgParseError as e:
|
||||||
@ -261,13 +252,14 @@ class CustomCommands(commands.Cog):
|
|||||||
async def cc_cooldown(
|
async def cc_cooldown(
|
||||||
self, ctx, command: str.lower, cooldown: int = None, *, per: str.lower = "member"
|
self, ctx, command: str.lower, cooldown: int = None, *, per: str.lower = "member"
|
||||||
):
|
):
|
||||||
"""
|
"""Set, edit, or view the cooldown for a custom command.
|
||||||
Sets, edits, or views cooldowns 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:
|
Example:
|
||||||
[p]customcom cooldown yourcommand 30
|
- `[p]customcom cooldown yourcommand 30`
|
||||||
"""
|
"""
|
||||||
if cooldown is None:
|
if cooldown is None:
|
||||||
try:
|
try:
|
||||||
@ -293,18 +285,19 @@ class CustomCommands(commands.Cog):
|
|||||||
await ctx.send(_("Custom command cooldown successfully edited."))
|
await ctx.send(_("Custom command cooldown successfully edited."))
|
||||||
except NotFound:
|
except NotFound:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("That command doesn't exist. Use `{}` to add it.").format(
|
_("That command doesn't exist. Use `{command}` to add it.").format(
|
||||||
"{}customcom add".format(ctx.prefix)
|
command="{}customcom create".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@customcom.command(name="delete")
|
@customcom.command(name="delete")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_delete(self, ctx, command: str.lower):
|
async def cc_delete(self, ctx, command: str.lower):
|
||||||
"""Deletes a custom command
|
"""Delete a custom command
|
||||||
|
.
|
||||||
Example:
|
Example:
|
||||||
[p]customcom delete yourcommand"""
|
- `[p]customcom delete yourcommand`
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
await self.commandobj.delete(ctx=ctx, command=command)
|
await self.commandobj.delete(ctx=ctx, command=command)
|
||||||
await ctx.send(_("Custom command successfully deleted."))
|
await ctx.send(_("Custom command successfully deleted."))
|
||||||
@ -314,18 +307,20 @@ class CustomCommands(commands.Cog):
|
|||||||
@customcom.command(name="edit")
|
@customcom.command(name="edit")
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def cc_edit(self, ctx, command: str.lower, *, text: str = None):
|
async def cc_edit(self, ctx, command: str.lower, *, text: str = None):
|
||||||
"""Edits a custom command's response
|
"""Edit a custom command.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
[p]customcom edit yourcommand Text you want
|
- `[p]customcom edit yourcommand Text you want`
|
||||||
"""
|
"""
|
||||||
|
command = command.lower()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.commandobj.edit(ctx=ctx, command=command, response=text)
|
await self.commandobj.edit(ctx=ctx, command=command, response=text)
|
||||||
await ctx.send(_("Custom command successfully edited."))
|
await ctx.send(_("Custom command successfully edited."))
|
||||||
except NotFound:
|
except NotFound:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("That command doesn't exist. Use `{}` to add it.").format(
|
_("That command doesn't exist. Use `{command}` to add it.").format(
|
||||||
"{}customcom add".format(ctx.prefix)
|
command="{}customcom create".format(ctx.prefix)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except ArgParseError as e:
|
except ArgParseError as e:
|
||||||
@ -333,7 +328,7 @@ class CustomCommands(commands.Cog):
|
|||||||
|
|
||||||
@customcom.command(name="list")
|
@customcom.command(name="list")
|
||||||
async def cc_list(self, ctx):
|
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))
|
response = await CommandObj.get_commands(self.config.guild(ctx.guild))
|
||||||
|
|
||||||
@ -341,8 +336,8 @@ class CustomCommands(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"There are no custom commands in this server."
|
"There are no custom commands in this server."
|
||||||
" Use `{}` to start adding some."
|
" Use `{command}` to start adding some."
|
||||||
).format("{}customcom add".format(ctx.prefix))
|
).format(command="{}customcom create".format(ctx.prefix))
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -454,9 +449,8 @@ class CustomCommands(commands.Cog):
|
|||||||
gaps = set(indices).symmetric_difference(range(high + 1))
|
gaps = set(indices).symmetric_difference(range(high + 1))
|
||||||
if gaps:
|
if gaps:
|
||||||
raise ArgParseError(
|
raise ArgParseError(
|
||||||
_("Arguments must be sequential. Missing arguments: {}.").format(
|
_("Arguments must be sequential. Missing arguments: ")
|
||||||
", ".join(str(i + low) for i in gaps)
|
+ ", ".join(str(i + low) for i in gaps)
|
||||||
)
|
|
||||||
)
|
)
|
||||||
fin = [Parameter("_" + str(i), Parameter.POSITIONAL_OR_KEYWORD) for i in range(high + 1)]
|
fin = [Parameter("_" + str(i), Parameter.POSITIONAL_OR_KEYWORD) for i in range(high + 1)]
|
||||||
for arg in args:
|
for arg in args:
|
||||||
@ -481,8 +475,12 @@ class CustomCommands(commands.Cog):
|
|||||||
and anno != fin[index].annotation
|
and anno != fin[index].annotation
|
||||||
):
|
):
|
||||||
raise ArgParseError(
|
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:
|
if anno is not Parameter.empty:
|
||||||
@ -511,6 +509,8 @@ class CustomCommands(commands.Cog):
|
|||||||
key = (command, ctx.guild, ctx.channel)
|
key = (command, ctx.guild, ctx.channel)
|
||||||
elif per == "member":
|
elif per == "member":
|
||||||
key = (command, ctx.guild, ctx.author)
|
key = (command, ctx.guild, ctx.author)
|
||||||
|
else:
|
||||||
|
raise ValueError(per)
|
||||||
cooldown = self.cooldowns.get(key)
|
cooldown = self.cooldowns.get(key)
|
||||||
if cooldown:
|
if cooldown:
|
||||||
cooldown += timedelta(seconds=rate)
|
cooldown += timedelta(seconds=rate)
|
||||||
|
|||||||
@ -13,9 +13,7 @@ _ = Translator("DataConverter", __file__)
|
|||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class DataConverter(commands.Cog):
|
class DataConverter(commands.Cog):
|
||||||
"""
|
"""Import Red V2 data to your V3 instance."""
|
||||||
Cog for importing Red v2 Data
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -24,13 +22,10 @@ class DataConverter(commands.Cog):
|
|||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
@commands.command(name="convertdata")
|
@commands.command(name="convertdata")
|
||||||
async def dataconversioncommand(self, ctx: commands.Context, v2path: str):
|
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
|
Takes the path where the V2 install is, and overwrites
|
||||||
|
values which have entries in both V2 and v3; use with caution.
|
||||||
Overwrites values which have entries in both v2 and v3,
|
|
||||||
use with caution.
|
|
||||||
"""
|
"""
|
||||||
resolver = SpecResolver(Path(v2path.strip()))
|
resolver = SpecResolver(Path(v2path.strip()))
|
||||||
|
|
||||||
@ -54,7 +49,7 @@ class DataConverter(commands.Cog):
|
|||||||
"message", check=MessagePredicate.same_context(ctx), timeout=60
|
"message", check=MessagePredicate.same_context(ctx), timeout=60
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
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:
|
else:
|
||||||
if message.content.strip().lower() in ["quit", "exit", "-1", "q", "cancel"]:
|
if message.content.strip().lower() in ["quit", "exit", "-1", "q", "cancel"]:
|
||||||
return await ctx.tick()
|
return await ctx.tick()
|
||||||
@ -72,7 +67,7 @@ class DataConverter(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
return await ctx.send(
|
return await ctx.send(
|
||||||
_(
|
_(
|
||||||
"There isn't anything else I know how to convert here."
|
"There isn't anything else I know how to convert here.\n"
|
||||||
"\nThere might be more things I can convert in the future."
|
"There might be more things I can convert in the future."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,11 +1,15 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.i18n import Translator
|
||||||
from redbot.core.utils.predicates import MessagePredicate
|
from redbot.core.utils.predicates import MessagePredicate
|
||||||
|
|
||||||
__all__ = ["do_install_agreement"]
|
__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"
|
"You're about to add a 3rd party repository. The creator of Red"
|
||||||
" and its community have no responsibility for any potential "
|
" and its community have no responsibility for any potential "
|
||||||
"damage that the content of 3rd party repositories might cause."
|
"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"
|
"shown again until the next reboot.\n\nYou have **30** seconds"
|
||||||
" to reply to this message."
|
" to reply to this message."
|
||||||
)
|
)
|
||||||
|
_ = T_
|
||||||
|
|
||||||
|
|
||||||
async def do_install_agreement(ctx: commands.Context):
|
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:
|
if downloader is None or downloader.already_agreed:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
await ctx.send(REPO_INSTALL_MSG)
|
await ctx.send(T_(REPO_INSTALL_MSG))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await ctx.bot.wait_for(
|
await ctx.bot.wait_for(
|
||||||
"message", check=MessagePredicate.lower_equal_to("i agree", ctx), timeout=30
|
"message", check=MessagePredicate.lower_equal_to("i agree", ctx), timeout=30
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
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
|
return False
|
||||||
|
|
||||||
downloader.already_agreed = True
|
downloader.already_agreed = True
|
||||||
|
|||||||
@ -8,10 +8,10 @@ class InstalledCog(Installable):
|
|||||||
async def convert(cls, ctx: commands.Context, arg: str) -> Installable:
|
async def convert(cls, ctx: commands.Context, arg: str) -> Installable:
|
||||||
downloader = ctx.bot.get_cog("Downloader")
|
downloader = ctx.bot.get_cog("Downloader")
|
||||||
if downloader is None:
|
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)
|
cog = discord.utils.get(await downloader.installed_cogs(), name=arg)
|
||||||
if cog is None:
|
if cog is None:
|
||||||
raise commands.BadArgument("That cog is not installed")
|
raise commands.BadArgument(_("That cog is not installed"))
|
||||||
|
|
||||||
return cog
|
return cog
|
||||||
|
|||||||
@ -193,9 +193,7 @@ class Downloader(commands.Cog):
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def pipinstall(self, ctx, *deps: str):
|
async def pipinstall(self, ctx, *deps: str):
|
||||||
"""
|
"""Install a group of dependencies using pip."""
|
||||||
Installs a group of dependencies using pip.
|
|
||||||
"""
|
|
||||||
repo = Repo("", "", "", Path.cwd(), loop=ctx.bot.loop)
|
repo = Repo("", "", "", Path.cwd(), loop=ctx.bot.loop)
|
||||||
success = await repo.install_raw_requirements(deps, self.LIB_PATH)
|
success = await repo.install_raw_requirements(deps, self.LIB_PATH)
|
||||||
|
|
||||||
@ -212,18 +210,15 @@ class Downloader(commands.Cog):
|
|||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def repo(self, ctx):
|
async def repo(self, ctx):
|
||||||
"""
|
"""Repo management commands."""
|
||||||
Command group for managing Downloader repos.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@repo.command(name="add")
|
@repo.command(name="add")
|
||||||
async def _repo_add(self, ctx, name: str, repo_url: str, branch: str = None):
|
async def _repo_add(self, ctx, name: str, repo_url: str, branch: str = None):
|
||||||
"""
|
"""Add a new repo.
|
||||||
Add a new repo to Downloader.
|
|
||||||
|
|
||||||
Name can only contain characters A-z, numbers and underscore
|
The name can only contain characters A-z, numbers and underscores.
|
||||||
Branch will default to master if not specified
|
The branch will be the default branch if not specified.
|
||||||
"""
|
"""
|
||||||
agreed = await do_install_agreement(ctx)
|
agreed = await do_install_agreement(ctx)
|
||||||
if not agreed:
|
if not agreed:
|
||||||
@ -242,24 +237,22 @@ class Downloader(commands.Cog):
|
|||||||
exc_info=err,
|
exc_info=err,
|
||||||
)
|
)
|
||||||
else:
|
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:
|
if repo.install_msg is not None:
|
||||||
await ctx.send(repo.install_msg.replace("[p]", ctx.prefix))
|
await ctx.send(repo.install_msg.replace("[p]", ctx.prefix))
|
||||||
|
|
||||||
@repo.command(name="delete")
|
@repo.command(name="delete", aliases=["remove"], usage="<repo_name>")
|
||||||
async def _repo_del(self, ctx, repo_name: Repo):
|
async def _repo_del(self, ctx, repo: Repo):
|
||||||
"""
|
"""Remove a repo and its files."""
|
||||||
Removes a repo from Downloader and its' files.
|
await self._repo_manager.delete_repo(repo.name)
|
||||||
"""
|
|
||||||
await self._repo_manager.delete_repo(repo_name.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")
|
@repo.command(name="list")
|
||||||
async def _repo_list(self, ctx):
|
async def _repo_list(self, ctx):
|
||||||
"""
|
"""List all installed repos."""
|
||||||
Lists all installed repos.
|
|
||||||
"""
|
|
||||||
repos = self._repo_manager.get_all_repo_names()
|
repos = self._repo_manager.get_all_repo_names()
|
||||||
repos = sorted(repos, key=str.lower)
|
repos = sorted(repos, key=str.lower)
|
||||||
joined = _("Installed Repos:\n\n")
|
joined = _("Installed Repos:\n\n")
|
||||||
@ -270,94 +263,93 @@ class Downloader(commands.Cog):
|
|||||||
for page in pagify(joined, ["\n"], shorten_by=16):
|
for page in pagify(joined, ["\n"], shorten_by=16):
|
||||||
await ctx.send(box(page.lstrip(" "), lang="diff"))
|
await ctx.send(box(page.lstrip(" "), lang="diff"))
|
||||||
|
|
||||||
@repo.command(name="info")
|
@repo.command(name="info", usage="<repo_name>")
|
||||||
async def _repo_info(self, ctx, repo_name: Repo):
|
async def _repo_info(self, ctx, repo: Repo):
|
||||||
"""
|
"""Show information about a repo."""
|
||||||
Lists information about a single repo
|
if repo is None:
|
||||||
"""
|
await ctx.send(_("Repo `{repo.name}` not found.").format(repo=repo))
|
||||||
if repo_name is None:
|
|
||||||
await ctx.send(_("There is no repo `{}`").format(repo_name.name))
|
|
||||||
return
|
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))
|
await ctx.send(box(msg))
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def cog(self, ctx):
|
async def cog(self, ctx):
|
||||||
"""
|
"""Cog installation management commands."""
|
||||||
Command group for managing installable Cogs.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@cog.command(name="install")
|
@cog.command(name="install", usage="<repo_name> <cog_name>")
|
||||||
async def _cog_install(self, ctx, repo_name: Repo, cog_name: str):
|
async def _cog_install(self, ctx, repo: Repo, cog_name: str):
|
||||||
"""
|
"""Install a cog from the given repo."""
|
||||||
Installs a cog from the given repo.
|
cog: Installable = discord.utils.get(repo.available_cogs, name=cog_name)
|
||||||
"""
|
|
||||||
cog = discord.utils.get(repo_name.available_cogs, name=cog_name) # type: Installable
|
|
||||||
if cog is None:
|
if cog is None:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
elif cog.min_python_version > sys.version_info:
|
elif cog.min_python_version > sys.version_info:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("This cog requires at least python version {}, aborting install.").format(
|
_("This cog requires at least python version {version}, aborting install.").format(
|
||||||
".".join([str(n) for n in cog.min_python_version])
|
version=".".join([str(n) for n in cog.min_python_version])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
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(
|
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
|
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 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:
|
if cog.install_msg is not None:
|
||||||
await ctx.send(cog.install_msg.replace("[p]", ctx.prefix))
|
await ctx.send(cog.install_msg.replace("[p]", ctx.prefix))
|
||||||
|
|
||||||
@cog.command(name="uninstall")
|
@cog.command(name="uninstall", usage="<cog_name>")
|
||||||
async def _cog_uninstall(self, ctx, cog_name: InstalledCog):
|
async def _cog_uninstall(self, ctx, cog: InstalledCog):
|
||||||
"""
|
"""Uninstall a cog.
|
||||||
Allows you to uninstall cogs that were previously installed
|
|
||||||
through Downloader.
|
You may only uninstall cogs which were previously installed
|
||||||
|
by Downloader.
|
||||||
"""
|
"""
|
||||||
# noinspection PyUnresolvedReferences,PyProtectedMember
|
# noinspection PyUnresolvedReferences,PyProtectedMember
|
||||||
real_name = cog_name.name
|
real_name = cog.name
|
||||||
|
|
||||||
poss_installed_path = (await self.cog_install_path()) / real_name
|
poss_installed_path = (await self.cog_install_path()) / real_name
|
||||||
if poss_installed_path.exists():
|
if poss_installed_path.exists():
|
||||||
await self._delete_cog(poss_installed_path)
|
await self._delete_cog(poss_installed_path)
|
||||||
# noinspection PyTypeChecker
|
# noinspection PyTypeChecker
|
||||||
await self._remove_from_installed(cog_name)
|
await self._remove_from_installed(cog)
|
||||||
await ctx.send(_("`{}` was successfully removed.").format(real_name))
|
await ctx.send(
|
||||||
|
_("Cog `{cog_name}` was successfully uninstalled.").format(cog_name=real_name)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"That cog was installed but can no longer"
|
"That cog was installed but can no longer"
|
||||||
" be located. You may need to remove it's"
|
" be located. You may need to remove it's"
|
||||||
" files manually if it is still usable."
|
" 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")
|
@cog.command(name="update")
|
||||||
async def _cog_update(self, ctx, cog_name: InstalledCog = None):
|
async def _cog_update(self, ctx, cog_name: InstalledCog = None):
|
||||||
"""
|
"""Update all cogs, or one of your choosing."""
|
||||||
Updates all cogs or one of your choosing.
|
|
||||||
"""
|
|
||||||
installed_cogs = set(await self.installed_cogs())
|
installed_cogs = set(await self.installed_cogs())
|
||||||
|
|
||||||
async with ctx.typing():
|
async with ctx.typing():
|
||||||
@ -418,11 +410,9 @@ class Downloader(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("OK then."))
|
await ctx.send(_("OK then."))
|
||||||
|
|
||||||
@cog.command(name="list")
|
@cog.command(name="list", usage="<repo_name>")
|
||||||
async def _cog_list(self, ctx, repo_name: Repo):
|
async def _cog_list(self, ctx, repo: Repo):
|
||||||
"""
|
"""List all available cogs from a single repo."""
|
||||||
Lists all available cogs from a single repo.
|
|
||||||
"""
|
|
||||||
installed = await self.installed_cogs()
|
installed = await self.installed_cogs()
|
||||||
installed_str = ""
|
installed_str = ""
|
||||||
if installed:
|
if installed:
|
||||||
@ -430,10 +420,10 @@ class Downloader(commands.Cog):
|
|||||||
[
|
[
|
||||||
"- {}{}".format(i.name, ": {}".format(i.short) if i.short else "")
|
"- {}{}".format(i.name, ": {}".format(i.short) if i.short else "")
|
||||||
for i in installed
|
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(
|
cogs = _("Available Cogs:\n") + "\n".join(
|
||||||
[
|
[
|
||||||
"+ {}: {}".format(c.name, c.short or "")
|
"+ {}: {}".format(c.name, c.short or "")
|
||||||
@ -445,20 +435,24 @@ class Downloader(commands.Cog):
|
|||||||
for page in pagify(cogs, ["\n"], shorten_by=16):
|
for page in pagify(cogs, ["\n"], shorten_by=16):
|
||||||
await ctx.send(box(page.lstrip(" "), lang="diff"))
|
await ctx.send(box(page.lstrip(" "), lang="diff"))
|
||||||
|
|
||||||
@cog.command(name="info")
|
@cog.command(name="info", usage="<repo_name> <cog_name>")
|
||||||
async def _cog_info(self, ctx, repo_name: Repo, cog_name: str):
|
async def _cog_info(self, ctx, repo: Repo, cog_name: str):
|
||||||
"""
|
"""List information about a single cog."""
|
||||||
Lists information about a single cog.
|
cog = discord.utils.get(repo.available_cogs, name=cog_name)
|
||||||
"""
|
|
||||||
cog = discord.utils.get(repo_name.available_cogs, name=cog_name)
|
|
||||||
if cog is None:
|
if cog is None:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
|
|
||||||
msg = _("Information on {}:\n{}\n\nRequirements: {}").format(
|
msg = _(
|
||||||
cog.name, cog.description or "", ", ".join(cog.requirements) or "None"
|
"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))
|
await ctx.send(box(msg))
|
||||||
|
|
||||||
@ -512,9 +506,9 @@ class Downloader(commands.Cog):
|
|||||||
repo_url = "https://github.com/Cog-Creators/Red-DiscordBot"
|
repo_url = "https://github.com/Cog-Creators/Red-DiscordBot"
|
||||||
cog_name = cog_installable.__class__.__name__
|
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:
|
def cog_name_from_instance(self, instance: object) -> str:
|
||||||
"""Determines the cog name that Downloader knows from the cog instance.
|
"""Determines the cog name that Downloader knows from the cog instance.
|
||||||
@ -537,9 +531,9 @@ class Downloader(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def findcog(self, ctx: commands.Context, command_name: str):
|
async def findcog(self, ctx: commands.Context, command_name: str):
|
||||||
"""
|
"""Find which cog a command comes from.
|
||||||
Figures out which cog a command comes from. Only works with loaded
|
|
||||||
cogs.
|
This will only work with loaded cogs.
|
||||||
"""
|
"""
|
||||||
command = ctx.bot.all_commands.get(command_name)
|
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 import data_manager, commands
|
||||||
from redbot.core.utils import safe_delete
|
from redbot.core.utils import safe_delete
|
||||||
|
from redbot.core.i18n import Translator
|
||||||
|
|
||||||
from . import errors
|
from . import errors
|
||||||
from .installable import Installable, InstallableType
|
from .installable import Installable, InstallableType
|
||||||
from .json_mixins import RepoJSONMixin
|
from .json_mixins import RepoJSONMixin
|
||||||
from .log import log
|
from .log import log
|
||||||
|
|
||||||
|
_ = Translator("RepoManager", __file__)
|
||||||
|
|
||||||
|
|
||||||
class Repo(RepoJSONMixin):
|
class Repo(RepoJSONMixin):
|
||||||
GIT_CLONE = "git clone --recurse-submodules -b {branch} {url} {folder}"
|
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):
|
async def convert(cls, ctx: commands.Context, argument: str):
|
||||||
downloader_cog = ctx.bot.get_cog("Downloader")
|
downloader_cog = ctx.bot.get_cog("Downloader")
|
||||||
if downloader_cog is None:
|
if downloader_cog is None:
|
||||||
raise commands.CommandError("No Downloader cog found.")
|
raise commands.CommandError(_("No Downloader cog found."))
|
||||||
|
|
||||||
# noinspection PyProtectedMember
|
# noinspection PyProtectedMember
|
||||||
repo_manager = downloader_cog._repo_manager
|
repo_manager = downloader_cog._repo_manager
|
||||||
poss_repo = repo_manager.get_repo(argument)
|
poss_repo = repo_manager.get_repo(argument)
|
||||||
if poss_repo is None:
|
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
|
return poss_repo
|
||||||
|
|
||||||
def _existing_git_repo(self) -> (bool, Path):
|
def _existing_git_repo(self) -> (bool, Path):
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import logging
|
|||||||
import random
|
import random
|
||||||
from collections import defaultdict, deque
|
from collections import defaultdict, deque
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import cast, Iterable
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@ -14,7 +15,7 @@ from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
|||||||
|
|
||||||
from redbot.core.bot import Red
|
from redbot.core.bot import Red
|
||||||
|
|
||||||
_ = Translator("Economy", __file__)
|
T_ = Translator("Economy", __file__)
|
||||||
|
|
||||||
logger = logging.getLogger("red.economy")
|
logger = logging.getLogger("red.economy")
|
||||||
|
|
||||||
@ -34,6 +35,7 @@ class SMReel(Enum):
|
|||||||
snowflake = "\N{SNOWFLAKE}"
|
snowflake = "\N{SNOWFLAKE}"
|
||||||
|
|
||||||
|
|
||||||
|
_ = lambda s: s
|
||||||
PAYOUTS = {
|
PAYOUTS = {
|
||||||
(SMReel.two, SMReel.two, SMReel.six): {
|
(SMReel.two, SMReel.two, SMReel.six): {
|
||||||
"payout": lambda x: x * 2500 + x,
|
"payout": lambda x: x * 2500 + x,
|
||||||
@ -72,6 +74,7 @@ SLOT_PAYOUTS_MSG = _(
|
|||||||
"Three symbols: +500\n"
|
"Three symbols: +500\n"
|
||||||
"Two symbols: Bet * 2"
|
"Two symbols: Bet * 2"
|
||||||
).format(**SMReel.__dict__)
|
).format(**SMReel.__dict__)
|
||||||
|
_ = T_
|
||||||
|
|
||||||
|
|
||||||
def guild_only_check():
|
def guild_only_check():
|
||||||
@ -106,9 +109,7 @@ class SetParser:
|
|||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Economy(commands.Cog):
|
class Economy(commands.Cog):
|
||||||
"""Economy
|
"""Get rich and have fun with imaginary currency!"""
|
||||||
|
|
||||||
Get rich and have fun with imaginary currency!"""
|
|
||||||
|
|
||||||
default_guild_settings = {
|
default_guild_settings = {
|
||||||
"PAYDAY_TIME": 300,
|
"PAYDAY_TIME": 300,
|
||||||
@ -142,12 +143,12 @@ class Economy(commands.Cog):
|
|||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
@commands.group(name="bank")
|
@commands.group(name="bank")
|
||||||
async def _bank(self, ctx: commands.Context):
|
async def _bank(self, ctx: commands.Context):
|
||||||
"""Bank operations"""
|
"""Manage the bank."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@_bank.command()
|
@_bank.command()
|
||||||
async def balance(self, ctx: commands.Context, user: discord.Member = None):
|
async def balance(self, ctx: commands.Context, user: discord.Member = None):
|
||||||
"""Shows balance of user.
|
"""Show the user's account balance.
|
||||||
|
|
||||||
Defaults to yours."""
|
Defaults to yours."""
|
||||||
if user is None:
|
if user is None:
|
||||||
@ -156,11 +157,15 @@ class Economy(commands.Cog):
|
|||||||
bal = await bank.get_balance(user)
|
bal = await bank.get_balance(user)
|
||||||
currency = await bank.get_currency_name(ctx.guild)
|
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()
|
@_bank.command()
|
||||||
async def transfer(self, ctx: commands.Context, to: discord.Member, amount: int):
|
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
|
from_ = ctx.author
|
||||||
currency = await bank.get_currency_name(ctx.guild)
|
currency = await bank.get_currency_name(ctx.guild)
|
||||||
|
|
||||||
@ -170,72 +175,83 @@ class Economy(commands.Cog):
|
|||||||
return await ctx.send(str(e))
|
return await ctx.send(str(e))
|
||||||
|
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("{} transferred {} {} to {}").format(
|
_("{user} transferred {num} {currency} to {other_user}").format(
|
||||||
from_.display_name, amount, currency, to.display_name
|
user=from_.display_name, num=amount, currency=currency, other_user=to.display_name
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@_bank.command(name="set")
|
@_bank.command(name="set")
|
||||||
@check_global_setting_admin()
|
@check_global_setting_admin()
|
||||||
async def _set(self, ctx: commands.Context, to: discord.Member, creds: SetParser):
|
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:
|
Examples:
|
||||||
bank set @Twentysix 26 - Sets balance to 26
|
- `[p]bank set @Twentysix 26` - Sets balance to 26
|
||||||
bank set @Twentysix +2 - Increases balance by 2
|
- `[p]bank set @Twentysix +2` - Increases balance by 2
|
||||||
bank set @Twentysix -6 - Decreases balance by 6"""
|
- `[p]bank set @Twentysix -6` - Decreases balance by 6
|
||||||
|
"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
currency = await bank.get_currency_name(ctx.guild)
|
currency = await bank.get_currency_name(ctx.guild)
|
||||||
|
|
||||||
if creds.operation == "deposit":
|
if creds.operation == "deposit":
|
||||||
await bank.deposit_credits(to, creds.sum)
|
await bank.deposit_credits(to, creds.sum)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("{} added {} {} to {}'s account.").format(
|
_("{author} added {num} {currency} to {user}'s account.").format(
|
||||||
author.display_name, creds.sum, currency, to.display_name
|
author=author.display_name,
|
||||||
|
num=creds.sum,
|
||||||
|
currency=currency,
|
||||||
|
user=to.display_name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
elif creds.operation == "withdraw":
|
elif creds.operation == "withdraw":
|
||||||
await bank.withdraw_credits(to, creds.sum)
|
await bank.withdraw_credits(to, creds.sum)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("{} removed {} {} from {}'s account.").format(
|
_("{author} removed {num} {currency} from {user}'s account.").format(
|
||||||
author.display_name, creds.sum, currency, to.display_name
|
author=author.display_name,
|
||||||
|
num=creds.sum,
|
||||||
|
currency=currency,
|
||||||
|
user=to.display_name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await bank.set_balance(to, creds.sum)
|
await bank.set_balance(to, creds.sum)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("{} set {}'s account to {} {}.").format(
|
_("{author} set {users}'s account balance to {num} {currency}.").format(
|
||||||
author.display_name, to.display_name, creds.sum, currency
|
author=author.display_name,
|
||||||
|
num=creds.sum,
|
||||||
|
currency=currency,
|
||||||
|
user=to.display_name,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@_bank.command()
|
@_bank.command()
|
||||||
@check_global_setting_guildowner()
|
@check_global_setting_guildowner()
|
||||||
async def reset(self, ctx, confirmation: bool = False):
|
async def reset(self, ctx, confirmation: bool = False):
|
||||||
"""Deletes bank accounts"""
|
"""Delete all bank accounts."""
|
||||||
if confirmation is False:
|
if confirmation is False:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"This will delete all bank accounts for {}.\nIf you're sure, type "
|
"This will delete all bank accounts for {scope}.\nIf you're sure, type "
|
||||||
"`{}bank reset yes`"
|
"`{prefix}bank reset yes`"
|
||||||
).format(
|
).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:
|
else:
|
||||||
await bank.wipe_bank()
|
await bank.wipe_bank(guild=ctx.guild)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("All bank accounts for {} have been deleted.").format(
|
_("All bank accounts for {scope} have been deleted.").format(
|
||||||
self.bot.user.name if await bank.is_global() else "this server"
|
scope=self.bot.user.name if await bank.is_global() else _("this server")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def payday(self, ctx: commands.Context):
|
async def payday(self, ctx: commands.Context):
|
||||||
"""Get some free currency"""
|
"""Get some free currency."""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
@ -251,24 +267,25 @@ class Economy(commands.Cog):
|
|||||||
pos = await bank.get_leaderboard_position(author)
|
pos = await bank.get_leaderboard_position(author)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
"{author.mention} Here, take some {currency}. "
|
||||||
"You currently have {3} {1}.\n\n"
|
"Enjoy! (+{amount} {new_balance}!)\n\n"
|
||||||
"You are currently #{4} on the global leaderboard!"
|
"You currently have {new_balance} {currency}.\n\n"
|
||||||
|
"You are currently #{pos} on the global leaderboard!"
|
||||||
).format(
|
).format(
|
||||||
author,
|
author=author,
|
||||||
credits_name,
|
currency=credits_name,
|
||||||
str(await self.config.PAYDAY_CREDITS()),
|
amount=await self.config.PAYDAY_CREDITS(),
|
||||||
str(await bank.get_balance(author)),
|
new_balance=await bank.get_balance(author),
|
||||||
pos,
|
pos=pos,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
dtime = self.display_time(next_payday - cur_time)
|
dtime = self.display_time(next_payday - cur_time)
|
||||||
await ctx.send(
|
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:
|
else:
|
||||||
next_payday = await self.config.member(author).next_payday()
|
next_payday = await self.config.member(author).next_payday()
|
||||||
@ -286,31 +303,33 @@ class Economy(commands.Cog):
|
|||||||
pos = await bank.get_leaderboard_position(author)
|
pos = await bank.get_leaderboard_position(author)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
|
"{author.mention} Here, take some {currency}. "
|
||||||
"You currently have {3} {1}.\n\n"
|
"Enjoy! (+{amount} {new_balance}!)\n\n"
|
||||||
"You are currently #{4} on the leaderboard!"
|
"You currently have {new_balance} {currency}.\n\n"
|
||||||
|
"You are currently #{pos} on the global leaderboard!"
|
||||||
).format(
|
).format(
|
||||||
author,
|
author=author,
|
||||||
credits_name,
|
currency=credits_name,
|
||||||
credit_amount,
|
amount=credit_amount,
|
||||||
str(await bank.get_balance(author)),
|
new_balance=await bank.get_balance(author),
|
||||||
pos,
|
pos=pos,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
dtime = self.display_time(next_payday - cur_time)
|
dtime = self.display_time(next_payday - cur_time)
|
||||||
await ctx.send(
|
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()
|
@commands.command()
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
async def leaderboard(self, ctx: commands.Context, top: int = 10, show_global: bool = False):
|
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
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
if top < 1:
|
if top < 1:
|
||||||
@ -320,9 +339,9 @@ class Economy(commands.Cog):
|
|||||||
): # show_global is only applicable if bank is global
|
): # show_global is only applicable if bank is global
|
||||||
guild = None
|
guild = None
|
||||||
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
|
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
|
||||||
if len(bank_sorted) < top:
|
header = "{pound:4}{name:36}{score:2}\n".format(
|
||||||
top = len(bank_sorted)
|
pound="#", name=_("Name"), score=_("Score")
|
||||||
header = f"{f'#':4}{f'Name':36}{f'Score':2}\n"
|
)
|
||||||
highscores = [
|
highscores = [
|
||||||
(
|
(
|
||||||
f"{f'{pos}.': <{3 if pos < 10 else 2}} {acc[1]['name']: <{35}s} "
|
f"{f'{pos}.': <{3 if pos < 10 else 2}} {acc[1]['name']: <{35}s} "
|
||||||
@ -347,13 +366,13 @@ class Economy(commands.Cog):
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
async def payouts(self, ctx: commands.Context):
|
async def payouts(self, ctx: commands.Context):
|
||||||
"""Shows slot machine payouts"""
|
"""Show the payouts for the slot machine."""
|
||||||
await ctx.author.send(SLOT_PAYOUTS_MSG)
|
await ctx.author.send(SLOT_PAYOUTS_MSG())
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
async def slot(self, ctx: commands.Context, bid: int):
|
async def slot(self, ctx: commands.Context, bid: int):
|
||||||
"""Play the slot machine"""
|
"""Use the slot machine."""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
@ -386,8 +405,9 @@ class Economy(commands.Cog):
|
|||||||
await self.config.member(author).last_slot.set(now)
|
await self.config.member(author).last_slot.set(now)
|
||||||
await self.slot_machine(author, channel, bid)
|
await self.slot_machine(author, channel, bid)
|
||||||
|
|
||||||
async def slot_machine(self, author, channel, bid):
|
@staticmethod
|
||||||
default_reel = deque(SMReel)
|
async def slot_machine(author, channel, bid):
|
||||||
|
default_reel = deque(cast(Iterable, SMReel))
|
||||||
reels = []
|
reels = []
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
default_reel.rotate(random.randint(-999, 999)) # weeeeee
|
default_reel.rotate(random.randint(-999, 999)) # weeeeee
|
||||||
@ -425,60 +445,62 @@ class Economy(commands.Cog):
|
|||||||
pay = payout["payout"](bid)
|
pay = payout["payout"](bid)
|
||||||
now = then - bid + pay
|
now = then - bid + pay
|
||||||
await bank.set_balance(author, now)
|
await bank.set_balance(author, now)
|
||||||
await channel.send(
|
phrase = T_(payout["phrase"])
|
||||||
_("{}\n{} {}\n\nYour bid: {}\n{} → {}!").format(
|
|
||||||
slot, author.mention, payout["phrase"], bid, then, now
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
then = await bank.get_balance(author)
|
then = await bank.get_balance(author)
|
||||||
await bank.withdraw_credits(author, bid)
|
await bank.withdraw_credits(author, bid)
|
||||||
now = then - bid
|
now = then - bid
|
||||||
await channel.send(
|
phrase = _("Nothing!")
|
||||||
_("{}\n{} Nothing!\nYour bid: {}\n{} → {}!").format(
|
await channel.send(
|
||||||
slot, author.mention, bid, then, now
|
(
|
||||||
)
|
"{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()
|
@commands.group()
|
||||||
@guild_only_check()
|
@guild_only_check()
|
||||||
@check_global_setting_admin()
|
@check_global_setting_admin()
|
||||||
async def economyset(self, ctx: commands.Context):
|
async def economyset(self, ctx: commands.Context):
|
||||||
"""Changes economy module settings"""
|
"""Manage Economy settings."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
slot_min = await self.config.SLOT_MIN()
|
conf = self.config
|
||||||
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()
|
|
||||||
else:
|
else:
|
||||||
slot_min = await self.config.guild(guild).SLOT_MIN()
|
conf = self.config.guild(ctx.guild)
|
||||||
slot_max = await self.config.guild(guild).SLOT_MAX()
|
await ctx.send(
|
||||||
slot_time = await self.config.guild(guild).SLOT_TIME()
|
box(
|
||||||
payday_time = await self.config.guild(guild).PAYDAY_TIME()
|
_(
|
||||||
payday_amount = await self.config.guild(guild).PAYDAY_CREDITS()
|
"----Economy Settings---\n"
|
||||||
register_amount = await bank.get_default_balance(guild)
|
"Minimum slot bid: {slot_min}\n"
|
||||||
msg = box(
|
"Maximum slot bid: {slot_max}\n"
|
||||||
_(
|
"Slot cooldown: {slot_time}\n"
|
||||||
"Minimum slot bid: {}\n"
|
"Payday amount: {payday_amount}\n"
|
||||||
"Maximum slot bid: {}\n"
|
"Payday cooldown: {payday_time}\n"
|
||||||
"Slot cooldown: {}\n"
|
"Amount given at account registration: {register_amount}"
|
||||||
"Payday amount: {}\n"
|
).format(
|
||||||
"Payday cooldown: {}\n"
|
slot_min=await conf.SLOT_MIN(),
|
||||||
"Amount given at account registration: {}"
|
slot_max=await conf.SLOT_MAX(),
|
||||||
""
|
slot_time=await conf.SLOT_TIME(),
|
||||||
).format(
|
payday_time=await conf.PAYDAY_TIME(),
|
||||||
slot_min, slot_max, slot_time, payday_amount, payday_time, register_amount
|
payday_amount=await conf.PAYDAY_CREDITS(),
|
||||||
),
|
register_amount=await bank.get_default_balance(guild),
|
||||||
_("Current Economy settings:"),
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
await ctx.send(msg)
|
|
||||||
|
|
||||||
@economyset.command()
|
@economyset.command()
|
||||||
async def slotmin(self, ctx: commands.Context, bid: int):
|
async def slotmin(self, ctx: commands.Context, bid: int):
|
||||||
"""Minimum slot machine bid"""
|
"""Set the minimum slot machine bid."""
|
||||||
if bid < 1:
|
if bid < 1:
|
||||||
await ctx.send(_("Invalid min bid amount."))
|
await ctx.send(_("Invalid min bid amount."))
|
||||||
return
|
return
|
||||||
@ -488,14 +510,18 @@ class Economy(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await self.config.guild(guild).SLOT_MIN.set(bid)
|
await self.config.guild(guild).SLOT_MIN.set(bid)
|
||||||
credits_name = await bank.get_currency_name(guild)
|
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()
|
@economyset.command()
|
||||||
async def slotmax(self, ctx: commands.Context, bid: int):
|
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()
|
slot_min = await self.config.SLOT_MIN()
|
||||||
if bid < 1 or bid < 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
|
return
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
@ -503,33 +529,37 @@ class Economy(commands.Cog):
|
|||||||
await self.config.SLOT_MAX.set(bid)
|
await self.config.SLOT_MAX.set(bid)
|
||||||
else:
|
else:
|
||||||
await self.config.guild(guild).SLOT_MAX.set(bid)
|
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()
|
@economyset.command()
|
||||||
async def slottime(self, ctx: commands.Context, seconds: int):
|
async def slottime(self, ctx: commands.Context, seconds: int):
|
||||||
"""Seconds between each slots use"""
|
"""Set the cooldown for the slot machine."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
await self.config.SLOT_TIME.set(seconds)
|
await self.config.SLOT_TIME.set(seconds)
|
||||||
else:
|
else:
|
||||||
await self.config.guild(guild).SLOT_TIME.set(seconds)
|
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()
|
@economyset.command()
|
||||||
async def paydaytime(self, ctx: commands.Context, seconds: int):
|
async def paydaytime(self, ctx: commands.Context, seconds: int):
|
||||||
"""Seconds between each payday"""
|
"""Set the cooldown for payday."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if await bank.is_global():
|
if await bank.is_global():
|
||||||
await self.config.PAYDAY_TIME.set(seconds)
|
await self.config.PAYDAY_TIME.set(seconds)
|
||||||
else:
|
else:
|
||||||
await self.config.guild(guild).PAYDAY_TIME.set(seconds)
|
await self.config.guild(guild).PAYDAY_TIME.set(seconds)
|
||||||
await ctx.send(
|
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()
|
@economyset.command()
|
||||||
async def paydayamount(self, ctx: commands.Context, creds: int):
|
async def paydayamount(self, ctx: commands.Context, creds: int):
|
||||||
"""Amount earned each payday"""
|
"""Set the amount earned each payday."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
if creds <= 0:
|
if creds <= 0:
|
||||||
@ -539,37 +569,45 @@ class Economy(commands.Cog):
|
|||||||
await self.config.PAYDAY_CREDITS.set(creds)
|
await self.config.PAYDAY_CREDITS.set(creds)
|
||||||
else:
|
else:
|
||||||
await self.config.guild(guild).PAYDAY_CREDITS.set(creds)
|
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()
|
@economyset.command()
|
||||||
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
|
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
|
guild = ctx.guild
|
||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
if await bank.is_global():
|
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:
|
else:
|
||||||
await self.config.role(role).PAYDAY_CREDITS.set(creds)
|
await self.config.role(role).PAYDAY_CREDITS.set(creds)
|
||||||
await ctx.send(
|
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()
|
@economyset.command()
|
||||||
async def registeramount(self, ctx: commands.Context, creds: int):
|
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
|
guild = ctx.guild
|
||||||
if creds < 0:
|
if creds < 0:
|
||||||
creds = 0
|
creds = 0
|
||||||
credits_name = await bank.get_currency_name(guild)
|
credits_name = await bank.get_currency_name(guild)
|
||||||
await bank.set_default_balance(creds, guild)
|
await bank.set_default_balance(creds, guild)
|
||||||
await ctx.send(
|
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?
|
# 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
|
intervals = ( # Source: http://stackoverflow.com/a/24542445
|
||||||
(_("weeks"), 604800), # 60 * 60 * 24 * 7
|
(_("weeks"), 604800), # 60 * 60 * 24 * 7
|
||||||
(_("days"), 86400), # 60 * 60 * 24
|
(_("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.bot import Red
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.chat_formatting import pagify
|
from redbot.core.utils.chat_formatting import pagify
|
||||||
from redbot.core.utils.mod import is_mod_or_superior
|
|
||||||
|
|
||||||
_ = Translator("Filter", __file__)
|
_ = Translator("Filter", __file__)
|
||||||
|
|
||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Filter(commands.Cog):
|
class Filter(commands.Cog):
|
||||||
"""Filter-related commands"""
|
"""Filter unwanted words and phrases from text channels."""
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -35,7 +34,8 @@ class Filter(commands.Cog):
|
|||||||
def __unload(self):
|
def __unload(self):
|
||||||
self.register_task.cancel()
|
self.register_task.cancel()
|
||||||
|
|
||||||
async def register_filterban(self):
|
@staticmethod
|
||||||
|
async def register_filterban():
|
||||||
try:
|
try:
|
||||||
await modlog.register_casetype(
|
await modlog.register_casetype(
|
||||||
"filterban", False, ":filing_cabinet: :hammer:", "Filter ban", "ban"
|
"filterban", False, ":filing_cabinet: :hammer:", "Filter ban", "ban"
|
||||||
@ -47,18 +47,17 @@ class Filter(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(manage_guild=True)
|
@checks.admin_or_permissions(manage_guild=True)
|
||||||
async def filterset(self, ctx: commands.Context):
|
async def filterset(self, ctx: commands.Context):
|
||||||
"""
|
"""Manage filter settings."""
|
||||||
Filter settings
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@filterset.command(name="defaultname")
|
@filterset.command(name="defaultname")
|
||||||
async def filter_default_name(self, ctx: commands.Context, name: str):
|
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
|
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
|
guild = ctx.guild
|
||||||
await self.settings.guild(guild).filter_default_name.set(name)
|
await self.settings.guild(guild).filter_default_name.set(name)
|
||||||
@ -66,9 +65,12 @@ class Filter(commands.Cog):
|
|||||||
|
|
||||||
@filterset.command(name="ban")
|
@filterset.command(name="ban")
|
||||||
async def filter_ban(self, ctx: commands.Context, count: int, timeframe: int):
|
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):
|
if (count <= 0) != (timeframe <= 0):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
@ -91,11 +93,13 @@ class Filter(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.mod_or_permissions(manage_messages=True)
|
@checks.mod_or_permissions(manage_messages=True)
|
||||||
async def _filter(self, ctx: commands.Context):
|
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
|
Use double quotes to add or remove sentences.
|
||||||
Using this command with no subcommands will send
|
|
||||||
the list of the server's filtered words."""
|
Using this command with no subcommands will send the list of
|
||||||
|
the server's filtered words.
|
||||||
|
"""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
server = ctx.guild
|
server = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
@ -111,11 +115,13 @@ class Filter(commands.Cog):
|
|||||||
|
|
||||||
@_filter.group(name="channel")
|
@_filter.group(name="channel")
|
||||||
async def _filter_channel(self, ctx: commands.Context):
|
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
|
Use double quotes to add or remove sentences.
|
||||||
Using this command with no subcommands will send
|
|
||||||
the list of the channel's filtered words."""
|
Using this command with no subcommands will send the list of
|
||||||
|
the channel's filtered words.
|
||||||
|
"""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
@ -131,12 +137,14 @@ class Filter(commands.Cog):
|
|||||||
|
|
||||||
@_filter_channel.command("add")
|
@_filter_channel.command("add")
|
||||||
async def filter_channel_add(self, ctx: commands.Context, *, words: str):
|
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:
|
Examples:
|
||||||
filter add word1 word2 word3
|
- `[p]filter channel add word1 word2 word3`
|
||||||
filter add \"This is a sentence\""""
|
- `[p]filter channel add "This is a sentence"`
|
||||||
|
"""
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
split_words = words.split()
|
split_words = words.split()
|
||||||
word_list = []
|
word_list = []
|
||||||
@ -161,12 +169,14 @@ class Filter(commands.Cog):
|
|||||||
|
|
||||||
@_filter_channel.command("remove")
|
@_filter_channel.command("remove")
|
||||||
async def filter_channel_remove(self, ctx: commands.Context, *, words: str):
|
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:
|
Examples:
|
||||||
filter remove word1 word2 word3
|
- `[p]filter channel remove word1 word2 word3`
|
||||||
filter remove \"This is a sentence\""""
|
- `[p]filter channel remove "This is a sentence"`
|
||||||
|
"""
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
split_words = words.split()
|
split_words = words.split()
|
||||||
word_list = []
|
word_list = []
|
||||||
@ -191,12 +201,14 @@ class Filter(commands.Cog):
|
|||||||
|
|
||||||
@_filter.command(name="add")
|
@_filter.command(name="add")
|
||||||
async def filter_add(self, ctx: commands.Context, *, words: str):
|
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:
|
Examples:
|
||||||
filter add word1 word2 word3
|
- `[p]filter add word1 word2 word3`
|
||||||
filter add \"This is a sentence\""""
|
- `[p]filter add "This is a sentence"`
|
||||||
|
"""
|
||||||
server = ctx.guild
|
server = ctx.guild
|
||||||
split_words = words.split()
|
split_words = words.split()
|
||||||
word_list = []
|
word_list = []
|
||||||
@ -215,18 +227,20 @@ class Filter(commands.Cog):
|
|||||||
tmp += word + " "
|
tmp += word + " "
|
||||||
added = await self.add_to_filter(server, word_list)
|
added = await self.add_to_filter(server, word_list)
|
||||||
if added:
|
if added:
|
||||||
await ctx.send(_("Words added to filter."))
|
await ctx.send(_("Words successfully added to filter."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Words already in the filter."))
|
await ctx.send(_("Those words were already in the filter."))
|
||||||
|
|
||||||
@_filter.command(name="remove")
|
@_filter.command(name="remove")
|
||||||
async def filter_remove(self, ctx: commands.Context, *, words: str):
|
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:
|
Examples:
|
||||||
filter remove word1 word2 word3
|
- `[p]filter remove word1 word2 word3`
|
||||||
filter remove \"This is a sentence\""""
|
- `[p]filter remove "This is a sentence"`
|
||||||
|
"""
|
||||||
server = ctx.guild
|
server = ctx.guild
|
||||||
split_words = words.split()
|
split_words = words.split()
|
||||||
word_list = []
|
word_list = []
|
||||||
@ -245,23 +259,23 @@ class Filter(commands.Cog):
|
|||||||
tmp += word + " "
|
tmp += word + " "
|
||||||
removed = await self.remove_from_filter(server, word_list)
|
removed = await self.remove_from_filter(server, word_list)
|
||||||
if removed:
|
if removed:
|
||||||
await ctx.send(_("Words removed from filter."))
|
await ctx.send(_("Words successfully removed from filter."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Those words weren't in the filter."))
|
await ctx.send(_("Those words weren't in the filter."))
|
||||||
|
|
||||||
@_filter.command(name="names")
|
@_filter.command(name="names")
|
||||||
async def filter_names(self, ctx: commands.Context):
|
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
|
guild = ctx.guild
|
||||||
current_setting = await self.settings.guild(guild).filter_names()
|
current_setting = await self.settings.guild(guild).filter_names()
|
||||||
await self.settings.guild(guild).filter_names.set(not current_setting)
|
await self.settings.guild(guild).filter_names.set(not current_setting)
|
||||||
if 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:
|
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(
|
async def add_to_filter(
|
||||||
self, server_or_channel: Union[discord.Guild, discord.TextChannel], words: list
|
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():
|
if w in message.content.lower():
|
||||||
try:
|
try:
|
||||||
await message.delete()
|
await message.delete()
|
||||||
except:
|
except discord.HTTPException:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
if filter_count > 0 and filter_time > 0:
|
if filter_count > 0 and filter_time > 0:
|
||||||
@ -337,10 +351,10 @@ class Filter(commands.Cog):
|
|||||||
user_count >= filter_count
|
user_count >= filter_count
|
||||||
and message.created_at.timestamp() < next_reset_time
|
and message.created_at.timestamp() < next_reset_time
|
||||||
):
|
):
|
||||||
reason = "Autoban (too many filtered messages.)"
|
reason = _("Autoban (too many filtered messages.)")
|
||||||
try:
|
try:
|
||||||
await server.ban(author, reason=reason)
|
await server.ban(author, reason=reason)
|
||||||
except:
|
except discord.HTTPException:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
await modlog.create_case(
|
await modlog.create_case(
|
||||||
@ -366,20 +380,6 @@ class Filter(commands.Cog):
|
|||||||
|
|
||||||
await self.check_filter(message)
|
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):
|
async def on_message_edit(self, _prior, message):
|
||||||
# message content has to change for non-bot's currently.
|
# message content has to change for non-bot's currently.
|
||||||
# if this changes, we should compare before passing it.
|
# if this changes, we should compare before passing it.
|
||||||
@ -399,14 +399,14 @@ class Filter(commands.Cog):
|
|||||||
return # Discord Hierarchy applies to nicks
|
return # Discord Hierarchy applies to nicks
|
||||||
if await self.bot.is_automod_immune(member):
|
if await self.bot.is_automod_immune(member):
|
||||||
return
|
return
|
||||||
word_list = await self.settings.guild(member.guild).filter()
|
|
||||||
if not await self.settings.guild(member.guild).filter_names():
|
if not await self.settings.guild(member.guild).filter_names():
|
||||||
return
|
return
|
||||||
|
|
||||||
|
word_list = await self.settings.guild(member.guild).filter()
|
||||||
for w in word_list:
|
for w in word_list:
|
||||||
if w in member.display_name.lower():
|
if w in member.display_name.lower():
|
||||||
name_to_use = await self.settings.guild(member.guild).filter_default_name()
|
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:
|
try:
|
||||||
await member.edit(nick=name_to_use, reason=reason)
|
await member.edit(nick=name_to_use, reason=reason)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
|
|||||||
@ -2,15 +2,14 @@ import datetime
|
|||||||
import time
|
import time
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from random import randint, choice
|
from random import randint, choice
|
||||||
from urllib.parse import quote_plus
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import discord
|
import discord
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core.i18n import Translator, cog_i18n
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
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):
|
class RPS(Enum):
|
||||||
@ -29,71 +28,78 @@ class RPSParser:
|
|||||||
elif argument == "scissors":
|
elif argument == "scissors":
|
||||||
self.choice = RPS.scissors
|
self.choice = RPS.scissors
|
||||||
else:
|
else:
|
||||||
raise
|
raise ValueError
|
||||||
|
|
||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class General(commands.Cog):
|
class General(commands.Cog):
|
||||||
"""General commands."""
|
"""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):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.stopwatches = {}
|
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()
|
@commands.command()
|
||||||
async def choose(self, ctx, *choices):
|
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]
|
choices = [escape(c, mass_mentions=True) for c in choices]
|
||||||
if len(choices) < 2:
|
if len(choices) < 2:
|
||||||
await ctx.send(_("Not enough choices to pick from."))
|
await ctx.send(_("Not enough options to pick from."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(choice(choices))
|
await ctx.send(choice(choices))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def roll(self, ctx, number: int = 100):
|
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
|
author = ctx.author
|
||||||
if number > 1:
|
if number > 1:
|
||||||
n = randint(1, number)
|
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:
|
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()
|
@commands.command()
|
||||||
async def flip(self, ctx, user: discord.Member = None):
|
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 = ""
|
msg = ""
|
||||||
if user.id == ctx.bot.user.id:
|
if user.id == ctx.bot.user.id:
|
||||||
user = ctx.author
|
user = ctx.author
|
||||||
@ -112,7 +118,7 @@ class General(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def rps(self, ctx, your_choice: RPSParser):
|
async def rps(self, ctx, your_choice: RPSParser):
|
||||||
"""Play rock paper scissors"""
|
"""Play Rock Paper Scissors."""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
player_choice = your_choice.choice
|
player_choice = your_choice.choice
|
||||||
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
|
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
|
||||||
@ -131,39 +137,53 @@ class General(commands.Cog):
|
|||||||
outcome = cond[(player_choice, red_choice)]
|
outcome = cond[(player_choice, red_choice)]
|
||||||
|
|
||||||
if outcome is True:
|
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:
|
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:
|
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"])
|
@commands.command(name="8", aliases=["8ball"])
|
||||||
async def _8ball(self, ctx, *, question: str):
|
async def _8ball(self, ctx, *, question: str):
|
||||||
"""Ask 8 ball a question
|
"""Ask 8 ball a question.
|
||||||
|
|
||||||
Question must end with a question mark.
|
Question must end with a question mark.
|
||||||
"""
|
"""
|
||||||
if question.endswith("?") and question != "?":
|
if question.endswith("?") and question != "?":
|
||||||
await ctx.send("`" + choice(self.ball) + "`")
|
await ctx.send("`" + T_(choice(self.ball)) + "`")
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("That doesn't look like a question."))
|
await ctx.send(_("That doesn't look like a question."))
|
||||||
|
|
||||||
@commands.command(aliases=["sw"])
|
@commands.command(aliases=["sw"])
|
||||||
async def stopwatch(self, ctx):
|
async def stopwatch(self, ctx):
|
||||||
"""Starts/stops stopwatch"""
|
"""Start or stop the stopwatch."""
|
||||||
author = ctx.author
|
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())
|
self.stopwatches[author.id] = int(time.perf_counter())
|
||||||
await ctx.send(author.mention + _(" Stopwatch started!"))
|
await ctx.send(author.mention + _(" Stopwatch started!"))
|
||||||
else:
|
else:
|
||||||
tmp = abs(self.stopwatches[author.id] - int(time.perf_counter()))
|
tmp = abs(self.stopwatches[author.id] - int(time.perf_counter()))
|
||||||
tmp = str(datetime.timedelta(seconds=tmp))
|
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)
|
self.stopwatches.pop(author.id, None)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def lmgtfy(self, ctx, *, search_terms: str):
|
async def lmgtfy(self, ctx, *, search_terms: str):
|
||||||
"""Creates a lmgtfy link"""
|
"""Create a lmgtfy link."""
|
||||||
search_terms = escape(
|
search_terms = escape(
|
||||||
search_terms.replace("+", "%2B").replace(" ", "+"), mass_mentions=True
|
search_terms.replace("+", "%2B").replace(" ", "+"), mass_mentions=True
|
||||||
)
|
)
|
||||||
@ -172,9 +192,10 @@ class General(commands.Cog):
|
|||||||
@commands.command(hidden=True)
|
@commands.command(hidden=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def hug(self, ctx, user: discord.Member, intensity: int = 1):
|
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)
|
name = italics(user.display_name)
|
||||||
if intensity <= 0:
|
if intensity <= 0:
|
||||||
msg = "(っ˘̩╭╮˘̩)っ" + name
|
msg = "(っ˘̩╭╮˘̩)っ" + name
|
||||||
@ -186,24 +207,27 @@ class General(commands.Cog):
|
|||||||
msg = "(つ≧▽≦)つ" + name
|
msg = "(つ≧▽≦)つ" + name
|
||||||
elif intensity >= 10:
|
elif intensity >= 10:
|
||||||
msg = "(づ ̄ ³ ̄)づ{} ⊂(´・ω・`⊂)".format(name)
|
msg = "(づ ̄ ³ ̄)づ{} ⊂(´・ω・`⊂)".format(name)
|
||||||
|
else:
|
||||||
|
# For the purposes of "msg might not be defined" linter errors
|
||||||
|
raise RuntimeError
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def serverinfo(self, ctx):
|
async def serverinfo(self, ctx):
|
||||||
"""Shows server's informations"""
|
"""Show server information."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
online = len([m.status for m in guild.members if m.status != discord.Status.offline])
|
online = len([m.status for m in guild.members if m.status != discord.Status.offline])
|
||||||
total_users = len(guild.members)
|
total_users = len(guild.members)
|
||||||
text_channels = len(guild.text_channels)
|
text_channels = len(guild.text_channels)
|
||||||
voice_channels = len(guild.voice_channels)
|
voice_channels = len(guild.voice_channels)
|
||||||
passed = (ctx.message.created_at - guild.created_at).days
|
passed = (ctx.message.created_at - guild.created_at).days
|
||||||
created_at = _("Since {}. That's over {} days ago!").format(
|
created_at = _("Since {date}. That's over {num} days ago!").format(
|
||||||
guild.created_at.strftime("%d %b %Y %H:%M"), passed
|
date=guild.created_at.strftime("%d %b %Y %H:%M"), num=passed
|
||||||
)
|
)
|
||||||
data = discord.Embed(description=created_at, colour=(await ctx.embed_colour()))
|
data = discord.Embed(description=created_at, colour=(await ctx.embed_colour()))
|
||||||
data.add_field(name=_("Region"), value=str(guild.region))
|
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=_("Text Channels"), value=str(text_channels))
|
||||||
data.add_field(name=_("Voice Channels"), value=str(voice_channels))
|
data.add_field(name=_("Voice Channels"), value=str(voice_channels))
|
||||||
data.add_field(name=_("Roles"), value=str(len(guild.roles)))
|
data.add_field(name=_("Roles"), value=str(len(guild.roles)))
|
||||||
@ -218,12 +242,15 @@ class General(commands.Cog):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
await ctx.send(embed=data)
|
await ctx.send(embed=data)
|
||||||
except discord.HTTPException:
|
except discord.Forbidden:
|
||||||
await ctx.send(_("I need the `Embed links` permission to send this."))
|
await ctx.send(_("I need the `Embed links` permission to send this."))
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def urban(self, ctx, *, word):
|
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:
|
try:
|
||||||
url = "https://api.urbandictionary.com/v0/define?term=" + str(word).lower()
|
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:
|
async with session.get(url, headers=headers) as response:
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
|
|
||||||
except:
|
except aiohttp.ClientError:
|
||||||
await ctx.send(
|
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:
|
if data.get("error") != 404:
|
||||||
|
|
||||||
@ -246,20 +274,20 @@ class General(commands.Cog):
|
|||||||
embeds = []
|
embeds = []
|
||||||
for ud in data["list"]:
|
for ud in data["list"]:
|
||||||
embed = discord.Embed()
|
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"]
|
embed.url = ud["permalink"]
|
||||||
|
|
||||||
description = "{} \n \n **Example : ** {}".format(
|
description = _("{definition}\n\n**Example:** {example}").format(**ud)
|
||||||
ud["definition"], ud.get("example", "N/A")
|
|
||||||
)
|
|
||||||
if len(description) > 2048:
|
if len(description) > 2048:
|
||||||
description = "{}...".format(description[:2045])
|
description = "{}...".format(description[:2045])
|
||||||
embed.description = description
|
embed.description = description
|
||||||
|
|
||||||
embed.set_footer(
|
embed.set_footer(
|
||||||
text=_("{} Down / {} Up , Powered by urban dictionary").format(
|
text=_(
|
||||||
ud["thumbs_down"], ud["thumbs_up"]
|
"{thumbs_down} Down / {thumbs_up} Up, Powered by Urban Dictionary."
|
||||||
)
|
).format(**ud)
|
||||||
)
|
)
|
||||||
embeds.append(embed)
|
embeds.append(embed)
|
||||||
|
|
||||||
@ -275,24 +303,15 @@ class General(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
messages = []
|
messages = []
|
||||||
for ud in data["list"]:
|
for ud in data["list"]:
|
||||||
description = _("{} \n \n **Example : ** {}").format(
|
ud.set_default("example", "N/A")
|
||||||
ud["definition"], ud.get("example", "N/A")
|
description = _("{definition}\n\n**Example:** {example}").format(**ud)
|
||||||
)
|
|
||||||
if len(description) > 2048:
|
if len(description) > 2048:
|
||||||
description = "{}...".format(description[:2045])
|
description = "{}...".format(description[:2045])
|
||||||
description = description
|
|
||||||
|
|
||||||
message = _(
|
message = _(
|
||||||
"<{}> \n {} by {} \n \n {} \n \n {} Down / {} Up, Powered by urban "
|
"<{permalink}>\n {word} by {author}\n\n{description}\n\n"
|
||||||
"dictionary"
|
"{thumbs_down} Down / {thumbs_up} Up, Powered by urban dictionary"
|
||||||
).format(
|
).format(word=ud.pop("word").capitalize(), description=description, **ud)
|
||||||
ud["permalink"],
|
|
||||||
ud["word"].capitalize(),
|
|
||||||
ud["author"],
|
|
||||||
description,
|
|
||||||
ud["thumbs_down"],
|
|
||||||
ud["thumbs_up"],
|
|
||||||
)
|
|
||||||
messages.append(message)
|
messages.append(message)
|
||||||
|
|
||||||
if messages is not None and len(messages) > 0:
|
if messages is not None and len(messages) > 0:
|
||||||
@ -306,6 +325,6 @@ class General(commands.Cog):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
|
|||||||
@ -29,23 +29,26 @@ class Image(commands.Cog):
|
|||||||
|
|
||||||
@commands.group(name="imgur")
|
@commands.group(name="imgur")
|
||||||
async def _imgur(self, ctx):
|
async def _imgur(self, ctx):
|
||||||
"""Retrieves pictures from imgur
|
"""Retrieve pictures from Imgur.
|
||||||
|
|
||||||
Make sure to set the client ID using
|
Make sure to set the Client ID using `[p]imgurcreds`.
|
||||||
[p]imgurcreds"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@_imgur.command(name="search")
|
@_imgur.command(name="search")
|
||||||
async def imgur_search(self, ctx, *, term: str):
|
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"
|
url = self.imgur_base_url + "gallery/search/time/all/0"
|
||||||
params = {"q": term}
|
params = {"q": term}
|
||||||
imgur_client_id = await self.settings.imgur_client_id()
|
imgur_client_id = await self.settings.imgur_client_id()
|
||||||
if not imgur_client_id:
|
if not imgur_client_id:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
headers = {"Authorization": "Client-ID {}".format(imgur_client_id)}
|
headers = {"Authorization": "Client-ID {}".format(imgur_client_id)}
|
||||||
@ -64,37 +67,41 @@ class Image(commands.Cog):
|
|||||||
msg += "\n"
|
msg += "\n"
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
else:
|
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")
|
@_imgur.command(name="subreddit")
|
||||||
async def imgur_subreddit(
|
async def imgur_subreddit(
|
||||||
self, ctx, subreddit: str, sort_type: str = "top", window: str = "day"
|
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
|
You can customize the search with the following options:
|
||||||
Time windows: day, week, month, year, all"""
|
- `<sort_type>`: new, top
|
||||||
|
- `<window>`: day, week, month, year, all
|
||||||
|
"""
|
||||||
sort_type = sort_type.lower()
|
sort_type = sort_type.lower()
|
||||||
window = window.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":
|
if sort_type == "new":
|
||||||
sort = "time"
|
sort = "time"
|
||||||
elif sort_type == "top":
|
elif sort_type == "top":
|
||||||
sort = "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()
|
imgur_client_id = await self.settings.imgur_client_id()
|
||||||
if not imgur_client_id:
|
if not imgur_client_id:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
|
|
||||||
@ -117,29 +124,33 @@ class Image(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("No results found."))
|
await ctx.send(_("No results found."))
|
||||||
else:
|
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()
|
@checks.is_owner()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def imgurcreds(self, ctx, imgur_client_id: str):
|
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
|
To get an Imgur Client ID:
|
||||||
|
1. Login to an Imgur account.
|
||||||
You can get these by visiting https://api.imgur.com/oauth2/addclient
|
2. Visit [this](https://api.imgur.com/oauth2/addclient) page
|
||||||
and filling out the form. Enter a name for the application, select
|
3. Enter a name for your application
|
||||||
'Anonymous usage without user authorization' for the auth type,
|
4. Select *Anonymous usage without user authorization* for the auth type
|
||||||
set the authorization callback url to 'https://localhost'
|
5. Set the authorization callback URL to `https://localhost`
|
||||||
leave the app website blank, enter a valid email address, and
|
6. Leave the app website blank
|
||||||
enter a description. Check the box for the captcha, then click Next.
|
7. Enter a valid email address and a description
|
||||||
Your client ID will be on the page that loads."""
|
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 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.guild_only()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def gif(self, ctx, *keywords):
|
async def gif(self, ctx, *keywords):
|
||||||
"""Retrieves first search result from giphy"""
|
"""Retrieve the first search result from Giphy."""
|
||||||
if keywords:
|
if keywords:
|
||||||
keywords = "+".join(keywords)
|
keywords = "+".join(keywords)
|
||||||
else:
|
else:
|
||||||
@ -158,12 +169,12 @@ class Image(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await ctx.send(_("No results found."))
|
await ctx.send(_("No results found."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Error contacting the API."))
|
await ctx.send(_("Error contacting the Giphy API."))
|
||||||
|
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def gifr(self, ctx, *keywords):
|
async def gifr(self, ctx, *keywords):
|
||||||
"""Retrieves a random gif from a giphy search"""
|
"""Retrieve a random GIF from a Giphy search."""
|
||||||
if keywords:
|
if keywords:
|
||||||
keywords = "+".join(keywords)
|
keywords = "+".join(keywords)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
import discord
|
|
||||||
|
|
||||||
|
|
||||||
def mod_or_voice_permissions(**perms):
|
def mod_or_voice_permissions(**perms):
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
import contextlib
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from collections import deque, defaultdict, namedtuple
|
from collections import deque, defaultdict, namedtuple
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@ -14,7 +16,7 @@ from .log import log
|
|||||||
|
|
||||||
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
|
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
|
||||||
|
|
||||||
_ = Translator("Mod", __file__)
|
_ = T_ = Translator("Mod", __file__)
|
||||||
|
|
||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
@ -58,7 +60,8 @@ class Mod(commands.Cog):
|
|||||||
self.registration_task.cancel()
|
self.registration_task.cancel()
|
||||||
self.tban_expiry_task.cancel()
|
self.tban_expiry_task.cancel()
|
||||||
|
|
||||||
async def _casetype_registration(self):
|
@staticmethod
|
||||||
|
async def _casetype_registration():
|
||||||
casetypes_to_register = [
|
casetypes_to_register = [
|
||||||
{
|
{
|
||||||
"name": "ban",
|
"name": "ban",
|
||||||
@ -168,7 +171,7 @@ class Mod(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def modset(self, ctx: commands.Context):
|
async def modset(self, ctx: commands.Context):
|
||||||
"""Manages server administration settings."""
|
"""Manage server administration settings."""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
# Display current settings
|
# Display current settings
|
||||||
@ -178,23 +181,37 @@ class Mod(commands.Cog):
|
|||||||
delete_delay = await self.settings.guild(guild).delete_delay()
|
delete_delay = await self.settings.guild(guild).delete_delay()
|
||||||
reinvite_on_unban = await self.settings.guild(guild).reinvite_on_unban()
|
reinvite_on_unban = await self.settings.guild(guild).reinvite_on_unban()
|
||||||
msg = ""
|
msg = ""
|
||||||
msg += "Delete repeats: {}\n".format("Yes" if delete_repeats else "No")
|
msg += _("Delete repeats: {yes_or_no}\n").format(
|
||||||
msg += "Ban mention spam: {}\n".format(
|
yes_or_no=_("Yes") if delete_repeats else _("No")
|
||||||
"{} mentions".format(ban_mention_spam)
|
|
||||||
if isinstance(ban_mention_spam, int)
|
|
||||||
else "No"
|
|
||||||
)
|
)
|
||||||
msg += "Respects hierarchy: {}\n".format("Yes" if respect_hierarchy else "No")
|
msg += _("Ban mention spam: {num_mentions}\n").format(
|
||||||
msg += "Delete delay: {}\n".format(
|
num_mentions=_("{num} mentions").format(num=ban_mention_spam)
|
||||||
"{} seconds".format(delete_delay) if delete_delay != -1 else "None"
|
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))
|
await ctx.send(box(msg))
|
||||||
|
|
||||||
@modset.command()
|
@modset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def hierarchy(self, ctx: commands.Context):
|
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
|
guild = ctx.guild
|
||||||
toggled = await self.settings.guild(guild).respect_hierarchy()
|
toggled = await self.settings.guild(guild).respect_hierarchy()
|
||||||
if not toggled:
|
if not toggled:
|
||||||
@ -210,10 +227,14 @@ class Mod(commands.Cog):
|
|||||||
|
|
||||||
@modset.command()
|
@modset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def banmentionspam(self, ctx: commands.Context, max_mentions: int = False):
|
async def banmentionspam(self, ctx: commands.Context, max_mentions: int = 0):
|
||||||
"""Enables auto ban for messages mentioning X different people
|
"""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
|
guild = ctx.guild
|
||||||
if max_mentions:
|
if max_mentions:
|
||||||
if max_mentions < 5:
|
if max_mentions < 5:
|
||||||
@ -222,13 +243,13 @@ class Mod(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"Autoban for mention spam enabled. "
|
"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."
|
"in a single message will be autobanned."
|
||||||
).format(max_mentions)
|
).format(max_mentions=max_mentions)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
cur_setting = await self.settings.guild(guild).ban_mention_spam()
|
cur_setting = await self.settings.guild(guild).ban_mention_spam()
|
||||||
if cur_setting is False:
|
if not cur_setting:
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
return
|
return
|
||||||
await self.settings.guild(guild).ban_mention_spam.set(False)
|
await self.settings.guild(guild).ban_mention_spam.set(False)
|
||||||
@ -237,7 +258,7 @@ class Mod(commands.Cog):
|
|||||||
@modset.command()
|
@modset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def deleterepeats(self, ctx: commands.Context):
|
async def deleterepeats(self, ctx: commands.Context):
|
||||||
"""Enables auto deletion of repeated messages"""
|
"""Enable auto-deletion of repeated messages."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
cur_setting = await self.settings.guild(guild).delete_repeats()
|
cur_setting = await self.settings.guild(guild).delete_repeats()
|
||||||
if not cur_setting:
|
if not cur_setting:
|
||||||
@ -250,11 +271,12 @@ class Mod(commands.Cog):
|
|||||||
@modset.command()
|
@modset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def deletedelay(self, ctx: commands.Context, time: int = None):
|
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.
|
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
|
guild = ctx.guild
|
||||||
if time is not None:
|
if time is not None:
|
||||||
time = min(max(time, -1), 60) # Enforces the time limits
|
time = min(max(time, -1), 60) # Enforces the time limits
|
||||||
@ -262,16 +284,16 @@ class Mod(commands.Cog):
|
|||||||
if time == -1:
|
if time == -1:
|
||||||
await ctx.send(_("Command deleting disabled."))
|
await ctx.send(_("Command deleting disabled."))
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("Delete delay set to {} seconds.").format(time))
|
await ctx.send(_("Delete delay set to {num} seconds.").format(num=time))
|
||||||
else:
|
else:
|
||||||
delay = await self.settings.guild(guild).delete_delay()
|
delay = await self.settings.guild(guild).delete_delay()
|
||||||
if delay != -1:
|
if delay != -1:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"Bot will delete command messages after"
|
"Bot will delete command messages after"
|
||||||
" {} seconds. Set this value to -1 to"
|
" {num} seconds. Set this value to -1 to"
|
||||||
" stop deleting messages"
|
" stop deleting messages"
|
||||||
).format(delay)
|
).format(num=delay)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("I will not delete command messages."))
|
await ctx.send(_("I will not delete command messages."))
|
||||||
@ -279,33 +301,44 @@ class Mod(commands.Cog):
|
|||||||
@modset.command()
|
@modset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def reinvite(self, ctx: commands.Context):
|
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
|
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
|
guild = ctx.guild
|
||||||
cur_setting = await self.settings.guild(guild).reinvite_on_unban()
|
cur_setting = await self.settings.guild(guild).reinvite_on_unban()
|
||||||
if not cur_setting:
|
if not cur_setting:
|
||||||
await self.settings.guild(guild).reinvite_on_unban.set(True)
|
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:
|
else:
|
||||||
await self.settings.guild(guild).reinvite_on_unban.set(False)
|
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.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(kick_members=True)
|
||||||
@checks.admin_or_permissions(kick_members=True)
|
@checks.admin_or_permissions(kick_members=True)
|
||||||
async def kick(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
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
|
If a reason is specified, it will be the reason that shows up
|
||||||
in the audit log"""
|
in the audit log.
|
||||||
|
"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
if author == user:
|
if author == user:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
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.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def ban(
|
async def ban(
|
||||||
self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None
|
self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None
|
||||||
):
|
):
|
||||||
"""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.
|
Deletes `<days>` worth of messages.
|
||||||
Minimum 0 days, maximum 7. Defaults to 0."""
|
|
||||||
|
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
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
@ -429,16 +466,16 @@ class Mod(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def hackban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
|
async def hackban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
|
||||||
"""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
|
A user ID needs to be provided in order to ban
|
||||||
using this command"""
|
using this command.
|
||||||
|
"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
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
|
is_banned = False
|
||||||
ban_list = await guild.bans()
|
ban_list = await guild.bans()
|
||||||
for entry in ban_list:
|
for entry in ban_list:
|
||||||
@ -489,75 +526,77 @@ class Mod(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def tempban(
|
async def tempban(
|
||||||
self, ctx: commands.Context, user: discord.Member, days: int = 1, *, reason: str = None
|
self, ctx: commands.Context, user: discord.Member, days: int = 1, *, reason: str = None
|
||||||
):
|
):
|
||||||
"""Tempbans the user for the specified number of days"""
|
"""Temporarily ban a user from this server."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
days_delta = timedelta(days=int(days))
|
days_delta = timedelta(days=int(days))
|
||||||
unban_time = datetime.utcnow() + days_delta
|
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))
|
invite = await self.get_invite_for_reinvite(ctx, int(days_delta.total_seconds() + 86400))
|
||||||
if invite is None:
|
if invite is None:
|
||||||
invite = ""
|
invite = ""
|
||||||
|
|
||||||
if can_ban:
|
queue_entry = (guild.id, user.id)
|
||||||
queue_entry = (guild.id, user.id)
|
await self.settings.member(user).banned_until.set(unban_time.timestamp())
|
||||||
await self.settings.member(user).banned_until.set(unban_time.timestamp())
|
cur_tbans = await self.settings.guild(guild).current_tempbans()
|
||||||
cur_tbans = await self.settings.guild(guild).current_tempbans()
|
cur_tbans.append(user.id)
|
||||||
cur_tbans.append(user.id)
|
await self.settings.guild(guild).current_tempbans.set(cur_tbans)
|
||||||
await self.settings.guild(guild).current_tempbans.set(cur_tbans)
|
|
||||||
|
|
||||||
try: # We don't want blocked DMs preventing us from banning
|
with contextlib.suppress(discord.HTTPException):
|
||||||
msg = await user.send(
|
# We don't want blocked DMs preventing us from banning
|
||||||
_(
|
await user.send(
|
||||||
"You have been temporarily banned from {} until {}. "
|
_(
|
||||||
"Here is an invite for when your ban expires: {}"
|
"You have been temporarily banned from {server_name} until {date}. "
|
||||||
).format(guild.name, unban_time.strftime("%m-%d-%Y %H:%M:%S"), invite)
|
"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)
|
||||||
self.ban_queue.append(queue_entry)
|
try:
|
||||||
try:
|
await guild.ban(user)
|
||||||
await guild.ban(user)
|
except discord.Forbidden:
|
||||||
except discord.Forbidden:
|
await ctx.send(_("I can't do that for some reason."))
|
||||||
await ctx.send(_("I can't do that for some reason."))
|
except discord.HTTPException:
|
||||||
except discord.HTTPException:
|
await ctx.send(_("Something went wrong while banning"))
|
||||||
await ctx.send(_("Something went wrong while banning"))
|
else:
|
||||||
else:
|
await ctx.send(_("Done. Enough chaos for now"))
|
||||||
await ctx.send(_("Done. Enough chaos for now"))
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await modlog.create_case(
|
await modlog.create_case(
|
||||||
self.bot,
|
self.bot,
|
||||||
guild,
|
guild,
|
||||||
ctx.message.created_at,
|
ctx.message.created_at,
|
||||||
"tempban",
|
"tempban",
|
||||||
user,
|
user,
|
||||||
author,
|
author,
|
||||||
reason,
|
reason,
|
||||||
unban_time,
|
unban_time,
|
||||||
)
|
)
|
||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
await ctx.send(e)
|
await ctx.send(e)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def softban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
async def softban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||||
"""Kicks the user, deleting 1 day worth of messages."""
|
"""Kick a user and delete 1 day's worth of their messages."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
channel = ctx.channel
|
|
||||||
can_ban = channel.permissions_for(guild.me).ban_members
|
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
|
|
||||||
if author == user:
|
if author == user:
|
||||||
await ctx.send(
|
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
|
return
|
||||||
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
|
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:
|
if invite is None:
|
||||||
invite = ""
|
invite = ""
|
||||||
|
|
||||||
if can_ban:
|
queue_entry = (guild.id, user.id)
|
||||||
queue_entry = (guild.id, user.id)
|
try: # We don't want blocked DMs preventing us from banning
|
||||||
try: # We don't want blocked DMs preventing us from banning
|
msg = await user.send(
|
||||||
msg = await user.send(
|
_(
|
||||||
_(
|
"You have been banned and "
|
||||||
"You have been banned and "
|
"then unbanned as a quick way to delete your messages.\n"
|
||||||
"then unbanned as a quick way to delete your messages.\n"
|
"You can now join the server again. {invite_link}"
|
||||||
"You can now join the server again. {}"
|
).format(invite_link=invite)
|
||||||
).format(invite)
|
)
|
||||||
)
|
except discord.HTTPException:
|
||||||
except discord.HTTPException:
|
msg = None
|
||||||
msg = None
|
self.ban_queue.append(queue_entry)
|
||||||
self.ban_queue.append(queue_entry)
|
try:
|
||||||
try:
|
await guild.ban(user, reason=audit_reason, delete_message_days=1)
|
||||||
await guild.ban(user, reason=audit_reason, delete_message_days=1)
|
except discord.errors.Forbidden:
|
||||||
except discord.errors.Forbidden:
|
self.ban_queue.remove(queue_entry)
|
||||||
self.ban_queue.remove(queue_entry)
|
await ctx.send(_("My role is not high enough to softban that user."))
|
||||||
await ctx.send(_("My role is not high enough to softban that user."))
|
if msg is not None:
|
||||||
if msg is not None:
|
await msg.delete()
|
||||||
await msg.delete()
|
return
|
||||||
return
|
except discord.HTTPException as e:
|
||||||
except discord.HTTPException as e:
|
self.ban_queue.remove(queue_entry)
|
||||||
self.ban_queue.remove(queue_entry)
|
print(e)
|
||||||
print(e)
|
return
|
||||||
return
|
self.unban_queue.append(queue_entry)
|
||||||
self.unban_queue.append(queue_entry)
|
try:
|
||||||
try:
|
await guild.unban(user)
|
||||||
await guild.unban(user)
|
except discord.HTTPException as e:
|
||||||
except discord.HTTPException as e:
|
self.unban_queue.remove(queue_entry)
|
||||||
self.unban_queue.remove(queue_entry)
|
print(e)
|
||||||
print(e)
|
return
|
||||||
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)
|
|
||||||
else:
|
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.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(ban_members=True)
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def unban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
|
async def unban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
|
||||||
"""Unbans the target user.
|
"""Unban a user from this server.
|
||||||
|
|
||||||
Requires specifying the target user's ID. To find this, you may either:
|
Requires specifying the target user's ID. To find this, you may either:
|
||||||
1. Copy it from the mod log case (if one was created), or
|
1. Copy it from the mod log case (if one was created), or
|
||||||
2. enable developer mode, go to Bans in this server's settings, right-
|
2. enable developer mode, go to Bans in this server's settings, right-
|
||||||
click the user and select 'Copy ID'."""
|
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
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
user = await self.bot.get_user_info(user_id)
|
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)
|
invite = await self.get_invite_for_reinvite(ctx)
|
||||||
if invite:
|
if invite:
|
||||||
try:
|
try:
|
||||||
user.send(
|
await user.send(
|
||||||
_(
|
_(
|
||||||
"You've been unbanned from {}.\n"
|
"You've been unbanned from {server}.\n"
|
||||||
"Here is an invite for that server: {}"
|
"Here is an invite for that server: {invite_link}"
|
||||||
).format(guild.name, invite.url)
|
).format(server=guild.name, invite_link=invite.url)
|
||||||
)
|
)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"I failed to send an invite to that user. "
|
"I failed to send an invite to that user. "
|
||||||
"Perhaps you may be able to send it for me?\n"
|
"Perhaps you may be able to send it for me?\n"
|
||||||
"Here's the invite link: {}"
|
"Here's the invite link: {invite_link}"
|
||||||
).format(invite.url)
|
).format(invite_link=invite.url)
|
||||||
)
|
)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"Something went wrong when attempting to send that user"
|
"Something went wrong when attempting to send that user"
|
||||||
"an invite. Here's the link so you can try: {}"
|
"an invite. Here's the link so you can try: {invite_link}"
|
||||||
).format(invite.url)
|
).format(invite_link=invite.url)
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -750,7 +783,7 @@ class Mod(commands.Cog):
|
|||||||
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
||||||
@bot_has_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):
|
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
|
user_voice_state = user.voice
|
||||||
if user_voice_state is None:
|
if user_voice_state is None:
|
||||||
await ctx.send(_("No voice state for that user!"))
|
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)
|
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
|
||||||
@bot_has_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):
|
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
|
user_voice_state = user.voice
|
||||||
if user_voice_state is None:
|
if user_voice_state is None:
|
||||||
await ctx.send(_("No voice state for that user!"))
|
await ctx.send(_("No voice state for that user!"))
|
||||||
@ -828,27 +861,24 @@ class Mod(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(manage_nicknames=True)
|
||||||
@checks.admin_or_permissions(manage_nicknames=True)
|
@checks.admin_or_permissions(manage_nicknames=True)
|
||||||
async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname=""):
|
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()
|
nickname = nickname.strip()
|
||||||
if nickname == "":
|
if nickname == "":
|
||||||
nickname = None
|
nickname = None
|
||||||
try:
|
await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
|
||||||
await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
|
await ctx.send("Done.")
|
||||||
await ctx.send("Done.")
|
|
||||||
except discord.Forbidden:
|
|
||||||
await ctx.send(
|
|
||||||
_("I cannot do that, I lack the '{}' permission.").format("Manage Nicknames")
|
|
||||||
)
|
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.mod_or_permissions(manage_channel=True)
|
@checks.mod_or_permissions(manage_channel=True)
|
||||||
async def mute(self, ctx: commands.Context):
|
async def mute(self, ctx: commands.Context):
|
||||||
"""Mutes user in the channel/server"""
|
"""Mute users."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@mute.command(name="voice")
|
@mute.command(name="voice")
|
||||||
@ -856,7 +886,7 @@ class Mod(commands.Cog):
|
|||||||
@mod_or_voice_permissions(mute_members=True)
|
@mod_or_voice_permissions(mute_members=True)
|
||||||
@bot_has_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):
|
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
|
user_voice_state = user.voice
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
@ -868,9 +898,7 @@ class Mod(commands.Cog):
|
|||||||
audit_reason = get_audit_reason(ctx.author, reason)
|
audit_reason = get_audit_reason(ctx.author, reason)
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
await channel.set_permissions(user, overwrite=overwrites, reason=audit_reason)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Muted {}#{} in channel {}").format(
|
_("Muted {user} in channel {channel.name}").format(user, channel=channel)
|
||||||
user.name, user.discriminator, channel.name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await modlog.create_case(
|
await modlog.create_case(
|
||||||
@ -888,7 +916,9 @@ class Mod(commands.Cog):
|
|||||||
await ctx.send(e)
|
await ctx.send(e)
|
||||||
return
|
return
|
||||||
elif channel.permissions_for(user).speak is False:
|
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
|
return
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("That user is not in a voice channel right now!"))
|
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!"))
|
await ctx.send(_("No voice state for the target!"))
|
||||||
return
|
return
|
||||||
|
|
||||||
@checks.mod_or_permissions(administrator=True)
|
|
||||||
@mute.command(name="channel")
|
@mute.command(name="channel")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(manage_roles=True)
|
||||||
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def channel_mute(
|
async def channel_mute(
|
||||||
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
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
|
author = ctx.message.author
|
||||||
channel = ctx.message.channel
|
channel = ctx.message.channel
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
if reason is None:
|
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:
|
else:
|
||||||
audit_reason = "Channel mute requested by {} (ID {}). Reason: {}".format(
|
audit_reason = "Channel mute requested by {a} (ID {a.id}). Reason: {r}".format(
|
||||||
author, author.id, reason
|
a=author, r=reason
|
||||||
)
|
)
|
||||||
|
|
||||||
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
success, issue = await self.mute_user(guild, channel, author, user, audit_reason)
|
||||||
@ -935,20 +966,22 @@ class Mod(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await channel.send(issue)
|
await channel.send(issue)
|
||||||
|
|
||||||
@checks.mod_or_permissions(administrator=True)
|
|
||||||
@mute.command(name="server", aliases=["guild"])
|
@mute.command(name="server", aliases=["guild"])
|
||||||
@commands.guild_only()
|
@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):
|
async def guild_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
|
||||||
"""Mutes user in the server"""
|
"""Mutes user in the server"""
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
user_voice_state = user.voice
|
|
||||||
if reason is None:
|
if reason is None:
|
||||||
audit_reason = "server mute requested by {} (ID {})".format(author, author.id)
|
audit_reason = "server mute requested by {author} (ID {author.id})".format(
|
||||||
else:
|
author=author
|
||||||
audit_reason = "server mute requested by {} (ID {}). Reason: {}".format(
|
|
||||||
author, author.id, reason
|
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
audit_reason = (
|
||||||
|
"server mute requested by {author} (ID {author.id}). Reason: {reason}"
|
||||||
|
).format(author=author, reason=reason)
|
||||||
|
|
||||||
mute_success = []
|
mute_success = []
|
||||||
for channel in guild.channels:
|
for channel in guild.channels:
|
||||||
@ -992,10 +1025,10 @@ class Mod(commands.Cog):
|
|||||||
perms_cache = await self.settings.member(user).perms_cache()
|
perms_cache = await self.settings.member(user).perms_cache()
|
||||||
|
|
||||||
if overwrites.send_messages is False or permissions.send_messages is False:
|
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):
|
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)] = {
|
perms_cache[str(channel.id)] = {
|
||||||
"send_messages": overwrites.send_messages,
|
"send_messages": overwrites.send_messages,
|
||||||
@ -1005,28 +1038,27 @@ class Mod(commands.Cog):
|
|||||||
try:
|
try:
|
||||||
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
|
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
return False, mute_unmute_issues["permissions_issue"]
|
return False, T_(mute_unmute_issues["permissions_issue"])
|
||||||
else:
|
else:
|
||||||
await self.settings.member(user).perms_cache.set(perms_cache)
|
await self.settings.member(user).perms_cache.set(perms_cache)
|
||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(manage_roles=True)
|
||||||
@checks.mod_or_permissions(manage_channel=True)
|
@checks.mod_or_permissions(manage_channel=True)
|
||||||
async def unmute(self, ctx: commands.Context):
|
async def unmute(self, ctx: commands.Context):
|
||||||
"""Unmutes user in the channel/server
|
"""Unmute users."""
|
||||||
|
|
||||||
Defaults to channel"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@unmute.command(name="voice")
|
@unmute.command(name="voice")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@mod_or_voice_permissions(mute_members=True)
|
@mod_or_voice_permissions(mute_members=True)
|
||||||
@bot_has_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
|
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
|
user_voice_state = user.voice
|
||||||
if user_voice_state:
|
if user_voice_state:
|
||||||
channel = user_voice_state.channel
|
channel = user_voice_state.channel
|
||||||
@ -1067,11 +1099,12 @@ class Mod(commands.Cog):
|
|||||||
|
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
@unmute.command(name="channel")
|
@unmute.command(name="channel")
|
||||||
|
@commands.bot_has_permissions(manage_roles=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def channel_unmute(
|
async def unmute_channel(
|
||||||
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
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
|
channel = ctx.channel
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@ -1099,14 +1132,14 @@ class Mod(commands.Cog):
|
|||||||
|
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
@unmute.command(name="server", aliases=["guild"])
|
@unmute.command(name="server", aliases=["guild"])
|
||||||
|
@commands.bot_has_permissions(manage_roles=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def guild_unmute(
|
async def unmute_guild(
|
||||||
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
|
||||||
):
|
):
|
||||||
"""Unmutes user in the server"""
|
"""Unmute a user in this server."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
channel = ctx.channel
|
|
||||||
|
|
||||||
unmute_success = []
|
unmute_success = []
|
||||||
for channel in guild.channels:
|
for channel in guild.channels:
|
||||||
@ -1146,10 +1179,10 @@ class Mod(commands.Cog):
|
|||||||
perms_cache = await self.settings.member(user).perms_cache()
|
perms_cache = await self.settings.member(user).perms_cache()
|
||||||
|
|
||||||
if overwrites.send_messages or permissions.send_messages:
|
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):
|
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:
|
if channel.id in perms_cache:
|
||||||
old_values = perms_cache[channel.id]
|
old_values = perms_cache[channel.id]
|
||||||
@ -1164,9 +1197,11 @@ class Mod(commands.Cog):
|
|||||||
if not is_empty:
|
if not is_empty:
|
||||||
await channel.set_permissions(user, overwrite=overwrites)
|
await channel.set_permissions(user, overwrite=overwrites)
|
||||||
else:
|
else:
|
||||||
await channel.set_permissions(user, overwrite=None)
|
await channel.set_permissions(
|
||||||
|
user, overwrite=cast(discord.PermissionOverwrite, None)
|
||||||
|
)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
return False, mute_unmute_issues["permissions_issue"]
|
return False, T_(mute_unmute_issues["permissions_issue"])
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
del perms_cache[channel.id]
|
del perms_cache[channel.id]
|
||||||
@ -1180,15 +1215,16 @@ class Mod(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(manage_channels=True)
|
@checks.admin_or_permissions(manage_channels=True)
|
||||||
async def ignore(self, ctx: commands.Context):
|
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:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send(await self.count_ignored())
|
await ctx.send(await self.count_ignored())
|
||||||
|
|
||||||
@ignore.command(name="channel")
|
@ignore.command(name="channel")
|
||||||
async def ignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None):
|
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:
|
if not channel:
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
if not await self.settings.channel(channel).ignored():
|
if not await self.settings.channel(channel).ignored():
|
||||||
@ -1200,7 +1236,7 @@ class Mod(commands.Cog):
|
|||||||
@ignore.command(name="server", aliases=["guild"])
|
@ignore.command(name="server", aliases=["guild"])
|
||||||
@checks.admin_or_permissions(manage_guild=True)
|
@checks.admin_or_permissions(manage_guild=True)
|
||||||
async def ignore_guild(self, ctx: commands.Context):
|
async def ignore_guild(self, ctx: commands.Context):
|
||||||
"""Ignores current server"""
|
"""Ignore commands in this server."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if not await self.settings.guild(guild).ignored():
|
if not await self.settings.guild(guild).ignored():
|
||||||
await self.settings.guild(guild).ignored.set(True)
|
await self.settings.guild(guild).ignored.set(True)
|
||||||
@ -1212,15 +1248,16 @@ class Mod(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(manage_channels=True)
|
@checks.admin_or_permissions(manage_channels=True)
|
||||||
async def unignore(self, ctx: commands.Context):
|
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:
|
if ctx.invoked_subcommand is None:
|
||||||
await ctx.send(await self.count_ignored())
|
await ctx.send(await self.count_ignored())
|
||||||
|
|
||||||
@unignore.command(name="channel")
|
@unignore.command(name="channel")
|
||||||
async def unignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None):
|
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:
|
if not channel:
|
||||||
channel = ctx.channel
|
channel = ctx.channel
|
||||||
|
|
||||||
@ -1233,7 +1270,7 @@ class Mod(commands.Cog):
|
|||||||
@unignore.command(name="server", aliases=["guild"])
|
@unignore.command(name="server", aliases=["guild"])
|
||||||
@checks.admin_or_permissions(manage_guild=True)
|
@checks.admin_or_permissions(manage_guild=True)
|
||||||
async def unignore_guild(self, ctx: commands.Context):
|
async def unignore_guild(self, ctx: commands.Context):
|
||||||
"""Removes current guild from ignore list"""
|
"""Remove this server from the ignore list."""
|
||||||
guild = ctx.message.guild
|
guild = ctx.message.guild
|
||||||
if await self.settings.guild(guild).ignored():
|
if await self.settings.guild(guild).ignored():
|
||||||
await self.settings.guild(guild).ignored.set(False)
|
await self.settings.guild(guild).ignored.set(False)
|
||||||
@ -1258,7 +1295,8 @@ class Mod(commands.Cog):
|
|||||||
"""Global check to see if a channel or server is ignored.
|
"""Global check to see if a channel or server is ignored.
|
||||||
|
|
||||||
Any users who have permission to use the `ignore` or `unignore` commands
|
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)
|
perms = ctx.channel.permissions_for(ctx.author)
|
||||||
surpass_ignore = (
|
surpass_ignore = (
|
||||||
isinstance(ctx.channel, discord.abc.PrivateChannel)
|
isinstance(ctx.channel, discord.abc.PrivateChannel)
|
||||||
@ -1274,14 +1312,15 @@ class Mod(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
|
@commands.bot_has_permissions(embed_links=True)
|
||||||
async def userinfo(self, ctx, *, user: discord.Member = None):
|
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
|
This includes fields for status, discord join date, server
|
||||||
join date, voice state and previous names/nicknames.
|
join date, voice state and previous names/nicknames.
|
||||||
|
|
||||||
If the user has none of roles, previous names or previous
|
If the user has no roles, previous names or previous nicknames,
|
||||||
nicknames, these fields will be omitted.
|
these fields will be omitted.
|
||||||
"""
|
"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@ -1357,14 +1396,11 @@ class Mod(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
data.set_author(name=name)
|
data.set_author(name=name)
|
||||||
|
|
||||||
try:
|
await ctx.send(embed=data)
|
||||||
await ctx.send(embed=data)
|
|
||||||
except discord.HTTPException:
|
|
||||||
await ctx.send(_("I need the `Embed links` permission to send this."))
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def names(self, ctx: commands.Context, user: discord.Member):
|
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)
|
names, nicks = await self.get_names_and_nicks(user)
|
||||||
msg = ""
|
msg = ""
|
||||||
if names:
|
if names:
|
||||||
@ -1407,7 +1443,7 @@ class Mod(commands.Cog):
|
|||||||
queue_entry = (guild.id, user.id)
|
queue_entry = (guild.id, user.id)
|
||||||
self.unban_queue.append(queue_entry)
|
self.unban_queue.append(queue_entry)
|
||||||
try:
|
try:
|
||||||
await guild.unban(user, reason="Tempban finished")
|
await guild.unban(user, reason=_("Tempban finished"))
|
||||||
guild_tempbans.remove(uid)
|
guild_tempbans.remove(uid)
|
||||||
except discord.Forbidden:
|
except discord.Forbidden:
|
||||||
self.unban_queue.remove(queue_entry)
|
self.unban_queue.remove(queue_entry)
|
||||||
@ -1437,12 +1473,12 @@ class Mod(commands.Cog):
|
|||||||
guild = message.guild
|
guild = message.guild
|
||||||
author = message.author
|
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)
|
mentions = set(message.mentions)
|
||||||
if len(mentions) >= max_mentions:
|
if len(mentions) >= max_mentions:
|
||||||
try:
|
try:
|
||||||
await guild.ban(author, reason="Mention spam (Autoban)")
|
await guild.ban(author, reason=_("Mention spam (Autoban)"))
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
log.info(
|
log.info(
|
||||||
"Failed to ban member for mention spam in server {}.".format(guild.id)
|
"Failed to ban member for mention spam in server {}.".format(guild.id)
|
||||||
@ -1456,7 +1492,7 @@ class Mod(commands.Cog):
|
|||||||
"ban",
|
"ban",
|
||||||
author,
|
author,
|
||||||
guild.me,
|
guild.me,
|
||||||
"Mention spam (Autoban)",
|
_("Mention spam (Autoban)"),
|
||||||
until=None,
|
until=None,
|
||||||
channel=None,
|
channel=None,
|
||||||
)
|
)
|
||||||
@ -1469,6 +1505,7 @@ class Mod(commands.Cog):
|
|||||||
async def on_command_completion(self, ctx: commands.Context):
|
async def on_command_completion(self, ctx: commands.Context):
|
||||||
await self._delete_delay(ctx)
|
await self._delete_delay(ctx)
|
||||||
|
|
||||||
|
# noinspection PyUnusedLocal
|
||||||
async def on_command_error(self, ctx: commands.Context, error):
|
async def on_command_error(self, ctx: commands.Context, error):
|
||||||
await self._delete_delay(ctx)
|
await self._delete_delay(ctx)
|
||||||
|
|
||||||
@ -1485,11 +1522,9 @@ class Mod(commands.Cog):
|
|||||||
return
|
return
|
||||||
|
|
||||||
async def _delete_helper(m):
|
async def _delete_helper(m):
|
||||||
try:
|
with contextlib.suppress(discord.HTTPException):
|
||||||
await m.delete()
|
await m.delete()
|
||||||
log.debug("Deleted command msg {}".format(m.id))
|
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 asyncio.sleep(delay)
|
||||||
await _delete_helper(message)
|
await _delete_helper(message)
|
||||||
@ -1511,7 +1546,7 @@ class Mod(commands.Cog):
|
|||||||
return
|
return
|
||||||
deleted = await self.check_duplicates(message)
|
deleted = await self.check_duplicates(message)
|
||||||
if not deleted:
|
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):
|
async def on_member_ban(self, guild: discord.Guild, member: discord.Member):
|
||||||
if (guild.id, member.id) in self.ban_queue:
|
if (guild.id, member.id) in self.ban_queue:
|
||||||
@ -1551,7 +1586,8 @@ class Mod(commands.Cog):
|
|||||||
except RuntimeError as e:
|
except RuntimeError as e:
|
||||||
print(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
|
An event for modlog case creation
|
||||||
"""
|
"""
|
||||||
@ -1566,7 +1602,8 @@ class Mod(commands.Cog):
|
|||||||
msg = await mod_channel.send(case_content)
|
msg = await mod_channel.send(case_content)
|
||||||
await case.edit({"message": msg})
|
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
|
Event for modlog case edits
|
||||||
"""
|
"""
|
||||||
@ -1579,7 +1616,10 @@ class Mod(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await case.message.edit(content=case_content)
|
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.
|
"""Get info about an audit log entry.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
@ -1599,14 +1639,15 @@ class Mod(commands.Cog):
|
|||||||
if the audit log entry could not be found.
|
if the audit log entry could not be found.
|
||||||
"""
|
"""
|
||||||
try:
|
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:
|
except discord.HTTPException:
|
||||||
entry = None
|
entry = None
|
||||||
if entry is None:
|
if entry is None:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
return entry.user, entry.reason, entry.created_at
|
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.
|
"""Get an audit log entry.
|
||||||
|
|
||||||
Any exceptions encountered when looking through the audit log will be
|
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())]
|
return [p for p in iter(overwrites)] == [p for p in iter(discord.PermissionOverwrite())]
|
||||||
|
|
||||||
|
|
||||||
|
_ = lambda s: s
|
||||||
mute_unmute_issues = {
|
mute_unmute_issues = {
|
||||||
"already_muted": "That user can't send messages in this channel.",
|
"already_muted": _("That user can't send messages in this channel."),
|
||||||
"already_unmuted": "That user isn't muted 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 "
|
"hierarchy_problem": _(
|
||||||
"the user in the role hierarchy.",
|
"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 "
|
"permissions_issue": _(
|
||||||
"lower than myself in the role hierarchy.",
|
"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
|
import discord
|
||||||
|
|
||||||
from redbot.core import checks, modlog, commands
|
from redbot.core import checks, modlog, commands
|
||||||
@ -10,7 +12,7 @@ _ = Translator("ModLog", __file__)
|
|||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class ModLog(commands.Cog):
|
class ModLog(commands.Cog):
|
||||||
"""Log for mod actions"""
|
"""Manage log channels for moderation actions."""
|
||||||
|
|
||||||
def __init__(self, bot: Red):
|
def __init__(self, bot: Red):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@ -19,23 +21,28 @@ class ModLog(commands.Cog):
|
|||||||
@commands.group()
|
@commands.group()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def modlogset(self, ctx: commands.Context):
|
async def modlogset(self, ctx: commands.Context):
|
||||||
"""Settings for the mod log"""
|
"""Manage modlog settings."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@modlogset.command()
|
@modlogset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def modlog(self, ctx: commands.Context, channel: discord.TextChannel = None):
|
async def modlog(self, ctx: commands.Context, channel: discord.TextChannel = None):
|
||||||
"""Sets a channel as mod log
|
"""Set a channel as the modlog.
|
||||||
|
|
||||||
Leaving the channel parameter empty will deactivate it"""
|
Omit `<channel>` to disable the modlog.
|
||||||
|
"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
if channel:
|
if channel:
|
||||||
if channel.permissions_for(guild.me).send_messages:
|
if channel.permissions_for(guild.me).send_messages:
|
||||||
await modlog.set_modlog_channel(guild, channel)
|
await modlog.set_modlog_channel(guild, channel)
|
||||||
await ctx.send(_("Mod events will be sent to {}").format(channel.mention))
|
await ctx.send(
|
||||||
|
_("Mod events will be sent to {channel}").format(channel=channel.mention)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("I do not have permissions to send messages in {}!").format(channel.mention)
|
_("I do not have permissions to send messages in {channel}!").format(
|
||||||
|
channel=channel.mention
|
||||||
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@ -49,39 +56,36 @@ class ModLog(commands.Cog):
|
|||||||
@modlogset.command(name="cases")
|
@modlogset.command(name="cases")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def set_cases(self, ctx: commands.Context, action: str = None):
|
async def set_cases(self, ctx: commands.Context, action: str = None):
|
||||||
"""Enables or disables case creation for each type of mod action"""
|
"""Enable or disable case creation for a mod action."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
if action is None: # No args given
|
if action is None: # No args given
|
||||||
casetypes = await modlog.get_all_casetypes(guild)
|
casetypes = await modlog.get_all_casetypes(guild)
|
||||||
await ctx.send_help()
|
await ctx.send_help()
|
||||||
title = _("Current settings:")
|
lines = []
|
||||||
msg = ""
|
|
||||||
for ct in casetypes:
|
for ct in casetypes:
|
||||||
enabled = await ct.is_enabled()
|
enabled = "enabled" if await ct.is_enabled() else "disabled"
|
||||||
value = "enabled" if enabled else "disabled"
|
lines.append(f"{ct.name} : {enabled}")
|
||||||
msg += "%s : %s\n" % (ct.name, value)
|
|
||||||
|
|
||||||
msg = title + "\n" + box(msg)
|
await ctx.send(_("Current settings:\n") + box("\n".join(lines)))
|
||||||
await ctx.send(msg)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
casetype = await modlog.get_casetype(action, guild)
|
casetype = await modlog.get_casetype(action, guild)
|
||||||
if not casetype:
|
if not casetype:
|
||||||
await ctx.send(_("That action is not registered"))
|
await ctx.send(_("That action is not registered"))
|
||||||
else:
|
else:
|
||||||
|
|
||||||
enabled = await casetype.is_enabled()
|
enabled = await casetype.is_enabled()
|
||||||
await casetype.set_enabled(True if not enabled else False)
|
await casetype.set_enabled(not enabled)
|
||||||
|
await ctx.send(
|
||||||
msg = _("Case creation for {} actions is now {}.").format(
|
_("Case creation for {action_name} actions is now {enabled}.").format(
|
||||||
action, "enabled" if not enabled else "disabled"
|
action_name=action, enabled="enabled" if not enabled else "disabled"
|
||||||
|
)
|
||||||
)
|
)
|
||||||
await ctx.send(msg)
|
|
||||||
|
|
||||||
@modlogset.command()
|
@modlogset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def resetcases(self, ctx: commands.Context):
|
async def resetcases(self, ctx: commands.Context):
|
||||||
"""Resets modlog's cases"""
|
"""Reset all modlog cases in this server."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
await modlog.reset_cases(guild)
|
await modlog.reset_cases(guild)
|
||||||
await ctx.send(_("Cases have been reset."))
|
await ctx.send(_("Cases have been reset."))
|
||||||
@ -89,7 +93,7 @@ class ModLog(commands.Cog):
|
|||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def case(self, ctx: commands.Context, number: int):
|
async def case(self, ctx: commands.Context, number: int):
|
||||||
"""Shows the specified case"""
|
"""Show the specified case."""
|
||||||
try:
|
try:
|
||||||
case = await modlog.get_case(number, ctx.guild, self.bot)
|
case = await modlog.get_case(number, ctx.guild, self.bot)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
@ -101,24 +105,21 @@ class ModLog(commands.Cog):
|
|||||||
else:
|
else:
|
||||||
await ctx.send(await case.message_content(embed=False))
|
await ctx.send(await case.message_content(embed=False))
|
||||||
|
|
||||||
@commands.command(usage="[case] <reason>")
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def reason(self, ctx: commands.Context, *, reason: str):
|
async def reason(self, ctx: commands.Context, case: Optional[int], *, reason: str):
|
||||||
"""Lets you specify a reason for mod-log's cases
|
"""Specify a reason for a modlog case.
|
||||||
|
|
||||||
Please note that you can only edit cases you are
|
Please note that you can only edit cases you are
|
||||||
the owner of unless you are a mod/admin or the server owner.
|
the owner of unless you are a mod, admin or server owner.
|
||||||
|
|
||||||
If no number is specified, the latest case will be used."""
|
If no case number is specified, the latest case will be used.
|
||||||
|
"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
potential_case = reason.split()[0]
|
if case is None:
|
||||||
if potential_case.isdigit():
|
# get the latest case
|
||||||
case = int(potential_case)
|
case = int(await modlog.get_next_case_number(guild)) - 1
|
||||||
reason = reason.replace(potential_case, "")
|
|
||||||
else:
|
|
||||||
case = str(int(await modlog.get_next_case_number(guild)) - 1)
|
|
||||||
# latest case
|
|
||||||
try:
|
try:
|
||||||
case_before = await modlog.get_case(case, guild, self.bot)
|
case_before = await modlog.get_case(case, guild, self.bot)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
|
|||||||
@ -1,5 +1,9 @@
|
|||||||
from typing import NamedTuple, Union, Optional
|
from typing import NamedTuple, Union, Optional, cast, Type
|
||||||
|
|
||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
|
from redbot.core.i18n import Translator
|
||||||
|
|
||||||
|
_ = Translator("PermissionsConverters", __file__)
|
||||||
|
|
||||||
|
|
||||||
class CogOrCommand(NamedTuple):
|
class CogOrCommand(NamedTuple):
|
||||||
@ -18,39 +22,34 @@ class CogOrCommand(NamedTuple):
|
|||||||
return cls(type="COMMAND", name=cmd.qualified_name, obj=cmd)
|
return cls(type="COMMAND", name=cmd.qualified_name, obj=cmd)
|
||||||
|
|
||||||
raise commands.BadArgument(
|
raise commands.BadArgument(
|
||||||
'Cog or command "{arg}" not found. Please note that this is case sensitive.'
|
_(
|
||||||
"".format(arg=arg)
|
'Cog or command "{name}" not found. Please note that this is case sensitive.'
|
||||||
|
).format(name=arg)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class RuleType:
|
def RuleType(arg: str) -> bool:
|
||||||
|
if arg.lower() in ("allow", "whitelist", "allowed"):
|
||||||
|
return True
|
||||||
|
if arg.lower() in ("deny", "blacklist", "denied"):
|
||||||
|
return False
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
raise commands.BadArgument(
|
||||||
@classmethod
|
_('"{arg}" is not a valid rule. Valid rules are "allow" or "deny"').format(arg=arg)
|
||||||
async def convert(cls, ctx: commands.Context, arg: str) -> bool:
|
)
|
||||||
if arg.lower() in ("allow", "whitelist", "allowed"):
|
|
||||||
return True
|
|
||||||
if arg.lower() in ("deny", "blacklist", "denied"):
|
|
||||||
return False
|
|
||||||
|
|
||||||
raise commands.BadArgument(
|
|
||||||
'"{arg}" is not a valid rule. Valid rules are "allow" or "deny"'.format(arg=arg)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class ClearableRuleType:
|
def ClearableRuleType(arg: str) -> Optional[bool]:
|
||||||
|
if arg.lower() in ("allow", "whitelist", "allowed"):
|
||||||
|
return True
|
||||||
|
if arg.lower() in ("deny", "blacklist", "denied"):
|
||||||
|
return False
|
||||||
|
if arg.lower() in ("clear", "reset"):
|
||||||
|
return None
|
||||||
|
|
||||||
# noinspection PyUnusedLocal
|
raise commands.BadArgument(
|
||||||
@classmethod
|
_(
|
||||||
async def convert(cls, ctx: commands.Context, arg: str) -> Optional[bool]:
|
|
||||||
if arg.lower() in ("allow", "whitelist", "allowed"):
|
|
||||||
return True
|
|
||||||
if arg.lower() in ("deny", "blacklist", "denied"):
|
|
||||||
return False
|
|
||||||
if arg.lower() in ("clear", "reset"):
|
|
||||||
return None
|
|
||||||
|
|
||||||
raise commands.BadArgument(
|
|
||||||
'"{arg}" is not a valid rule. Valid rules are "allow" or "deny", or "clear" to '
|
'"{arg}" is not a valid rule. Valid rules are "allow" or "deny", or "clear" to '
|
||||||
"remove the rule".format(arg=arg)
|
"remove the rule"
|
||||||
)
|
).format(arg=arg)
|
||||||
|
)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import asyncio
|
|||||||
import io
|
import io
|
||||||
import textwrap
|
import textwrap
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from typing import Union, Optional, Dict, List, Tuple, Any, Iterator, ItemsView
|
from typing import Union, Optional, Dict, List, Tuple, Any, Iterator, ItemsView, cast
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
import yaml
|
import yaml
|
||||||
@ -287,9 +287,11 @@ class Permissions(commands.Cog):
|
|||||||
`<who_or_what>` is the user, channel, role or server the rule
|
`<who_or_what>` is the user, channel, role or server the rule
|
||||||
is for.
|
is for.
|
||||||
"""
|
"""
|
||||||
# noinspection PyTypeChecker
|
|
||||||
await self._add_rule(
|
await self._add_rule(
|
||||||
rule=allow_or_deny, cog_or_cmd=cog_or_command, model_id=who_or_what.id, guild_id=0
|
rule=cast(bool, allow_or_deny),
|
||||||
|
cog_or_cmd=cog_or_command,
|
||||||
|
model_id=who_or_what.id,
|
||||||
|
guild_id=0,
|
||||||
)
|
)
|
||||||
await ctx.send(_("Rule added."))
|
await ctx.send(_("Rule added."))
|
||||||
|
|
||||||
@ -312,9 +314,8 @@ class Permissions(commands.Cog):
|
|||||||
|
|
||||||
`<who_or_what>` is the user, channel or role the rule is for.
|
`<who_or_what>` is the user, channel or role the rule is for.
|
||||||
"""
|
"""
|
||||||
# noinspection PyTypeChecker
|
|
||||||
await self._add_rule(
|
await self._add_rule(
|
||||||
rule=allow_or_deny,
|
rule=cast(bool, allow_or_deny),
|
||||||
cog_or_cmd=cog_or_command,
|
cog_or_cmd=cog_or_command,
|
||||||
model_id=who_or_what.id,
|
model_id=who_or_what.id,
|
||||||
guild_id=ctx.guild.id,
|
guild_id=ctx.guild.id,
|
||||||
@ -381,9 +382,10 @@ class Permissions(commands.Cog):
|
|||||||
`<cog_or_command>` is the cog or command to set the default
|
`<cog_or_command>` is the cog or command to set the default
|
||||||
rule for. This is case sensitive.
|
rule for. This is case sensitive.
|
||||||
"""
|
"""
|
||||||
# noinspection PyTypeChecker
|
|
||||||
await self._set_default_rule(
|
await self._set_default_rule(
|
||||||
rule=allow_or_deny, cog_or_cmd=cog_or_command, guild_id=ctx.guild.id
|
rule=cast(Optional[bool], allow_or_deny),
|
||||||
|
cog_or_cmd=cog_or_command,
|
||||||
|
guild_id=ctx.guild.id,
|
||||||
)
|
)
|
||||||
await ctx.send(_("Default set."))
|
await ctx.send(_("Default set."))
|
||||||
|
|
||||||
@ -403,9 +405,8 @@ class Permissions(commands.Cog):
|
|||||||
`<cog_or_command>` is the cog or command to set the default
|
`<cog_or_command>` is the cog or command to set the default
|
||||||
rule for. This is case sensitive.
|
rule for. This is case sensitive.
|
||||||
"""
|
"""
|
||||||
# noinspection PyTypeChecker
|
|
||||||
await self._set_default_rule(
|
await self._set_default_rule(
|
||||||
rule=allow_or_deny, cog_or_cmd=cog_or_command, guild_id=GLOBAL
|
rule=cast(Optional[bool], allow_or_deny), cog_or_cmd=cog_or_command, guild_id=GLOBAL
|
||||||
)
|
)
|
||||||
await ctx.send(_("Default set."))
|
await ctx.send(_("Default set."))
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Union
|
from typing import Union, List
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from copy import copy
|
from copy import copy
|
||||||
import contextlib
|
import contextlib
|
||||||
@ -60,23 +60,20 @@ class Reports(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@commands.group(name="reportset")
|
@commands.group(name="reportset")
|
||||||
async def reportset(self, ctx: commands.Context):
|
async def reportset(self, ctx: commands.Context):
|
||||||
"""
|
"""Manage Reports."""
|
||||||
Settings for the report system.
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@checks.admin_or_permissions(manage_guild=True)
|
@checks.admin_or_permissions(manage_guild=True)
|
||||||
@reportset.command(name="output")
|
@reportset.command(name="output")
|
||||||
async def setoutput(self, ctx: commands.Context, channel: discord.TextChannel):
|
async def reportset_output(self, ctx: commands.Context, channel: discord.TextChannel):
|
||||||
"""Set the channel where reports will show up"""
|
"""Set the channel where reports will be sent."""
|
||||||
await self.config.guild(ctx.guild).output_channel.set(channel.id)
|
await self.config.guild(ctx.guild).output_channel.set(channel.id)
|
||||||
await ctx.send(_("The report channel has been set."))
|
await ctx.send(_("The report channel has been set."))
|
||||||
|
|
||||||
@checks.admin_or_permissions(manage_guild=True)
|
@checks.admin_or_permissions(manage_guild=True)
|
||||||
@reportset.command(name="toggle", aliases=["toggleactive"])
|
@reportset.command(name="toggle", aliases=["toggleactive"])
|
||||||
async def report_toggle(self, ctx: commands.Context):
|
async def reportset_toggle(self, ctx: commands.Context):
|
||||||
"""Enables or Disables reporting for the server"""
|
"""Enable or Disable reporting for this server."""
|
||||||
|
|
||||||
active = await self.config.guild(ctx.guild).active()
|
active = await self.config.guild(ctx.guild).active()
|
||||||
active = not active
|
active = not active
|
||||||
await self.config.guild(ctx.guild).active.set(active)
|
await self.config.guild(ctx.guild).active.set(active)
|
||||||
@ -168,7 +165,7 @@ class Reports(commands.Cog):
|
|||||||
if channel is None:
|
if channel is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
files = await Tunnel.files_from_attatch(msg)
|
files: List[discord.File] = await Tunnel.files_from_attatch(msg)
|
||||||
|
|
||||||
ticket_number = await self.config.guild(guild).next_ticket()
|
ticket_number = await self.config.guild(guild).next_ticket()
|
||||||
await self.config.guild(guild).next_ticket.set(ticket_number + 1)
|
await self.config.guild(guild).next_ticket.set(ticket_number + 1)
|
||||||
@ -204,11 +201,10 @@ class Reports(commands.Cog):
|
|||||||
|
|
||||||
@commands.group(name="report", invoke_without_command=True)
|
@commands.group(name="report", invoke_without_command=True)
|
||||||
async def report(self, ctx: commands.Context, *, _report: str = ""):
|
async def report(self, ctx: commands.Context, *, _report: str = ""):
|
||||||
"""
|
"""Send a report.
|
||||||
Send a report.
|
|
||||||
|
|
||||||
Use without arguments for interactive reporting, or do
|
Use without arguments for interactive reporting, or do
|
||||||
[p]report <text> to use it non-interactively.
|
`[p]report <text>` to use it non-interactively.
|
||||||
"""
|
"""
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@ -323,8 +319,7 @@ class Reports(commands.Cog):
|
|||||||
@checks.mod_or_permissions(manage_members=True)
|
@checks.mod_or_permissions(manage_members=True)
|
||||||
@report.command(name="interact")
|
@report.command(name="interact")
|
||||||
async def response(self, ctx, ticket_number: int):
|
async def response(self, ctx, ticket_number: int):
|
||||||
"""
|
"""Open a message tunnel.
|
||||||
Open a message tunnel.
|
|
||||||
|
|
||||||
This tunnel will forward things you say in this channel
|
This tunnel will forward things you say in this channel
|
||||||
to the ticket opener's direct messages.
|
to the ticket opener's direct messages.
|
||||||
@ -354,8 +349,7 @@ class Reports(commands.Cog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
big_topic = _(
|
big_topic = _(
|
||||||
"{who} opened a 2-way communication "
|
" Anything you say or upload here "
|
||||||
"about ticket number {ticketnum}. Anything you say or upload here "
|
|
||||||
"(8MB file size limitation on uploads) "
|
"(8MB file size limitation on uploads) "
|
||||||
"will be forwarded to them until the communication is closed.\n"
|
"will be forwarded to them until the communication is closed.\n"
|
||||||
"You can close a communication at any point by reacting with "
|
"You can close a communication at any point by reacting with "
|
||||||
@ -364,8 +358,12 @@ class Reports(commands.Cog):
|
|||||||
"\N{WHITE HEAVY CHECK MARK}.\n"
|
"\N{WHITE HEAVY CHECK MARK}.\n"
|
||||||
"Tunnels are not persistent across bot restarts."
|
"Tunnels are not persistent across bot restarts."
|
||||||
)
|
)
|
||||||
topic = big_topic.format(
|
topic = (
|
||||||
ticketnum=ticket_number, who=_("A moderator in `{guild.name}` has").format(guild=guild)
|
_(
|
||||||
|
"A moderator in the server `{guild.name}` has opened a 2-way communication about "
|
||||||
|
"ticket number {ticket_number}."
|
||||||
|
).format(guild=guild, ticket_number=ticket_number)
|
||||||
|
+ big_topic
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True)
|
m = await tun.communicate(message=ctx.message, topic=topic, skip_message_content=True)
|
||||||
@ -373,4 +371,9 @@ class Reports(commands.Cog):
|
|||||||
await ctx.send(_("That user has DMs disabled."))
|
await ctx.send(_("That user has DMs disabled."))
|
||||||
else:
|
else:
|
||||||
self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m}
|
self.tunnel_store[(guild, ticket_number)] = {"tun": tun, "msgs": m}
|
||||||
await ctx.send(big_topic.format(who=_("You have"), ticketnum=ticket_number))
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"You have opened a 2-way communication about ticket number {ticket_number}."
|
||||||
|
).format(ticket_number=ticket_number)
|
||||||
|
+ big_topic
|
||||||
|
)
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import contextlib
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
from redbot.core import Config, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
from redbot.core.utils.chat_formatting import pagify
|
from redbot.core.utils.chat_formatting import pagify
|
||||||
@ -22,7 +24,7 @@ from .errors import (
|
|||||||
StreamsError,
|
StreamsError,
|
||||||
InvalidTwitchCredentials,
|
InvalidTwitchCredentials,
|
||||||
)
|
)
|
||||||
from . import streamtypes as StreamClasses
|
from . import streamtypes as _streamtypes
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import asyncio
|
import asyncio
|
||||||
import re
|
import re
|
||||||
@ -76,14 +78,14 @@ class Streams(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def twitch(self, ctx: commands.Context, channel_name: str):
|
async def twitch(self, ctx: commands.Context, channel_name: str):
|
||||||
"""Checks if a Twitch channel is live"""
|
"""Check if a Twitch channel is live."""
|
||||||
token = await self.db.tokens.get_raw(TwitchStream.__name__, default=None)
|
token = await self.db.tokens.get_raw(TwitchStream.__name__, default=None)
|
||||||
stream = TwitchStream(name=channel_name, token=token)
|
stream = TwitchStream(name=channel_name, token=token)
|
||||||
await self.check_online(ctx, stream)
|
await self.check_online(ctx, stream)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def youtube(self, ctx: commands.Context, channel_id_or_name: str):
|
async def youtube(self, ctx: commands.Context, channel_id_or_name: str):
|
||||||
"""Checks if a Youtube channel is live"""
|
"""Check if a YouTube channel is live."""
|
||||||
apikey = await self.db.tokens.get_raw(YoutubeStream.__name__, default=None)
|
apikey = await self.db.tokens.get_raw(YoutubeStream.__name__, default=None)
|
||||||
is_name = self.check_name_or_id(channel_id_or_name)
|
is_name = self.check_name_or_id(channel_id_or_name)
|
||||||
if is_name:
|
if is_name:
|
||||||
@ -94,23 +96,24 @@ class Streams(commands.Cog):
|
|||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def hitbox(self, ctx: commands.Context, channel_name: str):
|
async def hitbox(self, ctx: commands.Context, channel_name: str):
|
||||||
"""Checks if a Hitbox channel is live"""
|
"""Check if a Hitbox channel is live."""
|
||||||
stream = HitboxStream(name=channel_name)
|
stream = HitboxStream(name=channel_name)
|
||||||
await self.check_online(ctx, stream)
|
await self.check_online(ctx, stream)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def mixer(self, ctx: commands.Context, channel_name: str):
|
async def mixer(self, ctx: commands.Context, channel_name: str):
|
||||||
"""Checks if a Mixer channel is live"""
|
"""Check if a Mixer channel is live."""
|
||||||
stream = MixerStream(name=channel_name)
|
stream = MixerStream(name=channel_name)
|
||||||
await self.check_online(ctx, stream)
|
await self.check_online(ctx, stream)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
async def picarto(self, ctx: commands.Context, channel_name: str):
|
async def picarto(self, ctx: commands.Context, channel_name: str):
|
||||||
"""Checks if a Picarto channel is live"""
|
"""Check if a Picarto channel is live."""
|
||||||
stream = PicartoStream(name=channel_name)
|
stream = PicartoStream(name=channel_name)
|
||||||
await self.check_online(ctx, stream)
|
await self.check_online(ctx, stream)
|
||||||
|
|
||||||
async def check_online(self, ctx: commands.Context, stream):
|
@staticmethod
|
||||||
|
async def check_online(ctx: commands.Context, stream):
|
||||||
try:
|
try:
|
||||||
embed = await stream.is_online()
|
embed = await stream.is_online()
|
||||||
except OfflineStream:
|
except OfflineStream:
|
||||||
@ -119,15 +122,17 @@ class Streams(commands.Cog):
|
|||||||
await ctx.send(_("That channel doesn't seem to exist."))
|
await ctx.send(_("That channel doesn't seem to exist."))
|
||||||
except InvalidTwitchCredentials:
|
except InvalidTwitchCredentials:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("The twitch token is either invalid or has not been set. See `{}`.").format(
|
_(
|
||||||
"{}streamset twitchtoken".format(ctx.prefix)
|
"The Twitch token is either invalid or has not been set. See "
|
||||||
)
|
"`{prefix}streamset twitchtoken`."
|
||||||
|
).format(prefix=ctx.prefix)
|
||||||
)
|
)
|
||||||
except InvalidYoutubeCredentials:
|
except InvalidYoutubeCredentials:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Your Youtube API key is either invalid or has not been set. See {}.").format(
|
_(
|
||||||
"`{}streamset youtubekey`".format(ctx.prefix)
|
"The YouTube API key is either invalid or has not been set. See "
|
||||||
)
|
"`{prefix}streamset youtubekey`."
|
||||||
|
).format(prefix=ctx.prefix)
|
||||||
)
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
@ -140,11 +145,12 @@ class Streams(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.mod()
|
@checks.mod()
|
||||||
async def streamalert(self, ctx: commands.Context):
|
async def streamalert(self, ctx: commands.Context):
|
||||||
|
"""Manage automated stream alerts."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@streamalert.group(name="twitch", invoke_without_command=True)
|
@streamalert.group(name="twitch", invoke_without_command=True)
|
||||||
async def _twitch(self, ctx: commands.Context, channel_name: str = None):
|
async def _twitch(self, ctx: commands.Context, channel_name: str = None):
|
||||||
"""Twitch stream alerts"""
|
"""Manage Twitch stream notifications."""
|
||||||
if channel_name is not None:
|
if channel_name is not None:
|
||||||
await ctx.invoke(self.twitch_alert_channel, channel_name)
|
await ctx.invoke(self.twitch_alert_channel, channel_name)
|
||||||
else:
|
else:
|
||||||
@ -152,7 +158,7 @@ class Streams(commands.Cog):
|
|||||||
|
|
||||||
@_twitch.command(name="channel")
|
@_twitch.command(name="channel")
|
||||||
async def twitch_alert_channel(self, ctx: commands.Context, channel_name: str):
|
async def twitch_alert_channel(self, ctx: commands.Context, channel_name: str):
|
||||||
"""Sets a Twitch alert notification in the channel"""
|
"""Toggle alerts in this channel for a Twitch stream."""
|
||||||
if re.fullmatch(r"<#\d+>", channel_name):
|
if re.fullmatch(r"<#\d+>", channel_name):
|
||||||
await ctx.send("Please supply the name of a *Twitch* channel, not a Discord channel.")
|
await ctx.send("Please supply the name of a *Twitch* channel, not a Discord channel.")
|
||||||
return
|
return
|
||||||
@ -160,33 +166,39 @@ class Streams(commands.Cog):
|
|||||||
|
|
||||||
@_twitch.command(name="community")
|
@_twitch.command(name="community")
|
||||||
async def twitch_alert_community(self, ctx: commands.Context, community: str):
|
async def twitch_alert_community(self, ctx: commands.Context, community: str):
|
||||||
"""Sets an alert notification in the channel for the specified twitch community."""
|
"""Toggle alerts in this channel for a Twitch community."""
|
||||||
await self.community_alert(ctx, TwitchCommunity, community.lower())
|
await self.community_alert(ctx, TwitchCommunity, community.lower())
|
||||||
|
|
||||||
@streamalert.command(name="youtube")
|
@streamalert.command(name="youtube")
|
||||||
async def youtube_alert(self, ctx: commands.Context, channel_name_or_id: str):
|
async def youtube_alert(self, ctx: commands.Context, channel_name_or_id: str):
|
||||||
"""Sets a Youtube alert notification in the channel"""
|
"""Toggle alerts in this channel for a YouTube stream."""
|
||||||
await self.stream_alert(ctx, YoutubeStream, channel_name_or_id)
|
await self.stream_alert(ctx, YoutubeStream, channel_name_or_id)
|
||||||
|
|
||||||
@streamalert.command(name="hitbox")
|
@streamalert.command(name="hitbox")
|
||||||
async def hitbox_alert(self, ctx: commands.Context, channel_name: str):
|
async def hitbox_alert(self, ctx: commands.Context, channel_name: str):
|
||||||
"""Sets a Hitbox alert notification in the channel"""
|
"""Toggle alerts in this channel for a Hitbox stream."""
|
||||||
await self.stream_alert(ctx, HitboxStream, channel_name)
|
await self.stream_alert(ctx, HitboxStream, channel_name)
|
||||||
|
|
||||||
@streamalert.command(name="mixer")
|
@streamalert.command(name="mixer")
|
||||||
async def mixer_alert(self, ctx: commands.Context, channel_name: str):
|
async def mixer_alert(self, ctx: commands.Context, channel_name: str):
|
||||||
"""Sets a Mixer alert notification in the channel"""
|
"""Toggle alerts in this channel for a Mixer stream."""
|
||||||
await self.stream_alert(ctx, MixerStream, channel_name)
|
await self.stream_alert(ctx, MixerStream, channel_name)
|
||||||
|
|
||||||
@streamalert.command(name="picarto")
|
@streamalert.command(name="picarto")
|
||||||
async def picarto_alert(self, ctx: commands.Context, channel_name: str):
|
async def picarto_alert(self, ctx: commands.Context, channel_name: str):
|
||||||
"""Sets a Picarto alert notification in the channel"""
|
"""Toggle alerts in this channel for a Picarto stream."""
|
||||||
await self.stream_alert(ctx, PicartoStream, channel_name)
|
await self.stream_alert(ctx, PicartoStream, channel_name)
|
||||||
|
|
||||||
@streamalert.command(name="stop")
|
@streamalert.command(name="stop", usage="[disable_all=No]")
|
||||||
async def streamalert_stop(self, ctx: commands.Context, _all: bool = False):
|
async def streamalert_stop(self, ctx: commands.Context, _all: bool = False):
|
||||||
"""Stops all stream notifications in the channel
|
"""Disable all stream alerts in this channel or server.
|
||||||
Adding 'yes' will disable all notifications in the server"""
|
|
||||||
|
`[p]streamalert stop` will disable this channel's stream
|
||||||
|
alerts.
|
||||||
|
|
||||||
|
Do `[p]streamalert stop yes` to disable all stream alerts in
|
||||||
|
this server.
|
||||||
|
"""
|
||||||
streams = self.streams.copy()
|
streams = self.streams.copy()
|
||||||
local_channel_ids = [c.id for c in ctx.guild.channels]
|
local_channel_ids = [c.id for c in ctx.guild.channels]
|
||||||
to_remove = []
|
to_remove = []
|
||||||
@ -208,9 +220,10 @@ class Streams(commands.Cog):
|
|||||||
self.streams = streams
|
self.streams = streams
|
||||||
await self.save_streams()
|
await self.save_streams()
|
||||||
|
|
||||||
msg = _("All the alerts in the {} have been disabled.").format(
|
if _all:
|
||||||
"server" if _all else "channel"
|
msg = _("All the stream alerts in this server have been disabled.")
|
||||||
)
|
else:
|
||||||
|
msg = _("All the stream alerts in this channel have been disabled.")
|
||||||
|
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
|
|
||||||
@ -250,16 +263,18 @@ class Streams(commands.Cog):
|
|||||||
exists = await self.check_exists(stream)
|
exists = await self.check_exists(stream)
|
||||||
except InvalidTwitchCredentials:
|
except InvalidTwitchCredentials:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("Your twitch token is either invalid or has not been set. See {}.").format(
|
_(
|
||||||
"`{}streamset twitchtoken`".format(ctx.prefix)
|
"The Twitch token is either invalid or has not been set. See "
|
||||||
)
|
"`{prefix}streamset twitchtoken`."
|
||||||
|
).format(prefix=ctx.prefix)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except InvalidYoutubeCredentials:
|
except InvalidYoutubeCredentials:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"Your Youtube API key is either invalid or has not been set. See {}."
|
"The YouTube API key is either invalid or has not been set. See "
|
||||||
).format("`{}streamset youtubekey`".format(ctx.prefix))
|
"`{prefix}streamset youtubekey`."
|
||||||
|
).format(prefix=ctx.prefix)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except APIError:
|
except APIError:
|
||||||
@ -283,9 +298,10 @@ class Streams(commands.Cog):
|
|||||||
await community.get_community_streams()
|
await community.get_community_streams()
|
||||||
except InvalidTwitchCredentials:
|
except InvalidTwitchCredentials:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("The twitch token is either invalid or has not been set. See {}.").format(
|
_(
|
||||||
"`{}streamset twitchtoken`".format(ctx.prefix)
|
"The Twitch token is either invalid or has not been set. See "
|
||||||
)
|
"`{prefix}streamset twitchtoken`."
|
||||||
|
).format(prefix=ctx.prefix)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except CommunityNotFound:
|
except CommunityNotFound:
|
||||||
@ -309,14 +325,15 @@ class Streams(commands.Cog):
|
|||||||
@streamset.command()
|
@streamset.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def twitchtoken(self, ctx: commands.Context, token: str):
|
async def twitchtoken(self, ctx: commands.Context, token: str):
|
||||||
"""Set the Client ID for twitch.
|
"""Set the Client ID for Twitch.
|
||||||
|
|
||||||
To do this, follow these steps:
|
To do this, follow these steps:
|
||||||
1. Go to this page: https://dev.twitch.tv/dashboard/apps.
|
1. Go to this page: https://dev.twitch.tv/dashboard/apps.
|
||||||
2. Click *Register Your Application*
|
2. Click *Register Your Application*
|
||||||
3. Enter a name, set the OAuth Redirect URI to `http://localhost`, and
|
3. Enter a name, set the OAuth Redirect URI to `http://localhost`, and
|
||||||
select an Application Category of your choosing.
|
select an Application Category of your choosing.
|
||||||
4. Click *Register*, and on the following page, copy the Client ID.
|
4. Click *Register*, and on the following page, copy the Client ID.
|
||||||
5. Paste the Client ID into this command. Done!
|
5. Paste the Client ID into this command. Done!
|
||||||
"""
|
"""
|
||||||
await self.db.tokens.set_raw("TwitchStream", value=token)
|
await self.db.tokens.set_raw("TwitchStream", value=token)
|
||||||
await self.db.tokens.set_raw("TwitchCommunity", value=token)
|
await self.db.tokens.set_raw("TwitchCommunity", value=token)
|
||||||
@ -325,64 +342,59 @@ class Streams(commands.Cog):
|
|||||||
@streamset.command()
|
@streamset.command()
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def youtubekey(self, ctx: commands.Context, key: str):
|
async def youtubekey(self, ctx: commands.Context, key: str):
|
||||||
"""Sets the API key for Youtube.
|
"""Set the API key for YouTube.
|
||||||
|
|
||||||
To get one, do the following:
|
To get one, do the following:
|
||||||
1. Create a project (see https://support.google.com/googleapi/answer/6251787 for details)
|
1. Create a project (see https://support.google.com/googleapi/answer/6251787 for details)
|
||||||
2. Enable the Youtube Data API v3 (see https://support.google.com/googleapi/answer/6158841 for instructions)
|
2. Enable the YouTube Data API v3 (see https://support.google.com/googleapi/answer/6158841
|
||||||
3. Set up your API key (see https://support.google.com/googleapi/answer/6158862 for instructions)
|
for instructions)
|
||||||
|
3. Set up your API key (see https://support.google.com/googleapi/answer/6158862 for
|
||||||
|
instructions)
|
||||||
4. Copy your API key and paste it into this command. Done!
|
4. Copy your API key and paste it into this command. Done!
|
||||||
"""
|
"""
|
||||||
await self.db.tokens.set_raw("YoutubeStream", value=key)
|
await self.db.tokens.set_raw("YoutubeStream", value=key)
|
||||||
await ctx.send(_("Youtube key set."))
|
await ctx.send(_("YouTube key set."))
|
||||||
|
|
||||||
@streamset.group()
|
@streamset.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def mention(self, ctx: commands.Context):
|
async def mention(self, ctx: commands.Context):
|
||||||
"""Sets mentions for alerts."""
|
"""Manage mention settings for stream alerts."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@mention.command(aliases=["everyone"])
|
@mention.command(aliases=["everyone"])
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def all(self, ctx: commands.Context):
|
async def all(self, ctx: commands.Context):
|
||||||
"""Toggles everyone mention"""
|
"""Toggle the `@\u200beveryone` mention."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
current_setting = await self.db.guild(guild).mention_everyone()
|
current_setting = await self.db.guild(guild).mention_everyone()
|
||||||
if current_setting:
|
if current_setting:
|
||||||
await self.db.guild(guild).mention_everyone.set(False)
|
await self.db.guild(guild).mention_everyone.set(False)
|
||||||
await ctx.send(
|
await ctx.send(_("`@\u200beveryone` will no longer be mentioned for stream alerts."))
|
||||||
_("{} will no longer be mentioned when a stream or community is live").format(
|
|
||||||
"@\u200beveryone"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
await self.db.guild(guild).mention_everyone.set(True)
|
await self.db.guild(guild).mention_everyone.set(True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("When a stream or community " "is live, {} will be mentioned.").format(
|
_("When a stream or community is live, `@\u200beveryone` will be mentioned.")
|
||||||
"@\u200beveryone"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@mention.command(aliases=["here"])
|
@mention.command(aliases=["here"])
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def online(self, ctx: commands.Context):
|
async def online(self, ctx: commands.Context):
|
||||||
"""Toggles here mention"""
|
"""Toggle the `@\u200bhere` mention."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
current_setting = await self.db.guild(guild).mention_here()
|
current_setting = await self.db.guild(guild).mention_here()
|
||||||
if current_setting:
|
if current_setting:
|
||||||
await self.db.guild(guild).mention_here.set(False)
|
await self.db.guild(guild).mention_here.set(False)
|
||||||
await ctx.send(_("{} will no longer be mentioned for an alert.").format("@\u200bhere"))
|
await ctx.send(_("`@\u200bhere` will no longer be mentioned for stream alerts."))
|
||||||
else:
|
else:
|
||||||
await self.db.guild(guild).mention_here.set(True)
|
await self.db.guild(guild).mention_here.set(True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("When a stream or community " "is live, {} will be mentioned.").format(
|
_("When a stream or community is live, `@\u200bhere` will be mentioned.")
|
||||||
"@\u200bhere"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@mention.command()
|
@mention.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def role(self, ctx: commands.Context, *, role: discord.Role):
|
async def role(self, ctx: commands.Context, *, role: discord.Role):
|
||||||
"""Toggles role mention"""
|
"""Toggle a role mention."""
|
||||||
current_setting = await self.db.role(role).mention()
|
current_setting = await self.db.role(role).mention()
|
||||||
if not role.mentionable:
|
if not role.mentionable:
|
||||||
await ctx.send("That role is not mentionable!")
|
await ctx.send("That role is not mentionable!")
|
||||||
@ -390,27 +402,27 @@ class Streams(commands.Cog):
|
|||||||
if current_setting:
|
if current_setting:
|
||||||
await self.db.role(role).mention.set(False)
|
await self.db.role(role).mention.set(False)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("{} will no longer be mentioned for an alert.").format(
|
_("`@\u200b{role.name}` will no longer be mentioned for stream alerts.").format(
|
||||||
"@\u200b{}".format(role.name)
|
role=role
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
await self.db.role(role).mention.set(True)
|
await self.db.role(role).mention.set(True)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("When a stream or community " "is live, {} will be mentioned." "").format(
|
_(
|
||||||
"@\u200b{}".format(role.name)
|
"When a stream or community is live, `@\u200b{role.name}` will be mentioned."
|
||||||
)
|
).format(role=role)
|
||||||
)
|
)
|
||||||
|
|
||||||
@streamset.command()
|
@streamset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def autodelete(self, ctx: commands.Context, on_off: bool):
|
async def autodelete(self, ctx: commands.Context, on_off: bool):
|
||||||
"""Toggles automatic deletion of notifications for streams that go offline"""
|
"""Toggle alert deletion for when streams go offline."""
|
||||||
await self.db.guild(ctx.guild).autodelete.set(on_off)
|
await self.db.guild(ctx.guild).autodelete.set(on_off)
|
||||||
if on_off:
|
if on_off:
|
||||||
await ctx.send("The notifications will be deleted once streams go offline.")
|
await ctx.send(_("The notifications will be deleted once streams go offline."))
|
||||||
else:
|
else:
|
||||||
await ctx.send("Notifications will never be deleted.")
|
await ctx.send(_("Notifications will no longer be deleted."))
|
||||||
|
|
||||||
async def add_or_remove(self, ctx: commands.Context, stream):
|
async def add_or_remove(self, ctx: commands.Context, stream):
|
||||||
if ctx.channel.id not in stream.channels:
|
if ctx.channel.id not in stream.channels:
|
||||||
@ -418,18 +430,18 @@ class Streams(commands.Cog):
|
|||||||
if stream not in self.streams:
|
if stream not in self.streams:
|
||||||
self.streams.append(stream)
|
self.streams.append(stream)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("I'll now send a notification in this channel when {} is live.").format(
|
_(
|
||||||
stream.name
|
"I'll now send a notification in this channel when {stream.name} is live."
|
||||||
)
|
).format(stream=stream)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
stream.channels.remove(ctx.channel.id)
|
stream.channels.remove(ctx.channel.id)
|
||||||
if not stream.channels:
|
if not stream.channels:
|
||||||
self.streams.remove(stream)
|
self.streams.remove(stream)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_("I won't send notifications about {} in this channel anymore.").format(
|
_(
|
||||||
stream.name
|
"I won't send notifications about {stream.name} in this channel anymore."
|
||||||
)
|
).format(stream=stream)
|
||||||
)
|
)
|
||||||
|
|
||||||
await self.save_streams()
|
await self.save_streams()
|
||||||
@ -442,9 +454,8 @@ class Streams(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"I'll send a notification in this channel when a "
|
"I'll send a notification in this channel when a "
|
||||||
"channel is live in the {} community."
|
"channel is live in the {community.name} community."
|
||||||
""
|
).format(community=community)
|
||||||
).format(community.name)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
community.channels.remove(ctx.channel.id)
|
community.channels.remove(ctx.channel.id)
|
||||||
@ -453,9 +464,8 @@ class Streams(commands.Cog):
|
|||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"I won't send notifications about channels streaming "
|
"I won't send notifications about channels streaming "
|
||||||
"in the {} community in this channel anymore."
|
"in the {community.name} community in this channel anymore."
|
||||||
""
|
).format(community=community)
|
||||||
).format(community.name)
|
|
||||||
)
|
)
|
||||||
await self.save_communities()
|
await self.save_communities()
|
||||||
|
|
||||||
@ -481,7 +491,8 @@ class Streams(commands.Cog):
|
|||||||
if community.type == _class.__name__ and community.name.lower() == name.lower():
|
if community.type == _class.__name__ and community.name.lower() == name.lower():
|
||||||
return community
|
return community
|
||||||
|
|
||||||
async def check_exists(self, stream):
|
@staticmethod
|
||||||
|
async def check_exists(stream):
|
||||||
try:
|
try:
|
||||||
await stream.is_online()
|
await stream.is_online()
|
||||||
except OfflineStream:
|
except OfflineStream:
|
||||||
@ -506,40 +517,36 @@ class Streams(commands.Cog):
|
|||||||
|
|
||||||
async def check_streams(self):
|
async def check_streams(self):
|
||||||
for stream in self.streams:
|
for stream in self.streams:
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
embed = await stream.is_online()
|
try:
|
||||||
except OfflineStream:
|
embed = await stream.is_online()
|
||||||
if not stream._messages_cache:
|
except OfflineStream:
|
||||||
continue
|
if not stream._messages_cache:
|
||||||
for message in stream._messages_cache:
|
continue
|
||||||
try:
|
for message in stream._messages_cache:
|
||||||
autodelete = await self.db.guild(message.guild).autodelete()
|
with contextlib.suppress(Exception):
|
||||||
if autodelete:
|
autodelete = await self.db.guild(message.guild).autodelete()
|
||||||
await message.delete()
|
if autodelete:
|
||||||
except:
|
await message.delete()
|
||||||
pass
|
stream._messages_cache.clear()
|
||||||
stream._messages_cache.clear()
|
await self.save_streams()
|
||||||
await self.save_streams()
|
else:
|
||||||
except:
|
if stream._messages_cache:
|
||||||
pass
|
continue
|
||||||
else:
|
for channel_id in stream.channels:
|
||||||
if stream._messages_cache:
|
channel = self.bot.get_channel(channel_id)
|
||||||
continue
|
mention_str = await self._get_mention_str(channel.guild)
|
||||||
for channel_id in stream.channels:
|
|
||||||
channel = self.bot.get_channel(channel_id)
|
|
||||||
mention_str = await self._get_mention_str(channel.guild)
|
|
||||||
|
|
||||||
if mention_str:
|
if mention_str:
|
||||||
content = "{}, {} is live!".format(mention_str, stream.name)
|
content = _("{mention}, {stream.name} is live!").format(
|
||||||
else:
|
mention=mention_str, stream=stream
|
||||||
content = "{} is live!".format(stream.name)
|
)
|
||||||
|
else:
|
||||||
|
content = _("{stream.name} is live!").format(stream=stream.name)
|
||||||
|
|
||||||
try:
|
|
||||||
m = await channel.send(content, embed=embed)
|
m = await channel.send(content, embed=embed)
|
||||||
stream._messages_cache.append(m)
|
stream._messages_cache.append(m)
|
||||||
await self.save_streams()
|
await self.save_streams()
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_mention_str(self, guild: discord.Guild):
|
async def _get_mention_str(self, guild: discord.Guild):
|
||||||
settings = self.db.guild(guild)
|
settings = self.db.guild(guild)
|
||||||
@ -555,45 +562,46 @@ class Streams(commands.Cog):
|
|||||||
|
|
||||||
async def check_communities(self):
|
async def check_communities(self):
|
||||||
for community in self.communities:
|
for community in self.communities:
|
||||||
try:
|
with contextlib.suppress(Exception):
|
||||||
stream_list = await community.get_community_streams()
|
try:
|
||||||
except CommunityNotFound:
|
stream_list = await community.get_community_streams()
|
||||||
print(_("The Community {} was not found!").format(community.name))
|
except CommunityNotFound:
|
||||||
continue
|
print(
|
||||||
except OfflineCommunity:
|
_("The Community {community.name} was not found!").format(
|
||||||
if not community._messages_cache:
|
community=community
|
||||||
|
)
|
||||||
|
)
|
||||||
continue
|
continue
|
||||||
for message in community._messages_cache:
|
except OfflineCommunity:
|
||||||
try:
|
if not community._messages_cache:
|
||||||
autodelete = await self.db.guild(message.guild).autodelete()
|
continue
|
||||||
if autodelete:
|
for message in community._messages_cache:
|
||||||
await message.delete()
|
with contextlib.suppress(Exception):
|
||||||
except:
|
autodelete = await self.db.guild(message.guild).autodelete()
|
||||||
pass
|
if autodelete:
|
||||||
community._messages_cache.clear()
|
await message.delete()
|
||||||
await self.save_communities()
|
community._messages_cache.clear()
|
||||||
except:
|
await self.save_communities()
|
||||||
pass
|
else:
|
||||||
else:
|
for channel in community.channels:
|
||||||
for channel in community.channels:
|
chn = self.bot.get_channel(channel)
|
||||||
chn = self.bot.get_channel(channel)
|
streams = await self.filter_streams(stream_list, chn)
|
||||||
streams = await self.filter_streams(stream_list, chn)
|
emb = await community.make_embed(streams)
|
||||||
emb = await community.make_embed(streams)
|
chn_msg = [m for m in community._messages_cache if m.channel == chn]
|
||||||
chn_msg = [m for m in community._messages_cache if m.channel == chn]
|
if not chn_msg:
|
||||||
if not chn_msg:
|
mentions = await self._get_mention_str(chn.guild)
|
||||||
mentions = await self._get_mention_str(chn.guild)
|
if mentions:
|
||||||
if mentions:
|
msg = await chn.send(mentions, embed=emb)
|
||||||
msg = await chn.send(mentions, embed=emb)
|
else:
|
||||||
|
msg = await chn.send(embed=emb)
|
||||||
|
community._messages_cache.append(msg)
|
||||||
|
await self.save_communities()
|
||||||
else:
|
else:
|
||||||
msg = await chn.send(embed=emb)
|
chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0]
|
||||||
community._messages_cache.append(msg)
|
community._messages_cache.remove(chn_msg)
|
||||||
await self.save_communities()
|
await chn_msg.edit(embed=emb)
|
||||||
else:
|
community._messages_cache.append(chn_msg)
|
||||||
chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0]
|
await self.save_communities()
|
||||||
community._messages_cache.remove(chn_msg)
|
|
||||||
await chn_msg.edit(embed=emb)
|
|
||||||
community._messages_cache.append(chn_msg)
|
|
||||||
await self.save_communities()
|
|
||||||
|
|
||||||
async def filter_streams(self, streams: list, channel: discord.TextChannel) -> list:
|
async def filter_streams(self, streams: list, channel: discord.TextChannel) -> list:
|
||||||
filtered = []
|
filtered = []
|
||||||
@ -611,7 +619,7 @@ class Streams(commands.Cog):
|
|||||||
streams = []
|
streams = []
|
||||||
|
|
||||||
for raw_stream in await self.db.streams():
|
for raw_stream in await self.db.streams():
|
||||||
_class = getattr(StreamClasses, raw_stream["type"], None)
|
_class = getattr(_streamtypes, raw_stream["type"], None)
|
||||||
if not _class:
|
if not _class:
|
||||||
continue
|
continue
|
||||||
raw_msg_cache = raw_stream["messages"]
|
raw_msg_cache = raw_stream["messages"]
|
||||||
@ -631,7 +639,7 @@ class Streams(commands.Cog):
|
|||||||
communities = []
|
communities = []
|
||||||
|
|
||||||
for raw_community in await self.db.communities():
|
for raw_community in await self.db.communities():
|
||||||
_class = getattr(StreamClasses, raw_community["type"], None)
|
_class = getattr(_streamtypes, raw_community["type"], None)
|
||||||
if not _class:
|
if not _class:
|
||||||
continue
|
continue
|
||||||
raw_msg_cache = raw_community["messages"]
|
raw_msg_cache = raw_community["messages"]
|
||||||
|
|||||||
@ -4,20 +4,30 @@ import time
|
|||||||
import random
|
import random
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
import discord
|
import discord
|
||||||
from redbot.core.bank import deposit_credits
|
from redbot.core import bank
|
||||||
from redbot.core.utils.chat_formatting import box
|
from redbot.core.i18n import Translator
|
||||||
|
from redbot.core.utils.chat_formatting import box, bold, humanize_list
|
||||||
from redbot.core.utils.common_filters import normalize_smartquotes
|
from redbot.core.utils.common_filters import normalize_smartquotes
|
||||||
from .log import LOG
|
from .log import LOG
|
||||||
|
|
||||||
__all__ = ["TriviaSession"]
|
__all__ = ["TriviaSession"]
|
||||||
|
|
||||||
_REVEAL_MESSAGES = ("I know this one! {}!", "Easy: {}.", "Oh really? It's {} of course.")
|
T_ = Translator("TriviaSession", __file__)
|
||||||
_FAIL_MESSAGES = (
|
|
||||||
"To the next one I guess...",
|
|
||||||
"Moving on...",
|
_ = lambda s: s
|
||||||
"I'm sure you'll know the answer of the next one.",
|
_REVEAL_MESSAGES = (
|
||||||
"\N{PENSIVE FACE} Next one.",
|
_("I know this one! {answer}!"),
|
||||||
|
_("Easy: {answer}."),
|
||||||
|
_("Oh really? It's {answer} of course."),
|
||||||
)
|
)
|
||||||
|
_FAIL_MESSAGES = (
|
||||||
|
_("To the next one I guess..."),
|
||||||
|
_("Moving on..."),
|
||||||
|
_("I'm sure you'll know the answer of the next one."),
|
||||||
|
_("\N{PENSIVE FACE} Next one."),
|
||||||
|
)
|
||||||
|
_ = T_
|
||||||
|
|
||||||
|
|
||||||
class TriviaSession:
|
class TriviaSession:
|
||||||
@ -104,7 +114,7 @@ class TriviaSession:
|
|||||||
async with self.ctx.typing():
|
async with self.ctx.typing():
|
||||||
await asyncio.sleep(3)
|
await asyncio.sleep(3)
|
||||||
self.count += 1
|
self.count += 1
|
||||||
msg = "**Question number {}!**\n\n{}".format(self.count, question)
|
msg = bold(_("**Question number {num}!").format(num=self.count)) + "\n\n" + question
|
||||||
await self.ctx.send(msg)
|
await self.ctx.send(msg)
|
||||||
continue_ = await self.wait_for_answer(answers, delay, timeout)
|
continue_ = await self.wait_for_answer(answers, delay, timeout)
|
||||||
if continue_ is False:
|
if continue_ is False:
|
||||||
@ -113,7 +123,7 @@ class TriviaSession:
|
|||||||
await self.end_game()
|
await self.end_game()
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
await self.ctx.send("There are no more questions!")
|
await self.ctx.send(_("There are no more questions!"))
|
||||||
await self.end_game()
|
await self.end_game()
|
||||||
|
|
||||||
async def _send_startup_msg(self):
|
async def _send_startup_msg(self):
|
||||||
@ -121,20 +131,13 @@ class TriviaSession:
|
|||||||
for idx, tup in enumerate(self.settings["lists"].items()):
|
for idx, tup in enumerate(self.settings["lists"].items()):
|
||||||
name, author = tup
|
name, author = tup
|
||||||
if author:
|
if author:
|
||||||
title = "{} (by {})".format(name, author)
|
title = _("{trivia_list} (by {author})").format(trivia_list=name, author=author)
|
||||||
else:
|
else:
|
||||||
title = name
|
title = name
|
||||||
list_names.append(title)
|
list_names.append(title)
|
||||||
num_lists = len(list_names)
|
await self.ctx.send(
|
||||||
if num_lists > 2:
|
_("Starting Trivia: {list_names}").format(list_names=humanize_list(list_names))
|
||||||
# at least 3 lists, join all but last with comma
|
)
|
||||||
msg = ", ".join(list_names[: num_lists - 1])
|
|
||||||
# join onto last with "and"
|
|
||||||
msg = " and ".join((msg, list_names[num_lists - 1]))
|
|
||||||
else:
|
|
||||||
# either 1 or 2 lists, join together with "and"
|
|
||||||
msg = " and ".join(list_names)
|
|
||||||
await self.ctx.send("Starting Trivia: " + msg)
|
|
||||||
|
|
||||||
def _iter_questions(self):
|
def _iter_questions(self):
|
||||||
"""Iterate over questions and answers for this session.
|
"""Iterate over questions and answers for this session.
|
||||||
@ -179,20 +182,20 @@ class TriviaSession:
|
|||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
if time.time() - self._last_response >= timeout:
|
if time.time() - self._last_response >= timeout:
|
||||||
await self.ctx.send("Guys...? Well, I guess I'll stop then.")
|
await self.ctx.send(_("Guys...? Well, I guess I'll stop then."))
|
||||||
self.stop()
|
self.stop()
|
||||||
return False
|
return False
|
||||||
if self.settings["reveal_answer"]:
|
if self.settings["reveal_answer"]:
|
||||||
reply = random.choice(_REVEAL_MESSAGES).format(answers[0])
|
reply = T_(random.choice(_REVEAL_MESSAGES)).format(answer=answers[0])
|
||||||
else:
|
else:
|
||||||
reply = random.choice(_FAIL_MESSAGES)
|
reply = T_(random.choice(_FAIL_MESSAGES))
|
||||||
if self.settings["bot_plays"]:
|
if self.settings["bot_plays"]:
|
||||||
reply += " **+1** for me!"
|
reply += _(" **+1** for me!")
|
||||||
self.scores[self.ctx.guild.me] += 1
|
self.scores[self.ctx.guild.me] += 1
|
||||||
await self.ctx.send(reply)
|
await self.ctx.send(reply)
|
||||||
else:
|
else:
|
||||||
self.scores[message.author] += 1
|
self.scores[message.author] += 1
|
||||||
reply = "You got it {}! **+1** to you!".format(message.author.display_name)
|
reply = _("You got it {user}! **+1** to you!").format(user=message.author.display_name)
|
||||||
await self.ctx.send(reply)
|
await self.ctx.send(reply)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -282,10 +285,16 @@ class TriviaSession:
|
|||||||
amount = int(multiplier * score)
|
amount = int(multiplier * score)
|
||||||
if amount > 0:
|
if amount > 0:
|
||||||
LOG.debug("Paying trivia winner: %d credits --> %s", amount, str(winner))
|
LOG.debug("Paying trivia winner: %d credits --> %s", amount, str(winner))
|
||||||
await deposit_credits(winner, int(multiplier * score))
|
await bank.deposit_credits(winner, int(multiplier * score))
|
||||||
await self.ctx.send(
|
await self.ctx.send(
|
||||||
"Congratulations, {0}, you have received {1} credits"
|
_(
|
||||||
" for coming first.".format(winner.display_name, amount)
|
"Congratulations, {user}, you have received {num} {currency}"
|
||||||
|
" for coming first."
|
||||||
|
).format(
|
||||||
|
user=winner.display_name,
|
||||||
|
num=amount,
|
||||||
|
currency=await bank.get_currency_name(self.ctx.guild),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -313,9 +322,9 @@ def _parse_answers(answers):
|
|||||||
for answer in answers:
|
for answer in answers:
|
||||||
if isinstance(answer, bool):
|
if isinstance(answer, bool):
|
||||||
if answer is True:
|
if answer is True:
|
||||||
ret.extend(["True", "Yes"])
|
ret.extend(["True", "Yes", _("Yes")])
|
||||||
else:
|
else:
|
||||||
ret.extend(["False", "No"])
|
ret.extend(["False", "No", _("No")])
|
||||||
else:
|
else:
|
||||||
ret.append(str(answer))
|
ret.append(str(answer))
|
||||||
# Uniquify list
|
# Uniquify list
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import discord
|
|||||||
from redbot.core import commands
|
from redbot.core import commands
|
||||||
from redbot.core import Config, checks
|
from redbot.core import Config, checks
|
||||||
from redbot.core.data_manager import cog_data_path
|
from redbot.core.data_manager import cog_data_path
|
||||||
from redbot.core.utils.chat_formatting import box, pagify
|
from redbot.core.i18n import Translator, cog_i18n
|
||||||
|
from redbot.core.utils.chat_formatting import box, pagify, bold
|
||||||
from redbot.cogs.bank import check_global_setting_admin
|
from redbot.cogs.bank import check_global_setting_admin
|
||||||
from .log import LOG
|
from .log import LOG
|
||||||
from .session import TriviaSession
|
from .session import TriviaSession
|
||||||
@ -16,6 +17,8 @@ __all__ = ["Trivia", "UNIQUE_ID", "get_core_lists"]
|
|||||||
|
|
||||||
UNIQUE_ID = 0xB3C0E453
|
UNIQUE_ID = 0xB3C0E453
|
||||||
|
|
||||||
|
_ = Translator("Trivia", __file__)
|
||||||
|
|
||||||
|
|
||||||
class InvalidListError(Exception):
|
class InvalidListError(Exception):
|
||||||
"""A Trivia list file is in invalid format."""
|
"""A Trivia list file is in invalid format."""
|
||||||
@ -23,6 +26,7 @@ class InvalidListError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cog_i18n(_)
|
||||||
class Trivia(commands.Cog):
|
class Trivia(commands.Cog):
|
||||||
"""Play trivia with friends!"""
|
"""Play trivia with friends!"""
|
||||||
|
|
||||||
@ -47,20 +51,21 @@ class Trivia(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.mod_or_permissions(administrator=True)
|
@checks.mod_or_permissions(administrator=True)
|
||||||
async def triviaset(self, ctx: commands.Context):
|
async def triviaset(self, ctx: commands.Context):
|
||||||
"""Manage trivia settings."""
|
"""Manage Trivia settings."""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
settings_dict = await settings.all()
|
settings_dict = await settings.all()
|
||||||
msg = box(
|
msg = box(
|
||||||
"**Current settings**\n"
|
_(
|
||||||
"Bot gains points: {bot_plays}\n"
|
"**Current settings**\n"
|
||||||
"Answer time limit: {delay} seconds\n"
|
"Bot gains points: {bot_plays}\n"
|
||||||
"Lack of response timeout: {timeout} seconds\n"
|
"Answer time limit: {delay} seconds\n"
|
||||||
"Points to win: {max_score}\n"
|
"Lack of response timeout: {timeout} seconds\n"
|
||||||
"Reveal answer on timeout: {reveal_answer}\n"
|
"Points to win: {max_score}\n"
|
||||||
"Payout multiplier: {payout_multiplier}\n"
|
"Reveal answer on timeout: {reveal_answer}\n"
|
||||||
"Allow lists to override settings: {allow_override}"
|
"Payout multiplier: {payout_multiplier}\n"
|
||||||
"".format(**settings_dict),
|
"Allow lists to override settings: {allow_override}"
|
||||||
|
).format(**settings_dict),
|
||||||
lang="py",
|
lang="py",
|
||||||
)
|
)
|
||||||
await ctx.send(msg)
|
await ctx.send(msg)
|
||||||
@ -69,33 +74,34 @@ class Trivia(commands.Cog):
|
|||||||
async def triviaset_max_score(self, ctx: commands.Context, score: int):
|
async def triviaset_max_score(self, ctx: commands.Context, score: int):
|
||||||
"""Set the total points required to win."""
|
"""Set the total points required to win."""
|
||||||
if score < 0:
|
if score < 0:
|
||||||
await ctx.send("Score must be greater than 0.")
|
await ctx.send(_("Score must be greater than 0."))
|
||||||
return
|
return
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
await settings.max_score.set(score)
|
await settings.max_score.set(score)
|
||||||
await ctx.send("Done. Points required to win set to {}.".format(score))
|
await ctx.send(_("Done. Points required to win set to {num}.").format(num=score))
|
||||||
|
|
||||||
@triviaset.command(name="timelimit")
|
@triviaset.command(name="timelimit")
|
||||||
async def triviaset_timelimit(self, ctx: commands.Context, seconds: float):
|
async def triviaset_timelimit(self, ctx: commands.Context, seconds: float):
|
||||||
"""Set the maximum seconds permitted to answer a question."""
|
"""Set the maximum seconds permitted to answer a question."""
|
||||||
if seconds < 4.0:
|
if seconds < 4.0:
|
||||||
await ctx.send("Must be at least 4 seconds.")
|
await ctx.send(_("Must be at least 4 seconds."))
|
||||||
return
|
return
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
await settings.delay.set(seconds)
|
await settings.delay.set(seconds)
|
||||||
await ctx.send("Done. Maximum seconds to answer set to {}.".format(seconds))
|
await ctx.send(_("Done. Maximum seconds to answer set to {num}.").format(num=seconds))
|
||||||
|
|
||||||
@triviaset.command(name="stopafter")
|
@triviaset.command(name="stopafter")
|
||||||
async def triviaset_stopafter(self, ctx: commands.Context, seconds: float):
|
async def triviaset_stopafter(self, ctx: commands.Context, seconds: float):
|
||||||
"""Set how long until trivia stops due to no response."""
|
"""Set how long until trivia stops due to no response."""
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
if seconds < await settings.delay():
|
if seconds < await settings.delay():
|
||||||
await ctx.send("Must be larger than the answer time limit.")
|
await ctx.send(_("Must be larger than the answer time limit."))
|
||||||
return
|
return
|
||||||
await settings.timeout.set(seconds)
|
await settings.timeout.set(seconds)
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Done. Trivia sessions will now time out after {}"
|
_(
|
||||||
" seconds of no responses.".format(seconds)
|
"Done. Trivia sessions will now time out after {num} seconds of no responses."
|
||||||
|
).format(num=seconds)
|
||||||
)
|
)
|
||||||
|
|
||||||
@triviaset.command(name="override")
|
@triviaset.command(name="override")
|
||||||
@ -103,46 +109,46 @@ class Trivia(commands.Cog):
|
|||||||
"""Allow/disallow trivia lists to override settings."""
|
"""Allow/disallow trivia lists to override settings."""
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
await settings.allow_override.set(enabled)
|
await settings.allow_override.set(enabled)
|
||||||
enabled = "now" if enabled else "no longer"
|
if enabled:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Done. Trivia lists can {} override the trivia settings"
|
_(
|
||||||
" for this server.".format(enabled)
|
"Done. Trivia lists can now override the trivia settings for this server."
|
||||||
)
|
).format(now=enabled)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await ctx.send(
|
||||||
|
_(
|
||||||
|
"Done. Trivia lists can no longer override the trivia settings for this "
|
||||||
|
"server."
|
||||||
|
).format(now=enabled)
|
||||||
|
)
|
||||||
|
|
||||||
@triviaset.command(name="botplays")
|
@triviaset.command(name="botplays", usage="<true_or_false>")
|
||||||
async def trivaset_bot_plays(self, ctx: commands.Context, true_or_false: bool):
|
async def trivaset_bot_plays(self, ctx: commands.Context, enabled: bool):
|
||||||
"""Set whether or not the bot gains points.
|
"""Set whether or not the bot gains points.
|
||||||
|
|
||||||
If enabled, the bot will gain a point if no one guesses correctly.
|
If enabled, the bot will gain a point if no one guesses correctly.
|
||||||
"""
|
"""
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
await settings.bot_plays.set(true_or_false)
|
await settings.bot_plays.set(enabled)
|
||||||
await ctx.send(
|
if enabled:
|
||||||
"Done. "
|
await ctx.send(_("Done. I'll now gain a point if users don't answer in time."))
|
||||||
+ (
|
else:
|
||||||
"I'll gain a point if users don't answer in time."
|
await ctx.send(_("Alright, I won't embarass you at trivia anymore."))
|
||||||
if true_or_false
|
|
||||||
else "Alright, I won't embarass you at trivia anymore."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@triviaset.command(name="revealanswer")
|
@triviaset.command(name="revealanswer", usage="<true_or_false>")
|
||||||
async def trivaset_reveal_answer(self, ctx: commands.Context, true_or_false: bool):
|
async def trivaset_reveal_answer(self, ctx: commands.Context, enabled: bool):
|
||||||
"""Set whether or not the answer is revealed.
|
"""Set whether or not the answer is revealed.
|
||||||
|
|
||||||
If enabled, the bot will reveal the answer if no one guesses correctly
|
If enabled, the bot will reveal the answer if no one guesses correctly
|
||||||
in time.
|
in time.
|
||||||
"""
|
"""
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
await settings.reveal_answer.set(true_or_false)
|
await settings.reveal_answer.set(enabled)
|
||||||
await ctx.send(
|
if enabled:
|
||||||
"Done. "
|
await ctx.send(_("Done. I'll reveal the answer if no one knows it."))
|
||||||
+ (
|
else:
|
||||||
"I'll reveal the answer if no one knows it."
|
await ctx.send(_("Alright, I won't reveal the answer to the questions anymore."))
|
||||||
if true_or_false
|
|
||||||
else "I won't reveal the answer to the questions anymore."
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@triviaset.command(name="payout")
|
@triviaset.command(name="payout")
|
||||||
@check_global_setting_admin()
|
@check_global_setting_admin()
|
||||||
@ -158,13 +164,13 @@ class Trivia(commands.Cog):
|
|||||||
"""
|
"""
|
||||||
settings = self.conf.guild(ctx.guild)
|
settings = self.conf.guild(ctx.guild)
|
||||||
if multiplier < 0:
|
if multiplier < 0:
|
||||||
await ctx.send("Multiplier must be at least 0.")
|
await ctx.send(_("Multiplier must be at least 0."))
|
||||||
return
|
return
|
||||||
await settings.payout_multiplier.set(multiplier)
|
await settings.payout_multiplier.set(multiplier)
|
||||||
if not multiplier:
|
if multiplier:
|
||||||
await ctx.send("Done. I will no longer reward the winner with a payout.")
|
await ctx.send(_("Done. Payout multiplier set to {num}.").format(num=multiplier))
|
||||||
return
|
else:
|
||||||
await ctx.send("Done. Payout multiplier set to {}.".format(multiplier))
|
await ctx.send(_("Done. I will no longer reward the winner with a payout."))
|
||||||
|
|
||||||
@commands.group(invoke_without_command=True)
|
@commands.group(invoke_without_command=True)
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@ -180,7 +186,7 @@ class Trivia(commands.Cog):
|
|||||||
categories = [c.lower() for c in categories]
|
categories = [c.lower() for c in categories]
|
||||||
session = self._get_trivia_session(ctx.channel)
|
session = self._get_trivia_session(ctx.channel)
|
||||||
if session is not None:
|
if session is not None:
|
||||||
await ctx.send("There is already an ongoing trivia session in this channel.")
|
await ctx.send(_("There is already an ongoing trivia session in this channel."))
|
||||||
return
|
return
|
||||||
trivia_dict = {}
|
trivia_dict = {}
|
||||||
authors = []
|
authors = []
|
||||||
@ -191,15 +197,17 @@ class Trivia(commands.Cog):
|
|||||||
dict_ = self.get_trivia_list(category)
|
dict_ = self.get_trivia_list(category)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Invalid category `{0}`. See `{1}trivia list`"
|
_(
|
||||||
" for a list of trivia categories."
|
"Invalid category `{name}`. See `{prefix}trivia list` for a list of "
|
||||||
"".format(category, ctx.prefix)
|
"trivia categories."
|
||||||
|
).format(name=category, prefix=ctx.prefix)
|
||||||
)
|
)
|
||||||
except InvalidListError:
|
except InvalidListError:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"There was an error parsing the trivia list for"
|
_(
|
||||||
" the `{}` category. It may be formatted"
|
"There was an error parsing the trivia list for the `{name}` category. It "
|
||||||
" incorrectly.".format(category)
|
"may be formatted incorrectly."
|
||||||
|
).format(name=category)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
trivia_dict.update(dict_)
|
trivia_dict.update(dict_)
|
||||||
@ -208,7 +216,7 @@ class Trivia(commands.Cog):
|
|||||||
return
|
return
|
||||||
if not trivia_dict:
|
if not trivia_dict:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"The trivia list was parsed successfully, however it appears to be empty!"
|
_("The trivia list was parsed successfully, however it appears to be empty!")
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
settings = await self.conf.guild(ctx.guild).all()
|
settings = await self.conf.guild(ctx.guild).all()
|
||||||
@ -225,7 +233,7 @@ class Trivia(commands.Cog):
|
|||||||
"""Stop an ongoing trivia session."""
|
"""Stop an ongoing trivia session."""
|
||||||
session = self._get_trivia_session(ctx.channel)
|
session = self._get_trivia_session(ctx.channel)
|
||||||
if session is None:
|
if session is None:
|
||||||
await ctx.send("There is no ongoing trivia session in this channel.")
|
await ctx.send(_("There is no ongoing trivia session in this channel."))
|
||||||
return
|
return
|
||||||
author = ctx.author
|
author = ctx.author
|
||||||
auth_checks = (
|
auth_checks = (
|
||||||
@ -238,20 +246,28 @@ class Trivia(commands.Cog):
|
|||||||
if any(auth_checks):
|
if any(auth_checks):
|
||||||
await session.end_game()
|
await session.end_game()
|
||||||
session.force_stop()
|
session.force_stop()
|
||||||
await ctx.send("Trivia stopped.")
|
await ctx.send(_("Trivia stopped."))
|
||||||
else:
|
else:
|
||||||
await ctx.send("You are not allowed to do that.")
|
await ctx.send(_("You are not allowed to do that."))
|
||||||
|
|
||||||
@trivia.command(name="list")
|
@trivia.command(name="list")
|
||||||
async def trivia_list(self, ctx: commands.Context):
|
async def trivia_list(self, ctx: commands.Context):
|
||||||
"""List available trivia categories."""
|
"""List available trivia categories."""
|
||||||
lists = set(p.stem for p in self._all_lists())
|
lists = set(p.stem for p in self._all_lists())
|
||||||
|
if await ctx.embed_requested():
|
||||||
msg = box("**Available trivia lists**\n\n{}".format(", ".join(sorted(lists))))
|
await ctx.send(
|
||||||
if len(msg) > 1000:
|
embed=discord.Embed(
|
||||||
await ctx.author.send(msg)
|
title=_("Available trivia lists"),
|
||||||
return
|
colour=await ctx.embed_colour(),
|
||||||
await ctx.send(msg)
|
description=", ".join(sorted(lists)),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
msg = box(bold(_("Available trivia lists")) + "\n\n" + ", ".join(sorted(lists)))
|
||||||
|
if len(msg) > 1000:
|
||||||
|
await ctx.author.send(msg)
|
||||||
|
else:
|
||||||
|
await ctx.send(msg)
|
||||||
|
|
||||||
@trivia.group(name="leaderboard", aliases=["lboard"], autohelp=False)
|
@trivia.group(name="leaderboard", aliases=["lboard"], autohelp=False)
|
||||||
async def trivia_leaderboard(self, ctx: commands.Context):
|
async def trivia_leaderboard(self, ctx: commands.Context):
|
||||||
@ -273,19 +289,21 @@ class Trivia(commands.Cog):
|
|||||||
):
|
):
|
||||||
"""Leaderboard for this server.
|
"""Leaderboard for this server.
|
||||||
|
|
||||||
<sort_by> can be any of the following fields:
|
`<sort_by>` can be any of the following fields:
|
||||||
- wins : total wins
|
- `wins` : total wins
|
||||||
- avg : average score
|
- `avg` : average score
|
||||||
- total : total correct answers
|
- `total` : total correct answers
|
||||||
|
- `games` : total games played
|
||||||
|
|
||||||
<top> is the number of ranks to show on the leaderboard.
|
`<top>` is the number of ranks to show on the leaderboard.
|
||||||
"""
|
"""
|
||||||
key = self._get_sort_key(sort_by)
|
key = self._get_sort_key(sort_by)
|
||||||
if key is None:
|
if key is None:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Unknown field `{}`, see `{}help trivia "
|
_(
|
||||||
"leaderboard server` for valid fields to sort by."
|
"Unknown field `{field_name}`, see `{prefix}help trivia leaderboard server` "
|
||||||
"".format(sort_by, ctx.prefix)
|
"for valid fields to sort by."
|
||||||
|
).format(field_name=sort_by, prefix=ctx.prefix)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
@ -300,20 +318,21 @@ class Trivia(commands.Cog):
|
|||||||
):
|
):
|
||||||
"""Global trivia leaderboard.
|
"""Global trivia leaderboard.
|
||||||
|
|
||||||
<sort_by> can be any of the following fields:
|
`<sort_by>` can be any of the following fields:
|
||||||
- wins : total wins
|
- `wins` : total wins
|
||||||
- avg : average score
|
- `avg` : average score
|
||||||
- total : total correct answers from all sessions
|
- `total` : total correct answers from all sessions
|
||||||
- games : total games played
|
- `games` : total games played
|
||||||
|
|
||||||
<top> is the number of ranks to show on the leaderboard.
|
`<top>` is the number of ranks to show on the leaderboard.
|
||||||
"""
|
"""
|
||||||
key = self._get_sort_key(sort_by)
|
key = self._get_sort_key(sort_by)
|
||||||
if key is None:
|
if key is None:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
"Unknown field `{}`, see `{}help trivia "
|
_(
|
||||||
"leaderboard global` for valid fields to sort by."
|
"Unknown field `{field_name}`, see `{prefix}help trivia leaderboard server` "
|
||||||
"".format(sort_by, ctx.prefix)
|
"for valid fields to sort by."
|
||||||
|
).format(field_name=sort_by, prefix=ctx.prefix)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
data = await self.conf.all_members()
|
data = await self.conf.all_members()
|
||||||
@ -365,7 +384,7 @@ class Trivia(commands.Cog):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
if not data:
|
if not data:
|
||||||
await ctx.send("There are no scores on record!")
|
await ctx.send(_("There are no scores on record!"))
|
||||||
return
|
return
|
||||||
leaderboard = self._get_leaderboard(data, key, top)
|
leaderboard = self._get_leaderboard(data, key, top)
|
||||||
ret = []
|
ret = []
|
||||||
@ -386,7 +405,7 @@ class Trivia(commands.Cog):
|
|||||||
try:
|
try:
|
||||||
priority.remove(key)
|
priority.remove(key)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ValueError("{} is not a valid key.".format(key))
|
raise ValueError(f"{key} is not a valid key.")
|
||||||
# Put key last in reverse priority
|
# Put key last in reverse priority
|
||||||
priority.append(key)
|
priority.append(key)
|
||||||
items = data.items()
|
items = data.items()
|
||||||
@ -395,16 +414,15 @@ class Trivia(commands.Cog):
|
|||||||
max_name_len = max(map(lambda m: len(str(m)), data.keys()))
|
max_name_len = max(map(lambda m: len(str(m)), data.keys()))
|
||||||
# Headers
|
# Headers
|
||||||
headers = (
|
headers = (
|
||||||
"Rank",
|
_("Rank"),
|
||||||
"Member{}".format(" " * (max_name_len - 6)),
|
_("Member") + " " * (max_name_len - 6),
|
||||||
"Wins",
|
_("Wins"),
|
||||||
"Games Played",
|
_("Games Played"),
|
||||||
"Total Score",
|
_("Total Score"),
|
||||||
"Average Score",
|
_("Average Score"),
|
||||||
)
|
)
|
||||||
lines = [" | ".join(headers)]
|
lines = [" | ".join(headers), " | ".join(("-" * len(h) for h in headers))]
|
||||||
# Header underlines
|
# Header underlines
|
||||||
lines.append(" | ".join(("-" * len(h) for h in headers)))
|
|
||||||
for rank, tup in enumerate(items, 1):
|
for rank, tup in enumerate(items, 1):
|
||||||
member, m_data = tup
|
member, m_data = tup
|
||||||
# Align fields to header width
|
# Align fields to header width
|
||||||
|
|||||||
@ -22,7 +22,7 @@ _ = Translator("Warnings", __file__)
|
|||||||
|
|
||||||
@cog_i18n(_)
|
@cog_i18n(_)
|
||||||
class Warnings(commands.Cog):
|
class Warnings(commands.Cog):
|
||||||
"""A warning system for Red"""
|
"""Warn misbehaving users and take automated actions."""
|
||||||
|
|
||||||
default_guild = {"actions": [], "reasons": {}, "allow_custom_reasons": False}
|
default_guild = {"actions": [], "reasons": {}, "allow_custom_reasons": False}
|
||||||
|
|
||||||
@ -48,31 +48,42 @@ class Warnings(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def warningset(self, ctx: commands.Context):
|
async def warningset(self, ctx: commands.Context):
|
||||||
"""Warning settings"""
|
"""Manage settings for Warnings."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@warningset.command()
|
@warningset.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def allowcustomreasons(self, ctx: commands.Context, allowed: bool):
|
async def allowcustomreasons(self, ctx: commands.Context, allowed: bool):
|
||||||
"""Enable or Disable custom reasons for a warning"""
|
"""Enable or disable custom reasons for a warning."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
await self.config.guild(guild).allow_custom_reasons.set(allowed)
|
await self.config.guild(guild).allow_custom_reasons.set(allowed)
|
||||||
await ctx.send(
|
if allowed:
|
||||||
_("Custom reasons have been {}.").format(_("enabled") if allowed else _("disabled"))
|
await ctx.send(_("Custom reasons have been enabled."))
|
||||||
)
|
else:
|
||||||
|
await ctx.send(_("Custom reasons have been disabled."))
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def warnaction(self, ctx: commands.Context):
|
async def warnaction(self, ctx: commands.Context):
|
||||||
"""Action management"""
|
"""Manage automated actions for Warnings.
|
||||||
|
|
||||||
|
Actions are essentially command macros. Any command can be run
|
||||||
|
when the action is initially triggered, and/or when the action
|
||||||
|
is lifted.
|
||||||
|
|
||||||
|
Actions must be given a name and a points threshold. When a
|
||||||
|
user is warned enough so that their points go over this
|
||||||
|
threshold, the action will be executed.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@warnaction.command(name="add")
|
@warnaction.command(name="add")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def action_add(self, ctx: commands.Context, name: str, points: int):
|
async def action_add(self, ctx: commands.Context, name: str, points: int):
|
||||||
"""Create an action to be taken at a specified point count
|
"""Create an automated action.
|
||||||
Duplicate action names are not allowed
|
|
||||||
|
Duplicate action names are not allowed.
|
||||||
"""
|
"""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
@ -103,7 +114,7 @@ class Warnings(commands.Cog):
|
|||||||
@warnaction.command(name="del")
|
@warnaction.command(name="del")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def action_del(self, ctx: commands.Context, action_name: str):
|
async def action_del(self, ctx: commands.Context, action_name: str):
|
||||||
"""Delete the point count action with the specified name"""
|
"""Delete the action with the specified name."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
guild_settings = self.config.guild(guild)
|
guild_settings = self.config.guild(guild)
|
||||||
async with guild_settings.actions() as registered_actions:
|
async with guild_settings.actions() as registered_actions:
|
||||||
@ -116,23 +127,29 @@ class Warnings(commands.Cog):
|
|||||||
registered_actions.remove(to_remove)
|
registered_actions.remove(to_remove)
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
else:
|
else:
|
||||||
await ctx.send(_("No action named {} exists!").format(action_name))
|
await ctx.send(_("No action named {name} exists!").format(name=action_name))
|
||||||
|
|
||||||
@commands.group()
|
@commands.group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.guildowner_or_permissions(administrator=True)
|
@checks.guildowner_or_permissions(administrator=True)
|
||||||
async def warnreason(self, ctx: commands.Context):
|
async def warnreason(self, ctx: commands.Context):
|
||||||
"""Add reasons for warnings"""
|
"""Manage warning reasons.
|
||||||
|
|
||||||
|
Reasons must be given a name, description and points value. The
|
||||||
|
name of the reason must be given when a user is warned.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@warnreason.command(name="add")
|
@warnreason.command(name="create", aliases=["add"])
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def reason_add(self, ctx: commands.Context, name: str, points: int, *, description: str):
|
async def reason_create(
|
||||||
"""Add a reason to be available for warnings"""
|
self, ctx: commands.Context, name: str, points: int, *, description: str
|
||||||
|
):
|
||||||
|
"""Create a warning reason."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
|
|
||||||
if name.lower() == "custom":
|
if name.lower() == "custom":
|
||||||
await ctx.send("That cannot be used as a reason name!")
|
await ctx.send(_("*Custom* cannot be used as a reason name!"))
|
||||||
return
|
return
|
||||||
to_add = {"points": points, "description": description}
|
to_add = {"points": points, "description": description}
|
||||||
completed = {name.lower(): to_add}
|
completed = {name.lower(): to_add}
|
||||||
@ -142,12 +159,12 @@ class Warnings(commands.Cog):
|
|||||||
async with guild_settings.reasons() as registered_reasons:
|
async with guild_settings.reasons() as registered_reasons:
|
||||||
registered_reasons.update(completed)
|
registered_reasons.update(completed)
|
||||||
|
|
||||||
await ctx.send(_("That reason has been registered."))
|
await ctx.send(_("The new reason has been registered."))
|
||||||
|
|
||||||
@warnreason.command(name="del")
|
@warnreason.command(name="del", aliases=["remove"])
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def reason_del(self, ctx: commands.Context, reason_name: str):
|
async def reason_del(self, ctx: commands.Context, reason_name: str):
|
||||||
"""Delete the reason with the specified name"""
|
"""Delete a warning reason."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
guild_settings = self.config.guild(guild)
|
guild_settings = self.config.guild(guild)
|
||||||
async with guild_settings.reasons() as registered_reasons:
|
async with guild_settings.reasons() as registered_reasons:
|
||||||
@ -160,7 +177,7 @@ class Warnings(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def reasonlist(self, ctx: commands.Context):
|
async def reasonlist(self, ctx: commands.Context):
|
||||||
"""List all configured reasons for warnings"""
|
"""List all configured reasons for Warnings."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
guild_settings = self.config.guild(guild)
|
guild_settings = self.config.guild(guild)
|
||||||
msg_list = []
|
msg_list = []
|
||||||
@ -174,9 +191,9 @@ class Warnings(commands.Cog):
|
|||||||
msg_list.append(em)
|
msg_list.append(em)
|
||||||
else:
|
else:
|
||||||
msg_list.append(
|
msg_list.append(
|
||||||
"Name: {}\nPoints: {}\nDescription: {}".format(
|
_(
|
||||||
r, v["points"], v["description"]
|
"Name: {reason_name}\nPoints: {points}\nDescription: {description}"
|
||||||
)
|
).format(reason_name=r, **v)
|
||||||
)
|
)
|
||||||
if msg_list:
|
if msg_list:
|
||||||
await menu(ctx, msg_list, DEFAULT_CONTROLS)
|
await menu(ctx, msg_list, DEFAULT_CONTROLS)
|
||||||
@ -187,7 +204,7 @@ class Warnings(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def actionlist(self, ctx: commands.Context):
|
async def actionlist(self, ctx: commands.Context):
|
||||||
"""List the actions to be taken at specific point values"""
|
"""List all configured automated actions for Warnings."""
|
||||||
guild = ctx.guild
|
guild = ctx.guild
|
||||||
guild_settings = self.config.guild(guild)
|
guild_settings = self.config.guild(guild)
|
||||||
msg_list = []
|
msg_list = []
|
||||||
@ -201,10 +218,10 @@ class Warnings(commands.Cog):
|
|||||||
msg_list.append(em)
|
msg_list.append(em)
|
||||||
else:
|
else:
|
||||||
msg_list.append(
|
msg_list.append(
|
||||||
"Name: {}\nPoints: {}\nExceed command: {}\n"
|
_(
|
||||||
"Drop command: {}".format(
|
"Name: {action_name}\nPoints: {points}\n"
|
||||||
r["action_name"], r["points"], r["exceed_command"], r["drop_command"]
|
"Exceed command: {exceed_command}\nDrop command: {drop_command}"
|
||||||
)
|
).format(**r)
|
||||||
)
|
)
|
||||||
if msg_list:
|
if msg_list:
|
||||||
await menu(ctx, msg_list, DEFAULT_CONTROLS)
|
await menu(ctx, msg_list, DEFAULT_CONTROLS)
|
||||||
@ -215,8 +232,10 @@ class Warnings(commands.Cog):
|
|||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def warn(self, ctx: commands.Context, user: discord.Member, reason: str):
|
async def warn(self, ctx: commands.Context, user: discord.Member, reason: str):
|
||||||
"""Warn the user for the specified reason
|
"""Warn the user for the specified reason.
|
||||||
Reason must be a registered reason, or "custom" if custom reasons are allowed
|
|
||||||
|
`<reason>` must be a registered reason name, or *custom* if
|
||||||
|
custom reasons are enabled.
|
||||||
"""
|
"""
|
||||||
if user == ctx.author:
|
if user == ctx.author:
|
||||||
await ctx.send(_("You cannot warn yourself."))
|
await ctx.send(_("You cannot warn yourself."))
|
||||||
@ -226,9 +245,9 @@ class Warnings(commands.Cog):
|
|||||||
if not custom_allowed:
|
if not custom_allowed:
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
_(
|
_(
|
||||||
"Custom reasons are not allowed! Please see {} for "
|
"Custom reasons are not allowed! Please see `{prefix}reasonlist` for "
|
||||||
"a complete list of valid reasons."
|
"a complete list of valid reasons."
|
||||||
).format("`{}reasonlist`".format(ctx.prefix))
|
).format(prefix=ctx.prefix)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
reason_type = await self.custom_warning_reason(ctx)
|
reason_type = await self.custom_warning_reason(ctx)
|
||||||
@ -272,9 +291,7 @@ class Warnings(commands.Cog):
|
|||||||
await warning_points_add_check(self.config, ctx, user, current_point_count)
|
await warning_points_add_check(self.config, ctx, user, current_point_count)
|
||||||
try:
|
try:
|
||||||
em = discord.Embed(
|
em = discord.Embed(
|
||||||
title=_("Warning from {mod_name}#{mod_discrim}").format(
|
title=_("Warning from {user}").format(user=ctx.author),
|
||||||
mod_name=ctx.author.display_name, mod_discrim=ctx.author.discriminator
|
|
||||||
),
|
|
||||||
description=reason_type["description"],
|
description=reason_type["description"],
|
||||||
)
|
)
|
||||||
em.add_field(name=_("Points"), value=str(reason_type["points"]))
|
em.add_field(name=_("Points"), value=str(reason_type["points"]))
|
||||||
@ -286,19 +303,17 @@ class Warnings(commands.Cog):
|
|||||||
)
|
)
|
||||||
except discord.HTTPException:
|
except discord.HTTPException:
|
||||||
pass
|
pass
|
||||||
await ctx.send(
|
await ctx.send(_("User {user} has been warned.").format(user=user))
|
||||||
_("User {user_name}#{user_discrim} has been warned.").format(
|
|
||||||
user_name=user.display_name, user_discrim=user.discriminator
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def warnings(self, ctx: commands.Context, userid: int = None):
|
async def warnings(self, ctx: commands.Context, userid: int = None):
|
||||||
"""Show warnings for the specified user.
|
"""List the warnings for the specified user.
|
||||||
If userid is None, show warnings for the person running the command
|
|
||||||
|
Emit `<userid>` to see your own warnings.
|
||||||
|
|
||||||
Note that showing warnings for users other than yourself requires
|
Note that showing warnings for users other than yourself requires
|
||||||
appropriate permissions
|
appropriate permissions.
|
||||||
"""
|
"""
|
||||||
if userid is None:
|
if userid is None:
|
||||||
user = ctx.author
|
user = ctx.author
|
||||||
@ -326,18 +341,24 @@ class Warnings(commands.Cog):
|
|||||||
)
|
)
|
||||||
if mod is None:
|
if mod is None:
|
||||||
mod = await self.bot.get_user_info(user_warnings[key]["mod"])
|
mod = await self.bot.get_user_info(user_warnings[key]["mod"])
|
||||||
msg += "{} point warning {} issued by {} for {}\n".format(
|
msg += _(
|
||||||
user_warnings[key]["points"], key, mod, user_warnings[key]["description"]
|
"{num_points} point warning {reason_name} issued by {user} for "
|
||||||
|
"{description}\n"
|
||||||
|
).format(
|
||||||
|
num_points=user_warnings[key]["points"],
|
||||||
|
reason_name=key,
|
||||||
|
user=mod,
|
||||||
|
description=user_warnings[key]["description"],
|
||||||
)
|
)
|
||||||
await ctx.send_interactive(
|
await ctx.send_interactive(
|
||||||
pagify(msg, shorten_by=58), box_lang="Warnings for {}".format(user)
|
pagify(msg, shorten_by=58), box_lang=_("Warnings for {user}").format(user=user)
|
||||||
)
|
)
|
||||||
|
|
||||||
@commands.command()
|
@commands.command()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
async def unwarn(self, ctx: commands.Context, user_id: int, warn_id: str):
|
async def unwarn(self, ctx: commands.Context, user_id: int, warn_id: str):
|
||||||
"""Removes the specified warning from the user specified"""
|
"""Remove a warning from a user."""
|
||||||
if user_id == ctx.author.id:
|
if user_id == ctx.author.id:
|
||||||
await ctx.send(_("You cannot remove warnings from yourself."))
|
await ctx.send(_("You cannot remove warnings from yourself."))
|
||||||
return
|
return
|
||||||
@ -351,7 +372,7 @@ class Warnings(commands.Cog):
|
|||||||
await warning_points_remove_check(self.config, ctx, member, current_point_count)
|
await warning_points_remove_check(self.config, ctx, member, current_point_count)
|
||||||
async with member_settings.warnings() as user_warnings:
|
async with member_settings.warnings() as user_warnings:
|
||||||
if warn_id not in user_warnings.keys():
|
if warn_id not in user_warnings.keys():
|
||||||
await ctx.send("That warning doesn't exist!")
|
await ctx.send(_("That warning doesn't exist!"))
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
current_point_count -= user_warnings[warn_id]["points"]
|
current_point_count -= user_warnings[warn_id]["points"]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
from typing import Union, List
|
from typing import Union, List, Optional
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
|
|
||||||
@ -296,12 +296,20 @@ async def transfer_credits(from_: discord.Member, to: discord.Member, amount: in
|
|||||||
return await deposit_credits(to, amount)
|
return await deposit_credits(to, amount)
|
||||||
|
|
||||||
|
|
||||||
async def wipe_bank():
|
async def wipe_bank(guild: Optional[discord.Guild] = None) -> None:
|
||||||
"""Delete all accounts from the bank."""
|
"""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():
|
if await is_global():
|
||||||
await _conf.clear_all_users()
|
await _conf.clear_all_users()
|
||||||
else:
|
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]:
|
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)
|
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.
|
"""Returns a `Group` for the given user.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
import os
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Callable, Union
|
||||||
|
|
||||||
from . import commands
|
from . import commands
|
||||||
|
|
||||||
@ -113,9 +115,9 @@ def _normalize(string, remove_newline=False):
|
|||||||
ends_with_space = s[-1] in " \n\t\r"
|
ends_with_space = s[-1] in " \n\t\r"
|
||||||
if remove_newline:
|
if remove_newline:
|
||||||
newline_re = re.compile("[\r\n]+")
|
newline_re = re.compile("[\r\n]+")
|
||||||
s = " ".join(filter(bool, newline_re.split(s)))
|
s = " ".join(filter(None, newline_re.split(s)))
|
||||||
s = " ".join(filter(bool, s.split("\t")))
|
s = " ".join(filter(None, s.split("\t")))
|
||||||
s = " ".join(filter(bool, s.split(" ")))
|
s = " ".join(filter(None, s.split(" ")))
|
||||||
if starts_with_space:
|
if starts_with_space:
|
||||||
s = " " + s
|
s = " " + s
|
||||||
if ends_with_space:
|
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)
|
return cog_folder / "locales" / "{}.{}".format(get_locale(), extension)
|
||||||
|
|
||||||
|
|
||||||
class Translator:
|
class Translator(Callable[[str], str]):
|
||||||
"""Function to get translated strings at runtime."""
|
"""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.
|
Initializes an internationalization object.
|
||||||
|
|
||||||
@ -173,7 +175,7 @@ class Translator:
|
|||||||
|
|
||||||
self.load_translations()
|
self.load_translations()
|
||||||
|
|
||||||
def __call__(self, untranslated: str):
|
def __call__(self, untranslated: str) -> str:
|
||||||
"""Translate the given string.
|
"""Translate the given string.
|
||||||
|
|
||||||
This will look for the string in the translator's :code:`.pot` file,
|
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 io
|
||||||
import sys
|
import sys
|
||||||
import weakref
|
import weakref
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
from .common_filters import filter_mass_mentions
|
from .common_filters import filter_mass_mentions
|
||||||
|
|
||||||
_instances = weakref.WeakValueDictionary({})
|
_instances = weakref.WeakValueDictionary({})
|
||||||
@ -86,7 +86,11 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def message_forwarder(
|
async def message_forwarder(
|
||||||
*, destination: discord.abc.Messageable, content: str = None, embed=None, files=[]
|
*,
|
||||||
|
destination: discord.abc.Messageable,
|
||||||
|
content: str = None,
|
||||||
|
embed=None,
|
||||||
|
files: Optional[List[discord.File]] = None
|
||||||
) -> List[discord.Message]:
|
) -> List[discord.Message]:
|
||||||
"""
|
"""
|
||||||
This does the actual sending, use this instead of a full tunnel
|
This does the actual sending, use this instead of a full tunnel
|
||||||
@ -95,19 +99,19 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
destination: `discord.abc.Messageable`
|
destination: discord.abc.Messageable
|
||||||
Where to send
|
Where to send
|
||||||
content: `str`
|
content: str
|
||||||
The message content
|
The message content
|
||||||
embed: `discord.Embed`
|
embed: discord.Embed
|
||||||
The embed to send
|
The embed to send
|
||||||
files: `list` of `discord.File`
|
files: Optional[List[discord.File]]
|
||||||
A list of files to send.
|
A list of files to send.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
list of `discord.Message`
|
List[discord.Message]
|
||||||
The `discord.Message`\ (s) sent as a result
|
The messages sent as a result.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
------
|
------
|
||||||
@ -117,7 +121,6 @@ class Tunnel(metaclass=TunnelMeta):
|
|||||||
see `discord.abc.Messageable.send`
|
see `discord.abc.Messageable.send`
|
||||||
"""
|
"""
|
||||||
rets = []
|
rets = []
|
||||||
files = files if files else None
|
|
||||||
if content:
|
if content:
|
||||||
for page in pagify(content):
|
for page in pagify(content):
|
||||||
rets.append(await destination.send(page, files=files, embed=embed))
|
rets.append(await destination.send(page, files=files, embed=embed))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user