[i18n] Pass over economy, filter, general, image, mod

Signed-off-by: Toby Harradine <tobyharradine@gmail.com>
This commit is contained in:
Toby Harradine 2018-10-05 15:07:56 +10:00
parent 0c3d8af8f4
commit fa692ccc0b
15 changed files with 720 additions and 640 deletions

View File

@ -11,41 +11,40 @@ from .converters import MemberDefaultAuthor, SelfRole
log = logging.getLogger("red.admin")
_ = Translator("Admin", __file__)
T_ = Translator("Admin", __file__)
# The following are all lambdas to allow us to fetch the translation
# during runtime, without having to copy the large strings everywhere
# in the code.
generic_forbidden = lambda: _(
_ = lambda s: s
GENERIC_FORBIDDEN = _(
"I attempted to do something that Discord denied me permissions for."
" Your command failed to successfully complete."
)
hierarchy_issue = lambda: _(
HIERARCHY_ISSUE = _(
"I tried to add {role.name} to {member.display_name} but that role"
" is higher than my highest role in the Discord hierarchy so I was"
" unable to successfully add it. Please give me a higher role and "
"try again."
)
user_hierarchy_issue = lambda: _(
USER_HIERARCHY_ISSUE = _(
"I tried to add {role.name} to {member.display_name} but that role"
" is higher than your highest role in the Discord hierarchy so I was"
" unable to successfully add it. Please get a higher role and "
"try again."
)
running_announcement = lambda: _(
RUNNING_ANNOUNCEMENT = _(
"I am already announcing something. If you would like to make a"
" different announcement please use `{prefix}announce cancel`"
" first."
)
_ = T_
@cog_i18n(_)
class Admin(commands.Cog):
"""A collection of server administration utilities."""
def __init__(self, config=Config):
super().__init__()
self.conf = config.get_conf(self, 8237492837454039, force_registration=True)
@ -105,9 +104,9 @@ class Admin(commands.Cog):
await member.add_roles(role)
except discord.Forbidden:
if not self.pass_hierarchy_check(ctx, role):
await self.complain(ctx, hierarchy_issue(), role=role, member=member)
await self.complain(ctx, T_(HIERARCHY_ISSUE), role=role, member=member)
else:
await self.complain(ctx, generic_forbidden())
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
else:
await ctx.send(
_("I successfully added {role.name} to {member.display_name}").format(
@ -120,9 +119,9 @@ class Admin(commands.Cog):
await member.remove_roles(role)
except discord.Forbidden:
if not self.pass_hierarchy_check(ctx, role):
await self.complain(ctx, hierarchy_issue(), role=role, member=member)
await self.complain(ctx, T_(HIERARCHY_ISSUE), role=role, member=member)
else:
await self.complain(ctx, generic_forbidden())
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
else:
await ctx.send(
_("I successfully removed {role.name} from {member.display_name}").format(
@ -146,7 +145,7 @@ class Admin(commands.Cog):
# noinspection PyTypeChecker
await self._addrole(ctx, user, rolename)
else:
await self.complain(ctx, user_hierarchy_issue(), member=ctx.author, role=rolename)
await self.complain(ctx, T_(USER_HIERARCHY_ISSUE), member=ctx.author, role=rolename)
@commands.command()
@commands.guild_only()
@ -164,7 +163,7 @@ class Admin(commands.Cog):
# noinspection PyTypeChecker
await self._removerole(ctx, user, rolename)
else:
await self.complain(ctx, user_hierarchy_issue())
await self.complain(ctx, T_(USER_HIERARCHY_ISSUE))
@commands.group()
@commands.guild_only()
@ -191,13 +190,13 @@ class Admin(commands.Cog):
reason = "{}({}) changed the colour of role '{}'".format(author.name, author.id, role.name)
if not self.pass_user_hierarchy_check(ctx, role):
await self.complain(ctx, user_hierarchy_issue())
await self.complain(ctx, T_(USER_HIERARCHY_ISSUE))
return
try:
await role.edit(reason=reason, color=value)
except discord.Forbidden:
await self.complain(ctx, generic_forbidden())
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
else:
log.info(reason)
await ctx.send(_("Done."))
@ -219,13 +218,13 @@ class Admin(commands.Cog):
)
if not self.pass_user_hierarchy_check(ctx, role):
await self.complain(ctx, user_hierarchy_issue())
await self.complain(ctx, T_(USER_HIERARCHY_ISSUE))
return
try:
await role.edit(reason=reason, name=name)
except discord.Forbidden:
await self.complain(ctx, generic_forbidden())
await self.complain(ctx, T_(GENERIC_FORBIDDEN))
else:
log.info(reason)
await ctx.send(_("Done."))
@ -243,7 +242,7 @@ class Admin(commands.Cog):
await ctx.send(_("The announcement has begun."))
else:
prefix = ctx.prefix
await self.complain(ctx, running_announcement(), prefix=prefix)
await self.complain(ctx, T_(RUNNING_ANNOUNCEMENT), prefix=prefix)
@announce.command(name="cancel")
@checks.is_owner()
@ -381,7 +380,7 @@ class Admin(commands.Cog):
serverlocked = await self.conf.serverlocked()
await self.conf.serverlocked.set(not serverlocked)
if serverlocked: # again with original logic I'm not sure of
if serverlocked:
await ctx.send(_("The bot is no longer serverlocked."))
else:
await ctx.send(_("The bot is now serverlocked."))

View File

@ -113,7 +113,8 @@ class Alias(commands.Cog):
return False
async def get_prefix(self, message: discord.Message) -> str:
"""Tries to determine what prefix is used in a message object.
"""
Tries to determine what prefix is used in a message object.
Looks to identify from longest prefix to smallest.
Will raise ValueError if no prefix is found.
@ -175,7 +176,7 @@ class Alias(commands.Cog):
@commands.group()
@commands.guild_only()
async def alias(self, ctx: commands.Context):
"""Manage per-server aliases for commands."""
"""Manage command aliases."""
pass
@alias.group(name="global")

View File

@ -14,6 +14,7 @@ import redbot.core
from redbot.core import Config, commands, checks, bank
from redbot.core.data_manager import cog_data_path
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import bold, box
from redbot.core.utils.menus import (
menu,
DEFAULT_CONTROLS,
@ -35,6 +36,7 @@ __author__ = ["aikaterna", "billy/bollo/ati"]
@cog_i18n(_)
class Audio(commands.Cog):
"""Play audio through voice channels."""
def __init__(self, bot):
super().__init__()
self.bot = bot
@ -151,8 +153,8 @@ class Audio(commands.Cog):
description=description,
)
embed.set_footer(
text="Track length: {} | Requested by: {}".format(
dur, player.current.requester
text=_("Track length: {length} | Requested by: {user}").format(
length=dur, user=player.current.requester
)
)
if (
@ -175,7 +177,7 @@ class Audio(commands.Cog):
if playing_servers > 1:
await self.bot.change_presence(
activity=discord.Activity(
name=_("music in {num} servers").format(num=playing_servers),
name=_("music in {} servers").format(playing_servers),
type=discord.ActivityType.playing,
)
)
@ -201,7 +203,7 @@ class Audio(commands.Cog):
if playing_servers > 1:
await self.bot.change_presence(
activity=discord.Activity(
name="music in {} servers".format(playing_servers),
name=_("music in {} servers").format(playing_servers),
type=discord.ActivityType.playing,
)
)
@ -247,7 +249,7 @@ class Audio(commands.Cog):
await ctx.bot.wait_for("message", timeout=15.0, check=pred)
await ctx.invoke(self.role, pred.result)
except asyncio.TimeoutError:
return await self._embed_msg(ctx, "Response timed out, try again later.")
return await self._embed_msg(ctx, _("Response timed out, try again later."))
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
await self.config.guild(ctx.guild).dj_enabled.set(not dj_enabled)
@ -282,7 +284,7 @@ class Audio(commands.Cog):
"""Set the role to use for DJ mode."""
await self.config.guild(ctx.guild).dj_role.set(role_name.id)
dj_role_obj = ctx.guild.get_role(await self.config.guild(ctx.guild).dj_role())
await self._embed_msg(ctx, "DJ role set to: {}.".format(dj_role_obj.name))
await self._embed_msg(ctx, _("DJ role set to: {role.name}.").format(role=dj_role_obj))
@audioset.command()
@checks.mod_or_permissions(administrator=True)
@ -330,7 +332,7 @@ class Audio(commands.Cog):
jarbuild = redbot.core.__version__
vote_percent = data["vote_percent"]
msg = _("```ini\n----Server Settings----\n")
msg = "----" + _("Server Settings") + "----"
if emptydc_enabled:
msg += _("Disconnect timer: [{num_seconds}]\n").format(
num_seconds=self._dynamic_time(emptydc_timer)
@ -347,7 +349,7 @@ class Audio(commands.Cog):
"Songs as status: [{status}]\n"
).format(**global_data, **data)
if thumbnail:
msg += "Thumbnails: [{0}]\n".format(thumbnail)
msg += _("Thumbnails: [{0}]\n").format(thumbnail)
if vote_percent > 0:
msg += _(
"Vote skip: [{vote_enabled}]\nSkip percentage: [{vote_percent}%]\n"
@ -356,10 +358,10 @@ class Audio(commands.Cog):
"---Lavalink Settings---\n"
"Cog version: [{version}]\n"
"Jar build: [{jarbuild}]\n"
"External server: [{use_external_lavalink}]```"
"External server: [{use_external_lavalink}]"
).format(version=__version__, jarbuild=jarbuild, **global_data)
embed = discord.Embed(colour=await ctx.embed_colour(), description=msg)
embed = discord.Embed(colour=await ctx.embed_colour(), description=box(msg, lang="ini"))
return await ctx.send(embed=embed)
@audioset.command()
@ -368,7 +370,7 @@ class Audio(commands.Cog):
"""Toggle displaying a thumbnail on audio messages."""
thumbnail = await self.config.guild(ctx.guild).thumbnail()
await self.config.guild(ctx.guild).thumbnail.set(not thumbnail)
await self._embed_msg(ctx, "Thumbnail display: {}.".format(not thumbnail))
await self._embed_msg(ctx, _("Thumbnail display: {}.").format(not thumbnail))
@audioset.command()
@checks.mod_or_permissions(administrator=True)
@ -498,11 +500,11 @@ class Audio(commands.Cog):
if self._player_check(ctx):
if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author):
return await self._embed_msg(ctx, "You need the DJ role to disconnect.")
return await self._embed_msg(ctx, _("You need the DJ role to disconnect."))
if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(
ctx, ctx.author
):
return await self._embed_msg(ctx, "There are other people listening to music.")
return await self._embed_msg(ctx, _("There are other people listening to music."))
else:
await lavalink.get_player(ctx.guild.id).stop()
return await lavalink.get_player(ctx.guild.id).disconnect()
@ -510,7 +512,7 @@ class Audio(commands.Cog):
@commands.group()
@commands.guild_only()
async def local(self, ctx):
"""Local playback options."""
"""Local playback commands."""
pass
@local.command(name="folder")
@ -527,7 +529,7 @@ class Audio(commands.Cog):
return
localtracks_folders = await self._localtracks_folders(ctx)
if not localtracks_folders:
return await self._embed_msg(ctx, "No album folders found.")
return await self._embed_msg(ctx, _("No local track folders found."))
len_folder_pages = math.ceil(len(localtracks_folders) / 5)
folder_page_list = []
for page_num in range(1, len_folder_pages + 1):
@ -573,14 +575,14 @@ class Audio(commands.Cog):
return
localtracks_folders = await self._localtracks_folders(ctx)
if not localtracks_folders:
return await self._embed_msg(ctx, "No album folders found.")
return await self._embed_msg(ctx, _("No album folders found."))
all_tracks = []
for local_folder in localtracks_folders:
folder_tracks = await self._folder_list(ctx, local_folder)
all_tracks = all_tracks + folder_tracks
search_list = await self._build_local_search_list(all_tracks, search_words)
if not search_list:
return await self._embed_msg(ctx, "No matches.")
return await self._embed_msg(ctx, _("No matches."))
await ctx.invoke(self.search, query=search_list)
async def _all_folder_tracks(self, ctx, folder):
@ -656,7 +658,7 @@ class Audio(commands.Cog):
f for f in os.listdir(os.getcwd()) if not os.path.isfile(f) if f == "localtracks"
)
if not localtracks_folder:
await self._embed_msg(ctx, "No localtracks folder.")
await self._embed_msg(ctx, _("No localtracks folder."))
return False
else:
return True
@ -772,7 +774,7 @@ class Audio(commands.Cog):
command = ctx.invoked_with
if not player.current:
return await self._embed_msg(ctx, "Nothing playing.")
return await self._embed_msg(ctx, _("Nothing playing."))
if "localtracks/" in player.current.uri:
description = "**{}**\n{}".format(
player.current.title, player.current.uri.replace("localtracks/", "")
@ -988,7 +990,9 @@ class Audio(commands.Cog):
if track_list and len(to_append) == 1 and to_append[0] in track_list:
return await self._embed_msg(
ctx,
"{} already in {}.".format(to_append[0]["info"]["title"], playlist_name),
_("{track} is already in {playlist}.").format(
track=to_append[0]["info"]["title"], playlist=playlist_name
),
)
if track_list:
playlists[playlist_name]["tracks"] = track_list + to_append
@ -1080,7 +1084,7 @@ class Audio(commands.Cog):
"""List saved playlists."""
playlists = await self.config.guild(ctx.guild).playlists.get_raw()
if not playlists:
return await self._embed_msg(ctx, "No saved playlists.")
return await self._embed_msg(ctx, _("No saved playlists."))
playlist_list = []
space = "\N{EN SPACE}"
for playlist_name in playlists:
@ -1089,12 +1093,12 @@ class Audio(commands.Cog):
tracks = []
author = playlists[playlist_name]["author"]
playlist_list.append(
"**{}**\n{}Tracks: {}\n{}Author: {}\n".format(
playlist_name,
(space * 4),
str(len(tracks)),
(space * 4),
self.bot.get_user(author),
("\n" + space * 4).join(
(
bold(playlist_name),
_("Tracks: {num}").format(num=len(tracks)),
_("Author: {name}").format(self.bot.get_user(author)),
)
)
)
abc_names = sorted(playlist_list, key=str.lower)
@ -1121,7 +1125,9 @@ class Audio(commands.Cog):
description=plist,
)
embed.set_footer(
text="Page {}/{} | {} playlists".format(page_num, plist_num_pages, len(abc_names))
text=_("Page {page_num}/{total_pages} | {num} playlists").format(
page_num=page_num, total_pages=plist_num_pages, num=len(abc_names)
)
)
return embed
@ -1449,9 +1455,11 @@ class Audio(commands.Cog):
player.current.title, player.current.uri.replace("localtracks/", "")
)
else:
description = "**[{}]({})**".format(player.current.title, player.current.uri)
description = f"**[{player.current.title}]({player.current.title})**"
embed = discord.Embed(
colour=await ctx.embed_colour(), title=_("Replaying Track"), description=description
colour=await ctx.embed_colour(),
title=_("Replaying Track"),
description=description,
)
await ctx.send(embed=embed)
@ -1460,7 +1468,8 @@ class Audio(commands.Cog):
async def queue(self, ctx, *, page="1"):
"""List the queue.
Use [p]queue search <search terms> to search the queue."""
Use [p]queue search <search terms> to search the queue.
"""
if not self._player_check(ctx):
return await self._embed_msg(ctx, _("There's nothing in the queue."))
player = lavalink.get_player(ctx.guild.id)
@ -1505,28 +1514,28 @@ class Audio(commands.Cog):
elif "localtracks" in player.current.uri:
if not player.current.title == "Unknown title":
queue_list += "Playing: **{} - {}**\n{}\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n".format(
player.current.author,
player.current.title,
queue_list += "\n".join(
(
_("Playing: ")
+ "**{current.author} - {current.title}**".format(current=player.current),
player.current.uri.replace("localtracks/", ""),
player.current.requester,
arrow,
pos,
dur,
_("Requested by: **{user}**\n").format(user=player.current.requester),
f"{arrow}`{pos}`/`{dur}`\n\n",
)
)
else:
queue_list += "Playing: {}\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n".format(
player.current.uri.replace("localtracks/", ""),
player.current.requester,
arrow,
pos,
dur,
queue_list += "\n".join(
(
_("Playing: ") + player.current.uri.replace("localtracks/", ""),
_("Requested by: **{user}**\n").format(user=player.current.requester),
f"{arrow}`{pos}`/`{dur}`\n\n",
)
)
else:
queue_list += _("Playing:")
queue_list += " **[{current.title}]({current.uri})**\n".format(current=player.current)
queue_list += _("Playing: ")
queue_list += "**[{current.title}]({current.uri})**\n".format(current=player.current)
queue_list += _("Requested by: **{user}**").format(user=player.current.requester)
queue_list += "\n\n{arrow}`{pos}`/`{dur}`\n\n".format(arrow=arrow, pos=pos, dur=dur)
queue_list += f"\n\n{arrow}`{pos}`/`{dur}`\n\n"
for i, track in enumerate(
player.queue[queue_idx_start:queue_idx_end], start=queue_idx_start
@ -1540,17 +1549,18 @@ class Audio(commands.Cog):
track_idx = i + 1
if "localtracks" in track.uri:
if track.title == "Unknown title":
queue_list += "`{}.` **{}**, requested by **{}**\n".format(
track_idx, track.uri.replace("localtracks/", ""), req_user
queue_list += f"`{track_idx}.` " + ", ".join(
(
bold(track.uri.replace("localtracks/", "")),
_("requested by **{user}**\n").format(user=req_user),
)
)
else:
queue_list += "`{}.` **{} - {}**, requested by **{}**\n".format(
track_idx, track.author, track_title, req_user
)
queue_list += f"`{track_idx}.` **{track.author} - {track_title}**, " + _(
"requested by **{user}**\n"
).format(user=req_user)
else:
queue_list += "`{idx}.` **[{title}]({uri})**, ".format(
idx=track_idx, title=track_title, uri=track.uri
)
queue_list += f"`{track_idx}.` **[{track_title}]({track.uri})**, "
queue_list += _("requested by **{user}**\n").format(user=req_user)
embed = discord.Embed(
@ -1581,7 +1591,7 @@ class Audio(commands.Cog):
player = lavalink.get_player(ctx.guild.id)
search_list = await self._build_queue_search_list(player.queue, search_words)
if not search_list:
return await self._embed_msg(ctx, "No matches.")
return await self._embed_msg(ctx, _("No matches."))
len_search_pages = math.ceil(len(search_list) / 10)
search_page_list = []
for page_num in range(1, len_search_pages + 1):
@ -1630,10 +1640,12 @@ class Audio(commands.Cog):
else:
track_match += "`{}.` **{}**\n".format(track[0], track[1])
embed = discord.Embed(
colour=await ctx.embed_colour(), title="Matching Tracks:", description=track_match
colour=await ctx.embed_colour(), title=_("Matching Tracks:"), description=track_match
)
embed.set_footer(
text="Page {}/{} | {} tracks".format(page_num, search_num_pages, len(search_list))
text=(_("Page {page_num}/{total_pages}") + " | {num_tracks} tracks").format(
page_num=page_num, total_pages=search_num_pages, num_tracks=len(search_list)
)
)
return embed
@ -1704,8 +1716,9 @@ class Audio(commands.Cog):
async def search(self, ctx, *, query):
"""Pick a track with a search.
Use `[p]search list <search term>` to queue all tracks found on YouTube.
`[p]search sc <search term>` will search SoundCloud instead of YouTube.
Use `[p]search list <search term>` to queue all tracks found
on YouTube. `[p]search sc <search term>` will search SoundCloud
instead of YouTube.
"""
async def _search_menu(
@ -1777,9 +1790,9 @@ class Audio(commands.Cog):
queue_total_duration = lavalink.utils.format_time(queue_duration)
if not shuffle and queue_duration > 0:
songembed.set_footer(
text=_("{time} until start of search playback: starts at #{position} in queue").format(
time=queue_total_duration, position=len(player.queue) + 1
)
text=_(
"{time} until start of search playback: starts at #{position} in queue"
).format(time=queue_total_duration, position=len(player.queue) + 1)
)
for track in tracks:
player.add(ctx.author, track)
@ -1829,7 +1842,7 @@ class Audio(commands.Cog):
player = lavalink.get_player(ctx.guild.id)
player.store("connect", datetime.datetime.utcnow())
except AttributeError:
return await self._embed_msg(ctx, "Connect to a voice channel first.")
return await self._embed_msg(ctx, _("Connect to a voice channel first."))
player = lavalink.get_player(ctx.guild.id)
jukebox_price = await self.config.guild(ctx.guild).jukebox_price()
shuffle = await self.config.guild(ctx.guild).shuffle()
@ -1886,9 +1899,7 @@ class Audio(commands.Cog):
)
)
elif queue_duration > 0:
embed.set_footer(
text=_("#{position} in queue").format(position=len(player.queue) + 1)
)
embed.set_footer(text=_("#{position} in queue").format(position=len(player.queue) + 1))
player.add(ctx.author, search_choice)
if not player.current:
@ -1949,13 +1960,11 @@ class Audio(commands.Cog):
colour=await ctx.embed_colour(), title=title, description=search_list
)
embed.set_footer(
text=(
_("Page {page_num}/{total_pages}") + " | {num_results} {footer}"
).format(
text=(_("Page {page_num}/{total_pages}") + " | {num_results} {footer}").format(
page_num=page_num,
total_pages=search_num_pages,
num_results=len(tracks),
footer=footer
footer=footer,
)
)
return embed
@ -2025,7 +2034,7 @@ class Audio(commands.Cog):
@commands.command()
@commands.guild_only()
async def sing(self, ctx):
"""Makes Red sing one of her songs"""
"""Make Red sing one of her songs"""
ids = (
"zGTkAVsrfg8",
"cGMWL8cOeAU",
@ -2040,7 +2049,7 @@ class Audio(commands.Cog):
@commands.command(aliases=["forceskip", "fs"])
@commands.guild_only()
async def skip(self, ctx):
"""Skips to the next track."""
"""Skip to the next track."""
if not self._player_check(ctx):
return await self._embed_msg(ctx, _("Nothing playing."))
player = lavalink.get_player(ctx.guild.id)
@ -2078,11 +2087,14 @@ class Audio(commands.Cog):
await self._embed_msg(ctx, _("Vote threshold met."))
return await self._skip_action(ctx)
else:
reply += _(" Votes: {num_votes}/{num_members}").format(
num_votes=num_votes, num_members=num_members
)
reply += _(" ({cur_percent}% out of {required_percent}% needed)").format(
cur_percent=vote, required_percent=percent
reply += _(
" Votes: {num_votes}/{num_members}"
" ({cur_percent}% out of {required_percent}% needed)"
).format(
num_votes=num_votes,
num_members=num_members,
cur_percent=vote,
required_percent=percent,
)
return await self._embed_msg(ctx, reply)
else:
@ -2304,8 +2316,7 @@ class Audio(commands.Cog):
await self.config.host.set(host)
if await self._check_external():
embed = discord.Embed(
colour=await ctx.embed_colour(),
title=_("Host set to {host}.").format(host=host),
colour=await ctx.embed_colour(), title=_("Host set to {host}.").format(host=host)
)
embed.set_footer(text=_("External lavalink server set to True."))
await ctx.send(embed=embed)
@ -2348,7 +2359,8 @@ class Audio(commands.Cog):
await self.config.ws_port.set(ws_port)
if await self._check_external():
embed = discord.Embed(
colour=await ctx.embed_colour(), title=_("Websocket port set to {}.").format(ws_port)
colour=await ctx.embed_colour(),
title=_("Websocket port set to {}.").format(ws_port),
)
embed.set_footer(text=_("External lavalink server set to True."))
await ctx.send(embed=embed)

View File

@ -16,7 +16,7 @@ _ = Translator("Cleanup", __file__)
@cog_i18n(_)
class Cleanup(commands.Cog):
"""Commands for cleaning messages."""
"""Commands for cleaning up messages."""
def __init__(self, bot: Red):
super().__init__()
@ -41,7 +41,7 @@ class Cleanup(commands.Cog):
await prompt.delete()
try:
await response.delete()
except:
except discord.HTTPException:
pass
return True
else:
@ -109,6 +109,7 @@ class Cleanup(commands.Cog):
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def text(
self, ctx: commands.Context, text: str, number: int, delete_pinned: bool = False
):
@ -121,9 +122,6 @@ class Cleanup(commands.Cog):
"""
channel = ctx.channel
if not channel.permissions_for(ctx.guild.me).manage_messages:
await ctx.send(_("I need the Manage Messages permission to do this."))
return
author = ctx.author
@ -157,6 +155,7 @@ class Cleanup(commands.Cog):
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def user(
self, ctx: commands.Context, user: str, number: int, delete_pinned: bool = False
):
@ -167,9 +166,6 @@ class Cleanup(commands.Cog):
`[p]cleanup user Red 6`
"""
channel = ctx.channel
if not channel.permissions_for(ctx.guild.me).manage_messages:
await ctx.send(_("I need the Manage Messages permission to do this."))
return
member = None
try:
@ -215,6 +211,7 @@ class Cleanup(commands.Cog):
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def after(self, ctx: commands.Context, message_id: int, delete_pinned: bool = False):
"""Delete all messages after a specified message.
@ -224,9 +221,6 @@ class Cleanup(commands.Cog):
"""
channel = ctx.channel
if not channel.permissions_for(ctx.guild.me).manage_messages:
await ctx.send(_("I need the Manage Messages permission to do this."))
return
author = ctx.author
try:
@ -247,6 +241,7 @@ class Cleanup(commands.Cog):
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def before(
self, ctx: commands.Context, message_id: int, number: int, delete_pinned: bool = False
):
@ -258,9 +253,6 @@ class Cleanup(commands.Cog):
"""
channel = ctx.channel
if not channel.permissions_for(ctx.guild.me).manage_messages:
await ctx.send("I need the Manage Messages permission to do this.")
return
author = ctx.author
try:
@ -281,6 +273,7 @@ class Cleanup(commands.Cog):
@cleanup.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def messages(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
"""Delete the last X messages.
@ -289,9 +282,6 @@ class Cleanup(commands.Cog):
"""
channel = ctx.channel
if not channel.permissions_for(ctx.guild.me).manage_messages:
await ctx.send(_("I need the Manage Messages permission to do this."))
return
author = ctx.author
if number > 100:
@ -313,13 +303,11 @@ class Cleanup(commands.Cog):
@cleanup.command(name="bot")
@commands.guild_only()
@commands.bot_has_permissions(manage_messages=True)
async def cleanup_bot(self, ctx: commands.Context, number: int, delete_pinned: bool = False):
"""Clean up command messages and messages from the bot."""
channel = ctx.channel
if not channel.permissions_for(ctx.guild.me).manage_messages:
await ctx.send(_("I need the Manage Messages permission to do this."))
return
author = ctx.message.author
if number > 100:

View File

@ -1,10 +1,9 @@
import os
import re
import random
from datetime import datetime, timedelta
from inspect import Parameter
from collections import OrderedDict
from typing import Mapping
from typing import Mapping, Tuple, Dict
import discord
@ -85,7 +84,7 @@ class CommandObj:
# in the ccinfo dict
return "{:%d/%m/%Y %H:%M:%S}".format(datetime.utcnow())
async def get(self, message: discord.Message, command: str) -> str:
async def get(self, message: discord.Message, command: str) -> Tuple[str, Dict]:
ccinfo = await self.db(message.guild).commands.get_raw(command, default=None)
if not ccinfo:
raise NotFound()
@ -180,9 +179,7 @@ class CommandObj:
@cog_i18n(_)
class CustomCommands(commands.Cog):
"""Custom commands
Creates commands used to display text"""
"""Creates commands used to display text."""
def __init__(self, bot):
super().__init__()
@ -227,8 +224,6 @@ class CustomCommands(commands.Cog):
)
)
# await ctx.send(str(responses))
@cc_create.command(name="simple")
@checks.mod_or_permissions(administrator=True)
async def cc_create_simple(self, ctx, command: str.lower, *, text: str):
@ -454,9 +449,8 @@ class CustomCommands(commands.Cog):
gaps = set(indices).symmetric_difference(range(high + 1))
if gaps:
raise ArgParseError(
_("Arguments must be sequential. Missing arguments: {}.").format(
", ".join(str(i + low) for i in gaps)
)
_("Arguments must be sequential. Missing arguments: ")
+ ", ".join(str(i + low) for i in gaps)
)
fin = [Parameter("_" + str(i), Parameter.POSITIONAL_OR_KEYWORD) for i in range(high + 1)]
for arg in args:
@ -481,8 +475,12 @@ class CustomCommands(commands.Cog):
and anno != fin[index].annotation
):
raise ArgParseError(
_('Conflicting colon notation for argument {}: "{}" and "{}".').format(
index + low, fin[index].annotation.__name__, anno.__name__
_(
'Conflicting colon notation for argument {index}: "{name1}" and "{name2}".'
).format(
index=index + low,
name1=fin[index].annotation.__name__,
name2=anno.__name__,
)
)
if anno is not Parameter.empty:
@ -511,6 +509,8 @@ class CustomCommands(commands.Cog):
key = (command, ctx.guild, ctx.channel)
elif per == "member":
key = (command, ctx.guild, ctx.author)
else:
raise ValueError(per)
cooldown = self.cooldowns.get(key)
if cooldown:
cooldown += timedelta(seconds=rate)

View File

@ -8,7 +8,7 @@ from sys import path as syspath
from typing import Tuple, Union, Iterable
import discord
from redbot.core import checks, commands, Config, checks, commands
from redbot.core import checks, commands, Config
from redbot.core.bot import Red
from redbot.core.data_manager import cog_data_path
from redbot.core.i18n import Translator, cog_i18n
@ -218,7 +218,7 @@ class Downloader(commands.Cog):
"""Add a new repo.
The name can only contain characters A-z, numbers and underscores.
The branch will default to master if not specified.
The branch will be the default branch if not specified.
"""
agreed = await do_install_agreement(ctx)
if not agreed:
@ -241,13 +241,13 @@ class Downloader(commands.Cog):
if repo.install_msg is not None:
await ctx.send(repo.install_msg.replace("[p]", ctx.prefix))
@repo.command(name="delete", aliases=["remove"])
async def _repo_del(self, ctx, repo_name: Repo):
@repo.command(name="delete", aliases=["remove"], usage="<repo_name>")
async def _repo_del(self, ctx, repo: Repo):
"""Remove a repo and its files."""
await self._repo_manager.delete_repo(repo_name.name)
await self._repo_manager.delete_repo(repo.name)
await ctx.send(
_("The repo `{name}` has been deleted successfully.").format(name=repo_name.name)
_("The repo `{repo.name}` has been deleted successfully.").format(repo=repo)
)
@repo.command(name="list")
@ -263,15 +263,15 @@ class Downloader(commands.Cog):
for page in pagify(joined, ["\n"], shorten_by=16):
await ctx.send(box(page.lstrip(" "), lang="diff"))
@repo.command(name="info")
async def _repo_info(self, ctx, repo_name: Repo):
@repo.command(name="info", usage="<repo_name>")
async def _repo_info(self, ctx, repo: Repo):
"""Show information about a repo."""
if repo_name is None:
await ctx.send(_("Repo `{repo_name}` not found.").format(repo_name=repo_name.name))
if repo is None:
await ctx.send(_("Repo `{repo.name}` not found.").format(repo=repo))
return
msg = _("Information on {repo_name}:\n{description}").format(
repo_name=repo_name.name, description=repo_name.description or ""
msg = _("Information on {repo.name}:\n{description}").format(
repo=repo, description=repo.description or ""
)
await ctx.send(box(msg))
@ -281,15 +281,15 @@ class Downloader(commands.Cog):
"""Cog installation management commands."""
pass
@cog.command(name="install")
async def _cog_install(self, ctx, repo_name: Repo, cog_name: str):
@cog.command(name="install", usage="<repo_name> <cog_name>")
async def _cog_install(self, ctx, repo: Repo, cog_name: str):
"""Install a cog from the given repo."""
cog: Installable = discord.utils.get(repo_name.available_cogs, name=cog_name)
cog: Installable = discord.utils.get(repo.available_cogs, name=cog_name)
if cog is None:
await ctx.send(
_(
"Error: there is no cog by the name of `{cog_name}` in the `{repo_name}` repo."
).format(cog_name=cog_name, repo_name=repo_name.name)
"Error: there is no cog by the name of `{cog_name}` in the `{repo.name}` repo."
).format(cog_name=cog_name, repo=repo)
)
return
elif cog.min_python_version > sys.version_info:
@ -300,7 +300,7 @@ class Downloader(commands.Cog):
)
return
if not await repo_name.install_requirements(cog, self.LIB_PATH):
if not await repo.install_requirements(cog, self.LIB_PATH):
await ctx.send(
_(
"Failed to install the required libraries for `{cog_name}`: `{libraries}`"
@ -308,31 +308,31 @@ class Downloader(commands.Cog):
)
return
await repo_name.install_cog(cog, await self.cog_install_path())
await repo.install_cog(cog, await self.cog_install_path())
await self._add_to_installed(cog)
await repo_name.install_libraries(self.SHAREDLIB_PATH)
await repo.install_libraries(self.SHAREDLIB_PATH)
await ctx.send(_("Cog `{cog_name}` successfully installed.").format(cog_name=cog_name))
if cog.install_msg is not None:
await ctx.send(cog.install_msg.replace("[p]", ctx.prefix))
@cog.command(name="uninstall")
async def _cog_uninstall(self, ctx, cog_name: InstalledCog):
@cog.command(name="uninstall", usage="<cog_name>")
async def _cog_uninstall(self, ctx, cog: InstalledCog):
"""Uninstall a cog.
You may only uninstall cogs which were previously installed
by Downloader.
"""
# noinspection PyUnresolvedReferences,PyProtectedMember
real_name = cog_name.name
real_name = cog.name
poss_installed_path = (await self.cog_install_path()) / real_name
if poss_installed_path.exists():
await self._delete_cog(poss_installed_path)
# noinspection PyTypeChecker
await self._remove_from_installed(cog_name)
await self._remove_from_installed(cog)
await ctx.send(
_("Cog `{cog_name}` was successfully uninstalled.").format(cog_name=real_name)
)
@ -410,8 +410,8 @@ class Downloader(commands.Cog):
else:
await ctx.send(_("OK then."))
@cog.command(name="list")
async def _cog_list(self, ctx, repo_name: Repo):
@cog.command(name="list", usage="<repo_name>")
async def _cog_list(self, ctx, repo: Repo):
"""List all available cogs from a single repo."""
installed = await self.installed_cogs()
installed_str = ""
@ -420,10 +420,10 @@ class Downloader(commands.Cog):
[
"- {}{}".format(i.name, ": {}".format(i.short) if i.short else "")
for i in installed
if i.repo_name == repo_name.name
if i.repo_name == repo.name
]
)
cogs = repo_name.available_cogs
cogs = repo.available_cogs
cogs = _("Available Cogs:\n") + "\n".join(
[
"+ {}: {}".format(c.name, c.short or "")
@ -435,14 +435,14 @@ class Downloader(commands.Cog):
for page in pagify(cogs, ["\n"], shorten_by=16):
await ctx.send(box(page.lstrip(" "), lang="diff"))
@cog.command(name="info")
async def _cog_info(self, ctx, repo_name: Repo, cog_name: str):
@cog.command(name="info", usage="<repo_name> <cog_name>")
async def _cog_info(self, ctx, repo: Repo, cog_name: str):
"""List information about a single cog."""
cog = discord.utils.get(repo_name.available_cogs, name=cog_name)
cog = discord.utils.get(repo.available_cogs, name=cog_name)
if cog is None:
await ctx.send(
_("There is no cog `{cog_name}` in the repo `{repo_name}`").format(
cog_name=cog_name, repo_name=repo_name.name
_("There is no cog `{cog_name}` in the repo `{repo.name}`").format(
cog_name=cog_name, repo=repo
)
)
return

View File

@ -3,6 +3,7 @@ import logging
import random
from collections import defaultdict, deque
from enum import Enum
from typing import cast, Iterable
import discord
@ -14,7 +15,7 @@ from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
from redbot.core.bot import Red
_ = Translator("Economy", __file__)
T_ = Translator("Economy", __file__)
logger = logging.getLogger("red.economy")
@ -34,6 +35,7 @@ class SMReel(Enum):
snowflake = "\N{SNOWFLAKE}"
_ = lambda s: s
PAYOUTS = {
(SMReel.two, SMReel.two, SMReel.six): {
"payout": lambda x: x * 2500 + x,
@ -72,6 +74,7 @@ SLOT_PAYOUTS_MSG = _(
"Three symbols: +500\n"
"Two symbols: Bet * 2"
).format(**SMReel.__dict__)
_ = T_
def guild_only_check():
@ -106,9 +109,7 @@ class SetParser:
@cog_i18n(_)
class Economy(commands.Cog):
"""Economy
Get rich and have fun with imaginary currency!"""
"""Get rich and have fun with imaginary currency!"""
default_guild_settings = {
"PAYDAY_TIME": 300,
@ -142,12 +143,12 @@ class Economy(commands.Cog):
@guild_only_check()
@commands.group(name="bank")
async def _bank(self, ctx: commands.Context):
"""Bank operations"""
"""Manage the bank."""
pass
@_bank.command()
async def balance(self, ctx: commands.Context, user: discord.Member = None):
"""Shows balance of user.
"""Show the user's account balance.
Defaults to yours."""
if user is None:
@ -156,11 +157,15 @@ class Economy(commands.Cog):
bal = await bank.get_balance(user)
currency = await bank.get_currency_name(ctx.guild)
await ctx.send(_("{}'s balance is {} {}").format(user.display_name, bal, currency))
await ctx.send(
_("{user}'s balance is {num} {currency}").format(
user=user.display_name, num=bal, currency=currency
)
)
@_bank.command()
async def transfer(self, ctx: commands.Context, to: discord.Member, amount: int):
"""Transfer currency to other users"""
"""Transfer currency to other users."""
from_ = ctx.author
currency = await bank.get_currency_name(ctx.guild)
@ -170,72 +175,83 @@ class Economy(commands.Cog):
return await ctx.send(str(e))
await ctx.send(
_("{} transferred {} {} to {}").format(
from_.display_name, amount, currency, to.display_name
_("{user} transferred {num} {currency} to {other_user}").format(
user=from_.display_name, num=amount, currency=currency, other_user=to.display_name
)
)
@_bank.command(name="set")
@check_global_setting_admin()
async def _set(self, ctx: commands.Context, to: discord.Member, creds: SetParser):
"""Sets balance of user's bank account. See help for more operations
"""Set the balance of user's bank account.
Passing positive and negative values will add/remove currency instead
Passing positive and negative values will add/remove currency instead.
Examples:
bank set @Twentysix 26 - Sets balance to 26
bank set @Twentysix +2 - Increases balance by 2
bank set @Twentysix -6 - Decreases balance by 6"""
- `[p]bank set @Twentysix 26` - Sets balance to 26
- `[p]bank set @Twentysix +2` - Increases balance by 2
- `[p]bank set @Twentysix -6` - Decreases balance by 6
"""
author = ctx.author
currency = await bank.get_currency_name(ctx.guild)
if creds.operation == "deposit":
await bank.deposit_credits(to, creds.sum)
await ctx.send(
_("{} added {} {} to {}'s account.").format(
author.display_name, creds.sum, currency, to.display_name
_("{author} added {num} {currency} to {user}'s account.").format(
author=author.display_name,
num=creds.sum,
currency=currency,
user=to.display_name,
)
)
elif creds.operation == "withdraw":
await bank.withdraw_credits(to, creds.sum)
await ctx.send(
_("{} removed {} {} from {}'s account.").format(
author.display_name, creds.sum, currency, to.display_name
_("{author} removed {num} {currency} from {user}'s account.").format(
author=author.display_name,
num=creds.sum,
currency=currency,
user=to.display_name,
)
)
else:
await bank.set_balance(to, creds.sum)
await ctx.send(
_("{} set {}'s account to {} {}.").format(
author.display_name, to.display_name, creds.sum, currency
_("{author} set {users}'s account balance to {num} {currency}.").format(
author=author.display_name,
num=creds.sum,
currency=currency,
user=to.display_name,
)
)
@_bank.command()
@check_global_setting_guildowner()
async def reset(self, ctx, confirmation: bool = False):
"""Deletes bank accounts"""
"""Delete all bank accounts."""
if confirmation is False:
await ctx.send(
_(
"This will delete all bank accounts for {}.\nIf you're sure, type "
"`{}bank reset yes`"
"This will delete all bank accounts for {scope}.\nIf you're sure, type "
"`{prefix}bank reset yes`"
).format(
self.bot.user.name if await bank.is_global() else "this server", ctx.prefix
scope=self.bot.user.name if await bank.is_global() else _("this server"),
prefix=ctx.prefix,
)
)
else:
await bank.wipe_bank()
await bank.wipe_bank(guild=ctx.guild)
await ctx.send(
_("All bank accounts for {} have been deleted.").format(
self.bot.user.name if await bank.is_global() else "this server"
_("All bank accounts for {scope} have been deleted.").format(
scope=self.bot.user.name if await bank.is_global() else _("this server")
)
)
@guild_only_check()
@commands.command()
async def payday(self, ctx: commands.Context):
"""Get some free currency"""
"""Get some free currency."""
author = ctx.author
guild = ctx.guild
@ -251,24 +267,25 @@ class Economy(commands.Cog):
pos = await bank.get_leaderboard_position(author)
await ctx.send(
_(
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
"You currently have {3} {1}.\n\n"
"You are currently #{4} on the global leaderboard!"
"{author.mention} Here, take some {currency}. "
"Enjoy! (+{amount} {new_balance}!)\n\n"
"You currently have {new_balance} {currency}.\n\n"
"You are currently #{pos} on the global leaderboard!"
).format(
author,
credits_name,
str(await self.config.PAYDAY_CREDITS()),
str(await bank.get_balance(author)),
pos,
author=author,
currency=credits_name,
amount=await self.config.PAYDAY_CREDITS(),
new_balance=await bank.get_balance(author),
pos=pos,
)
)
else:
dtime = self.display_time(next_payday - cur_time)
await ctx.send(
_("{} Too soon. For your next payday you have to wait {}.").format(
author.mention, dtime
)
_(
"{author.mention} Too soon. For your next payday you have to wait {time}."
).format(author=author, time=dtime)
)
else:
next_payday = await self.config.member(author).next_payday()
@ -286,31 +303,33 @@ class Economy(commands.Cog):
pos = await bank.get_leaderboard_position(author)
await ctx.send(
_(
"{0.mention} Here, take some {1}. Enjoy! (+{2} {1}!)\n\n"
"You currently have {3} {1}.\n\n"
"You are currently #{4} on the leaderboard!"
"{author.mention} Here, take some {currency}. "
"Enjoy! (+{amount} {new_balance}!)\n\n"
"You currently have {new_balance} {currency}.\n\n"
"You are currently #{pos} on the global leaderboard!"
).format(
author,
credits_name,
credit_amount,
str(await bank.get_balance(author)),
pos,
author=author,
currency=credits_name,
amount=credit_amount,
new_balance=await bank.get_balance(author),
pos=pos,
)
)
else:
dtime = self.display_time(next_payday - cur_time)
await ctx.send(
_("{} Too soon. For your next payday you have to wait {}.").format(
author.mention, dtime
)
_(
"{author.mention} Too soon. For your next payday you have to wait {time}."
).format(author=author, time=dtime)
)
@commands.command()
@guild_only_check()
async def leaderboard(self, ctx: commands.Context, top: int = 10, show_global: bool = False):
"""Prints out the leaderboard
"""Print the leaderboard.
Defaults to top 10"""
Defaults to top 10.
"""
guild = ctx.guild
author = ctx.author
if top < 1:
@ -320,9 +339,9 @@ class Economy(commands.Cog):
): # show_global is only applicable if bank is global
guild = None
bank_sorted = await bank.get_leaderboard(positions=top, guild=guild)
if len(bank_sorted) < top:
top = len(bank_sorted)
header = f"{f'#':4}{f'Name':36}{f'Score':2}\n"
header = "{pound:4}{name:36}{score:2}\n".format(
pound="#", name=_("Name"), score=_("Score")
)
highscores = [
(
f"{f'{pos}.': <{3 if pos < 10 else 2}} {acc[1]['name']: <{35}s} "
@ -347,13 +366,13 @@ class Economy(commands.Cog):
@commands.command()
@guild_only_check()
async def payouts(self, ctx: commands.Context):
"""Shows slot machine payouts"""
await ctx.author.send(SLOT_PAYOUTS_MSG)
"""Show the payouts for the slot machine."""
await ctx.author.send(SLOT_PAYOUTS_MSG())
@commands.command()
@guild_only_check()
async def slot(self, ctx: commands.Context, bid: int):
"""Play the slot machine"""
"""Use the slot machine."""
author = ctx.author
guild = ctx.guild
channel = ctx.channel
@ -386,8 +405,9 @@ class Economy(commands.Cog):
await self.config.member(author).last_slot.set(now)
await self.slot_machine(author, channel, bid)
async def slot_machine(self, author, channel, bid):
default_reel = deque(SMReel)
@staticmethod
async def slot_machine(author, channel, bid):
default_reel = deque(cast(Iterable, SMReel))
reels = []
for i in range(3):
default_reel.rotate(random.randint(-999, 999)) # weeeeee
@ -425,18 +445,24 @@ class Economy(commands.Cog):
pay = payout["payout"](bid)
now = then - bid + pay
await bank.set_balance(author, now)
await channel.send(
_("{}\n{} {}\n\nYour bid: {}\n{}{}!").format(
slot, author.mention, payout["phrase"], bid, then, now
)
)
phrase = T_(payout["phrase"])
else:
then = await bank.get_balance(author)
await bank.withdraw_credits(author, bid)
now = then - bid
phrase = _("Nothing!")
await channel.send(
_("{}\n{} Nothing!\nYour bid: {}\n{}{}!").format(
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,
)
)
@ -444,39 +470,37 @@ class Economy(commands.Cog):
@guild_only_check()
@check_global_setting_admin()
async def economyset(self, ctx: commands.Context):
"""Changes economy module settings"""
"""Manage Economy settings."""
guild = ctx.guild
if ctx.invoked_subcommand is None:
fmt = {}
if await bank.is_global():
fmt["slot_min"] = await self.config.SLOT_MIN()
fmt["slot_max"] = await self.config.SLOT_MAX()
fmt["slot_time"] = await self.config.SLOT_TIME()
fmt["payday_time"] = await self.config.PAYDAY_TIME()
fmt["payday_amount"] = await self.config.PAYDAY_CREDITS()
conf = self.config
else:
fmt["slot_min"] = await self.config.guild(guild).SLOT_MIN()
fmt["slot_max"] = await self.config.guild(guild).SLOT_MAX()
fmt["slot_time"] = await self.config.guild(guild).SLOT_TIME()
fmt["payday_time"] = await self.config.guild(guild).PAYDAY_TIME()
fmt["payday_amount"] = await self.config.guild(guild).PAYDAY_CREDITS()
fmt["register_amount"] = await bank.get_default_balance(guild)
msg = box(
conf = self.config.guild(ctx.guild)
await ctx.send(
box(
_(
"Current Economy settings:"
"----Economy Settings---\n"
"Minimum slot bid: {slot_min}\n"
"Maximum slot bid: {slot_max}\n"
"Slot cooldown: {slot_time}\n"
"Payday amount: {payday_amount}\n"
"Payday cooldown: {payday_time}\n"
"Amount given at account registration: {register_amount}"
).format(**fmt)
).format(
slot_min=await conf.SLOT_MIN(),
slot_max=await conf.SLOT_MAX(),
slot_time=await conf.SLOT_TIME(),
payday_time=await conf.PAYDAY_TIME(),
payday_amount=await conf.PAYDAY_CREDITS(),
register_amount=await bank.get_default_balance(guild),
)
)
)
await ctx.send(msg)
@economyset.command()
async def slotmin(self, ctx: commands.Context, bid: int):
"""Minimum slot machine bid"""
"""Set the minimum slot machine bid."""
if bid < 1:
await ctx.send(_("Invalid min bid amount."))
return
@ -492,10 +516,12 @@ class Economy(commands.Cog):
@economyset.command()
async def slotmax(self, ctx: commands.Context, bid: int):
"""Maximum slot machine bid"""
"""Set the maximum slot machine bid."""
slot_min = await self.config.SLOT_MIN()
if bid < 1 or bid < slot_min:
await ctx.send(_("Invalid slotmax bid amount. Must be greater than slotmin."))
await ctx.send(
_("Invalid maximum bid amount. Must be greater than the minimum amount.")
)
return
guild = ctx.guild
credits_name = await bank.get_currency_name(guild)
@ -509,7 +535,7 @@ class Economy(commands.Cog):
@economyset.command()
async def slottime(self, ctx: commands.Context, seconds: int):
"""Seconds between each slots use"""
"""Set the cooldown for the slot machine."""
guild = ctx.guild
if await bank.is_global():
await self.config.SLOT_TIME.set(seconds)
@ -519,7 +545,7 @@ class Economy(commands.Cog):
@economyset.command()
async def paydaytime(self, ctx: commands.Context, seconds: int):
"""Seconds between each payday"""
"""Set the cooldown for payday."""
guild = ctx.guild
if await bank.is_global():
await self.config.PAYDAY_TIME.set(seconds)
@ -533,7 +559,7 @@ class Economy(commands.Cog):
@economyset.command()
async def paydayamount(self, ctx: commands.Context, creds: int):
"""Amount earned each payday"""
"""Set the amount earned each payday."""
guild = ctx.guild
credits_name = await bank.get_currency_name(guild)
if creds <= 0:
@ -551,11 +577,11 @@ class Economy(commands.Cog):
@economyset.command()
async def rolepaydayamount(self, ctx: commands.Context, role: discord.Role, creds: int):
"""Amount earned each payday for a role"""
"""Set the amount earned each payday for a role."""
guild = ctx.guild
credits_name = await bank.get_currency_name(guild)
if await bank.is_global():
await ctx.send("The bank must be per-server for per-role paydays to work.")
await ctx.send(_("The bank must be per-server for per-role paydays to work."))
else:
await self.config.role(role).PAYDAY_CREDITS.set(creds)
await ctx.send(
@ -567,7 +593,7 @@ class Economy(commands.Cog):
@economyset.command()
async def registeramount(self, ctx: commands.Context, creds: int):
"""Amount given on registering an account"""
"""Set the initial balance for new bank accounts."""
guild = ctx.guild
if creds < 0:
creds = 0
@ -580,7 +606,8 @@ class Economy(commands.Cog):
)
# What would I ever do without stackoverflow?
def display_time(self, seconds, granularity=2):
@staticmethod
def display_time(seconds, granularity=2):
intervals = ( # Source: http://stackoverflow.com/a/24542445
(_("weeks"), 604800), # 60 * 60 * 24 * 7
(_("days"), 86400), # 60 * 60 * 24

View File

@ -5,14 +5,13 @@ from redbot.core import checks, Config, modlog, commands
from redbot.core.bot import Red
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.chat_formatting import pagify
from redbot.core.utils.mod import is_mod_or_superior
_ = Translator("Filter", __file__)
@cog_i18n(_)
class Filter(commands.Cog):
"""Filter-related commands"""
"""Filter unwanted words and phrases from text channels."""
def __init__(self, bot: Red):
super().__init__()
@ -35,7 +34,8 @@ class Filter(commands.Cog):
def __unload(self):
self.register_task.cancel()
async def register_filterban(self):
@staticmethod
async def register_filterban():
try:
await modlog.register_casetype(
"filterban", False, ":filing_cabinet: :hammer:", "Filter ban", "ban"
@ -47,18 +47,17 @@ class Filter(commands.Cog):
@commands.guild_only()
@checks.admin_or_permissions(manage_guild=True)
async def filterset(self, ctx: commands.Context):
"""
Filter settings
"""
"""Manage filter settings."""
pass
@filterset.command(name="defaultname")
async def filter_default_name(self, ctx: commands.Context, name: str):
"""Sets the default name to use if filtering names is enabled
"""Set the nickname for users with a filtered name.
Note that this has no effect if filtering names is disabled
(to toggle, run `[p]filter names`).
The default name used is John Doe
The default name used is *John Doe*.
"""
guild = ctx.guild
await self.settings.guild(guild).filter_default_name.set(name)
@ -66,9 +65,12 @@ class Filter(commands.Cog):
@filterset.command(name="ban")
async def filter_ban(self, ctx: commands.Context, count: int, timeframe: int):
"""Autobans if the specified number of messages are filtered in the timeframe
"""Set the filter's autoban conditions.
The timeframe is represented by seconds.
Users will be banned if they send `<count>` filtered words in
`<timeframe>` seconds.
Set both to zero to disable autoban.
"""
if (count <= 0) != (timeframe <= 0):
await ctx.send(
@ -91,11 +93,13 @@ class Filter(commands.Cog):
@commands.guild_only()
@checks.mod_or_permissions(manage_messages=True)
async def _filter(self, ctx: commands.Context):
"""Adds/removes words from server filter
"""Add or remove words from server filter.
Use double quotes to add/remove sentences
Using this command with no subcommands will send
the list of the server's filtered words."""
Use double quotes to add or remove sentences.
Using this command with no subcommands will send the list of
the server's filtered words.
"""
if ctx.invoked_subcommand is None:
server = ctx.guild
author = ctx.author
@ -111,11 +115,13 @@ class Filter(commands.Cog):
@_filter.group(name="channel")
async def _filter_channel(self, ctx: commands.Context):
"""Adds/removes words from channel filter
"""Add or remove words from channel filter.
Use double quotes to add/remove sentences
Using this command with no subcommands will send
the list of the channel's filtered words."""
Use double quotes to add or remove sentences.
Using this command with no subcommands will send the list of
the channel's filtered words.
"""
if ctx.invoked_subcommand is None:
channel = ctx.channel
author = ctx.author
@ -131,12 +137,14 @@ class Filter(commands.Cog):
@_filter_channel.command("add")
async def filter_channel_add(self, ctx: commands.Context, *, words: str):
"""Adds words to the filter
"""Add words to the filter.
Use double quotes to add sentences.
Use double quotes to add sentences
Examples:
filter add word1 word2 word3
filter add \"This is a sentence\""""
- `[p]filter channel add word1 word2 word3`
- `[p]filter channel add "This is a sentence"`
"""
channel = ctx.channel
split_words = words.split()
word_list = []
@ -161,12 +169,14 @@ class Filter(commands.Cog):
@_filter_channel.command("remove")
async def filter_channel_remove(self, ctx: commands.Context, *, words: str):
"""Remove words from the filter
"""Remove words from the filter.
Use double quotes to remove sentences.
Use double quotes to remove sentences
Examples:
filter remove word1 word2 word3
filter remove \"This is a sentence\""""
- `[p]filter channel remove word1 word2 word3`
- `[p]filter channel remove "This is a sentence"`
"""
channel = ctx.channel
split_words = words.split()
word_list = []
@ -191,12 +201,14 @@ class Filter(commands.Cog):
@_filter.command(name="add")
async def filter_add(self, ctx: commands.Context, *, words: str):
"""Adds words to the filter
"""Add words to the filter.
Use double quotes to add sentences.
Use double quotes to add sentences
Examples:
filter add word1 word2 word3
filter add \"This is a sentence\""""
- `[p]filter add word1 word2 word3`
- `[p]filter add "This is a sentence"`
"""
server = ctx.guild
split_words = words.split()
word_list = []
@ -215,18 +227,20 @@ class Filter(commands.Cog):
tmp += word + " "
added = await self.add_to_filter(server, word_list)
if added:
await ctx.send(_("Words added to filter."))
await ctx.send(_("Words successfully added to filter."))
else:
await ctx.send(_("Words already in the filter."))
await ctx.send(_("Those words were already in the filter."))
@_filter.command(name="remove")
async def filter_remove(self, ctx: commands.Context, *, words: str):
"""Remove words from the filter
"""Remove words from the filter.
Use double quotes to remove sentences.
Use double quotes to remove sentences
Examples:
filter remove word1 word2 word3
filter remove \"This is a sentence\""""
- `[p]filter remove word1 word2 word3`
- `[p]filter remove "This is a sentence"`
"""
server = ctx.guild
split_words = words.split()
word_list = []
@ -245,23 +259,23 @@ class Filter(commands.Cog):
tmp += word + " "
removed = await self.remove_from_filter(server, word_list)
if removed:
await ctx.send(_("Words removed from filter."))
await ctx.send(_("Words successfully removed from filter."))
else:
await ctx.send(_("Those words weren't in the filter."))
@_filter.command(name="names")
async def filter_names(self, ctx: commands.Context):
"""Toggles whether or not to check names and nicknames against the filter
"""Toggle name and nickname filtering.
This is disabled by default
This is disabled by default.
"""
guild = ctx.guild
current_setting = await self.settings.guild(guild).filter_names()
await self.settings.guild(guild).filter_names.set(not current_setting)
if current_setting:
await ctx.send(_("Names and nicknames will no longer be checked against the filter."))
await ctx.send(_("Names and nicknames will no longer be filtered."))
else:
await ctx.send(_("Names and nicknames will now be checked against the filter."))
await ctx.send(_("Names and nicknames will now be filtered."))
async def add_to_filter(
self, server_or_channel: Union[discord.Guild, discord.TextChannel], words: list
@ -327,7 +341,7 @@ class Filter(commands.Cog):
if w in message.content.lower():
try:
await message.delete()
except:
except discord.HTTPException:
pass
else:
if filter_count > 0 and filter_time > 0:
@ -337,10 +351,10 @@ class Filter(commands.Cog):
user_count >= filter_count
and message.created_at.timestamp() < next_reset_time
):
reason = "Autoban (too many filtered messages.)"
reason = _("Autoban (too many filtered messages.)")
try:
await server.ban(author, reason=reason)
except:
except discord.HTTPException:
pass
else:
await modlog.create_case(
@ -366,20 +380,6 @@ class Filter(commands.Cog):
await self.check_filter(message)
async def on_message_edit(self, _, message):
author = message.author
if message.guild is None or self.bot.user == author:
return
valid_user = isinstance(author, discord.Member) and not author.bot
if not valid_user:
return
# As is anyone configured to be
if await self.bot.is_automod_immune(message):
return
await self.check_filter(message)
async def on_message_edit(self, _prior, message):
# message content has to change for non-bot's currently.
# if this changes, we should compare before passing it.
@ -399,14 +399,14 @@ class Filter(commands.Cog):
return # Discord Hierarchy applies to nicks
if await self.bot.is_automod_immune(member):
return
word_list = await self.settings.guild(member.guild).filter()
if not await self.settings.guild(member.guild).filter_names():
return
word_list = await self.settings.guild(member.guild).filter()
for w in word_list:
if w in member.display_name.lower():
name_to_use = await self.settings.guild(member.guild).filter_default_name()
reason = "Filtered nick" if member.nick else "Filtered name"
reason = _("Filtered nickname") if member.nick else _("Filtered name")
try:
await member.edit(nick=name_to_use, reason=reason)
except discord.HTTPException:

View File

@ -2,15 +2,14 @@ import datetime
import time
from enum import Enum
from random import randint, choice
from urllib.parse import quote_plus
import aiohttp
import discord
from redbot.core import commands
from redbot.core.i18n import Translator, cog_i18n
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
from redbot.core.utils.chat_formatting import escape, italics, pagify
from redbot.core.utils.chat_formatting import escape, italics
_ = Translator("General", __file__)
_ = T_ = Translator("General", __file__)
class RPS(Enum):
@ -29,17 +28,16 @@ class RPSParser:
elif argument == "scissors":
self.choice = RPS.scissors
else:
raise
raise ValueError
@cog_i18n(_)
class General(commands.Cog):
"""General commands."""
def __init__(self):
super().__init__()
self.stopwatches = {}
self.ball = [
global _
_ = lambda s: s
ball = [
_("As I see it, yes"),
_("It is certain"),
_("It is decidedly so"),
@ -61,39 +59,47 @@ class General(commands.Cog):
_("Outlook not so good"),
_("Very doubtful"),
]
_ = T_
def __init__(self):
super().__init__()
self.stopwatches = {}
@commands.command()
async def choose(self, ctx, *choices):
"""Chooses between multiple choices.
"""Choose between multiple options.
To denote multiple choices, you should use double quotes.
To denote options which include whitespace, you should use
double quotes.
"""
choices = [escape(c, mass_mentions=True) for c in choices]
if len(choices) < 2:
await ctx.send(_("Not enough choices to pick from."))
await ctx.send(_("Not enough options to pick from."))
else:
await ctx.send(choice(choices))
@commands.command()
async def roll(self, ctx, number: int = 100):
"""Rolls random number (between 1 and user choice)
"""Roll a random number.
Defaults to 100.
The result will be between 1 and `<number>`.
`<number>` defaults to 100.
"""
author = ctx.author
if number > 1:
n = randint(1, number)
await ctx.send(_("{} :game_die: {} :game_die:").format(author.mention, n))
await ctx.send("{author.mention} :game_die: {n} :game_die:".format(author=author, n=n))
else:
await ctx.send(_("{} Maybe higher than 1? ;P").format(author.mention))
await ctx.send(_("{author.mention} Maybe higher than 1? ;P").format(author=author))
@commands.command()
async def flip(self, ctx, user: discord.Member = None):
"""Flips a coin... or a user.
"""Flip a coin... or a user.
Defaults to coin.
Defaults to a coin.
"""
if user != None:
if user is not None:
msg = ""
if user.id == ctx.bot.user.id:
user = ctx.author
@ -112,7 +118,7 @@ class General(commands.Cog):
@commands.command()
async def rps(self, ctx, your_choice: RPSParser):
"""Play rock paper scissors"""
"""Play Rock Paper Scissors."""
author = ctx.author
player_choice = your_choice.choice
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
@ -151,31 +157,33 @@ class General(commands.Cog):
@commands.command(name="8", aliases=["8ball"])
async def _8ball(self, ctx, *, question: str):
"""Ask 8 ball a question
"""Ask 8 ball a question.
Question must end with a question mark.
"""
if question.endswith("?") and question != "?":
await ctx.send("`" + choice(self.ball) + "`")
await ctx.send("`" + T_(choice(self.ball)) + "`")
else:
await ctx.send(_("That doesn't look like a question."))
@commands.command(aliases=["sw"])
async def stopwatch(self, ctx):
"""Starts/stops stopwatch"""
"""Start or stop the stopwatch."""
author = ctx.author
if not author.id in self.stopwatches:
if author.id not in self.stopwatches:
self.stopwatches[author.id] = int(time.perf_counter())
await ctx.send(author.mention + _(" Stopwatch started!"))
else:
tmp = abs(self.stopwatches[author.id] - int(time.perf_counter()))
tmp = str(datetime.timedelta(seconds=tmp))
await ctx.send(author.mention + _(" Stopwatch stopped! Time: **") + tmp + "**")
await ctx.send(
author.mention + _(" Stopwatch stopped! Time: **{seconds}**").format(seconds=tmp)
)
self.stopwatches.pop(author.id, None)
@commands.command()
async def lmgtfy(self, ctx, *, search_terms: str):
"""Creates a lmgtfy link"""
"""Create a lmgtfy link."""
search_terms = escape(
search_terms.replace("+", "%2B").replace(" ", "+"), mass_mentions=True
)
@ -184,9 +192,10 @@ class General(commands.Cog):
@commands.command(hidden=True)
@commands.guild_only()
async def hug(self, ctx, user: discord.Member, intensity: int = 1):
"""Because everyone likes hugs
"""Because everyone likes hugs!
Up to 10 intensity levels."""
Up to 10 intensity levels.
"""
name = italics(user.display_name)
if intensity <= 0:
msg = "(っ˘̩╭╮˘̩)っ" + name
@ -198,12 +207,15 @@ class General(commands.Cog):
msg = "(つ≧▽≦)つ" + name
elif intensity >= 10:
msg = "(づ ̄ ³ ̄)づ{} ⊂(´・ω・`⊂)".format(name)
else:
# For the purposes of "msg might not be defined" linter errors
raise RuntimeError
await ctx.send(msg)
@commands.command()
@commands.guild_only()
async def serverinfo(self, ctx):
"""Shows server's informations"""
"""Show server information."""
guild = ctx.guild
online = len([m.status for m in guild.members if m.status != discord.Status.offline])
total_users = len(guild.members)
@ -230,12 +242,15 @@ class General(commands.Cog):
try:
await ctx.send(embed=data)
except discord.HTTPException:
except discord.Forbidden:
await ctx.send(_("I need the `Embed links` permission to send this."))
@commands.command()
async def urban(self, ctx, *, word):
"""Searches urban dictionary entries using the unofficial API."""
"""Search the Urban Dictionary.
This uses the unofficial Urban Dictionary API.
"""
try:
url = "https://api.urbandictionary.com/v0/define?term=" + str(word).lower()
@ -246,7 +261,7 @@ class General(commands.Cog):
async with session.get(url, headers=headers) as response:
data = await response.json()
except:
except aiohttp.ClientError:
await ctx.send(
_("No Urban dictionary entries were found, or there was an error in the process")
)
@ -287,17 +302,16 @@ class General(commands.Cog):
)
else:
messages = []
ud.set_default("example", "N/A")
for ud in data["list"]:
ud.set_default("example", "N/A")
description = _("{definition}\n\n**Example:** {example}").format(**ud)
if len(description) > 2048:
description = "{}...".format(description[:2045])
description = description
message = _(
"<{permalink}>\n {word} by {author}\n\n{description}\n\n"
"{thumbs_down} Down / {thumbs_up} Up, Powered by urban dictionary"
).format(word=ud.pop("word").capitalize(), **ud)
).format(word=ud.pop("word").capitalize(), description=description, **ud)
messages.append(message)
if messages is not None and len(messages) > 0:

View File

@ -29,23 +29,26 @@ class Image(commands.Cog):
@commands.group(name="imgur")
async def _imgur(self, ctx):
"""Retrieves pictures from imgur
"""Retrieve pictures from Imgur.
Make sure to set the client ID using
[p]imgurcreds"""
Make sure to set the Client ID using `[p]imgurcreds`.
"""
pass
@_imgur.command(name="search")
async def imgur_search(self, ctx, *, term: str):
"""Searches Imgur for the specified term and returns up to 3 results"""
"""Search Imgur for the specified term.
Returns up to 3 results.
"""
url = self.imgur_base_url + "gallery/search/time/all/0"
params = {"q": term}
imgur_client_id = await self.settings.imgur_client_id()
if not imgur_client_id:
await ctx.send(
_("A client ID has not been set! Please set one with {}.").format(
"`{}imgurcreds`".format(ctx.prefix)
)
_(
"A Client ID has not been set! Please set one with `{prefix}imgurcreds`."
).format(prefix=ctx.prefix)
)
return
headers = {"Authorization": "Client-ID {}".format(imgur_client_id)}
@ -64,37 +67,41 @@ class Image(commands.Cog):
msg += "\n"
await ctx.send(msg)
else:
await ctx.send(_("Something went wrong. Error code is {}.").format(data["status"]))
await ctx.send(
_("Something went wrong. Error code is {code}.").format(code=data["status"])
)
@_imgur.command(name="subreddit")
async def imgur_subreddit(
self, ctx, subreddit: str, sort_type: str = "top", window: str = "day"
):
"""Gets images from the specified subreddit section
"""Get images from a subreddit.
Sort types: new, top
Time windows: day, week, month, year, all"""
You can customize the search with the following options:
- `<sort_type>`: new, top
- `<window>`: day, week, month, year, all
"""
sort_type = sort_type.lower()
window = window.lower()
if sort_type not in ("new", "top"):
await ctx.send(_("Only 'new' and 'top' are a valid sort type."))
return
elif window not in ("day", "week", "month", "year", "all"):
await ctx.send_help()
return
if sort_type == "new":
sort = "time"
elif sort_type == "top":
sort = "top"
else:
await ctx.send(_("Only 'new' and 'top' are a valid sort type."))
return
if window not in ("day", "week", "month", "year", "all"):
await ctx.send_help()
return
imgur_client_id = await self.settings.imgur_client_id()
if not imgur_client_id:
await ctx.send(
_("A client ID has not been set! Please set one with {}.").format(
"`{}imgurcreds`".format(ctx.prefix)
)
_(
"A Client ID has not been set! Please set one with `{prefix}imgurcreds`."
).format(prefix=ctx.prefix)
)
return
@ -117,29 +124,33 @@ class Image(commands.Cog):
else:
await ctx.send(_("No results found."))
else:
await ctx.send(_("Something went wrong. Error code is {}.").format(data["status"]))
await ctx.send(
_("Something went wrong. Error code is {code}.").format(code=data["status"])
)
@checks.is_owner()
@commands.command()
async def imgurcreds(self, ctx, imgur_client_id: str):
"""Sets the imgur client id
"""Set the Imgur Client ID.
You will need an account on Imgur to get this
You can get these by visiting https://api.imgur.com/oauth2/addclient
and filling out the form. Enter a name for the application, select
'Anonymous usage without user authorization' for the auth type,
set the authorization callback url to 'https://localhost'
leave the app website blank, enter a valid email address, and
enter a description. Check the box for the captcha, then click Next.
Your client ID will be on the page that loads."""
To get an Imgur Client ID:
1. Login to an Imgur account.
2. Visit [this](https://api.imgur.com/oauth2/addclient) page
3. Enter a name for your application
4. Select *Anonymous usage without user authorization* for the auth type
5. Set the authorization callback URL to `https://localhost`
6. Leave the app website blank
7. Enter a valid email address and a description
8. Check the captcha box and click next
9. Your Client ID will be on the next page.
"""
await self.settings.imgur_client_id.set(imgur_client_id)
await ctx.send(_("Set the imgur client id!"))
await ctx.send(_("The Imgur Client ID has been set!"))
@commands.guild_only()
@commands.command()
async def gif(self, ctx, *keywords):
"""Retrieves first search result from giphy"""
"""Retrieve the first search result from Giphy."""
if keywords:
keywords = "+".join(keywords)
else:
@ -158,12 +169,12 @@ class Image(commands.Cog):
else:
await ctx.send(_("No results found."))
else:
await ctx.send(_("Error contacting the API."))
await ctx.send(_("Error contacting the Giphy API."))
@commands.guild_only()
@commands.command()
async def gifr(self, ctx, *keywords):
"""Retrieves a random gif from a giphy search"""
"""Retrieve a random GIF from a Giphy search."""
if keywords:
keywords = "+".join(keywords)
else:

View File

@ -1,5 +1,4 @@
from redbot.core import commands
import discord
def mod_or_voice_permissions(**perms):

View File

@ -1,6 +1,8 @@
import asyncio
import contextlib
from datetime import datetime, timedelta
from collections import deque, defaultdict, namedtuple
from typing import cast
import discord
@ -14,7 +16,7 @@ from .log import log
from redbot.core.utils.common_filters import filter_invites, filter_various_mentions
_ = Translator("Mod", __file__)
_ = T_ = Translator("Mod", __file__)
@cog_i18n(_)
@ -58,7 +60,8 @@ class Mod(commands.Cog):
self.registration_task.cancel()
self.tban_expiry_task.cancel()
async def _casetype_registration(self):
@staticmethod
async def _casetype_registration():
casetypes_to_register = [
{
"name": "ban",
@ -168,7 +171,7 @@ class Mod(commands.Cog):
@commands.guild_only()
@checks.guildowner_or_permissions(administrator=True)
async def modset(self, ctx: commands.Context):
"""Manages server administration settings."""
"""Manage server administration settings."""
if ctx.invoked_subcommand is None:
guild = ctx.guild
# Display current settings
@ -183,7 +186,7 @@ class Mod(commands.Cog):
)
msg += _("Ban mention spam: {num_mentions}\n").format(
num_mentions=_("{num} mentions").format(num=ban_mention_spam)
if isinstance(ban_mention_spam, int)
if ban_mention_spam
else _("No")
)
msg += _("Respects hierarchy: {yes_or_no}\n").format(
@ -195,14 +198,20 @@ class Mod(commands.Cog):
else _("None")
)
msg += _("Reinvite on unban: {yes_or_no}\n").format(
yes_or_no=_("Yes") if respect_hierarchy else _("No")
yes_or_no=_("Yes") if reinvite_on_unban else _("No")
)
await ctx.send(box(msg))
@modset.command()
@commands.guild_only()
async def hierarchy(self, ctx: commands.Context):
"""Toggles role hierarchy check for mods / admins"""
"""Toggle role hierarchy check for mods and admins.
**WARNING**: Disabling this setting will allow mods to take
actions on users above them in the role hierarchy!
This is enabled by default.
"""
guild = ctx.guild
toggled = await self.settings.guild(guild).respect_hierarchy()
if not toggled:
@ -218,10 +227,14 @@ class Mod(commands.Cog):
@modset.command()
@commands.guild_only()
async def banmentionspam(self, ctx: commands.Context, max_mentions: int = False):
"""Enables auto ban for messages mentioning X different people
async def banmentionspam(self, ctx: commands.Context, max_mentions: int = 0):
"""Set the autoban conditions for mention spam.
Accepted values: 5 or superior"""
Users will be banned if they send any message which contains more than
`<max_mentions>` mentions.
`<max_mentions>` must be at least 5. Set to 0 to disable.
"""
guild = ctx.guild
if max_mentions:
if max_mentions < 5:
@ -236,7 +249,7 @@ class Mod(commands.Cog):
)
else:
cur_setting = await self.settings.guild(guild).ban_mention_spam()
if cur_setting is False:
if not cur_setting:
await ctx.send_help()
return
await self.settings.guild(guild).ban_mention_spam.set(False)
@ -245,7 +258,7 @@ class Mod(commands.Cog):
@modset.command()
@commands.guild_only()
async def deleterepeats(self, ctx: commands.Context):
"""Enables auto deletion of repeated messages"""
"""Enable auto-deletion of repeated messages."""
guild = ctx.guild
cur_setting = await self.settings.guild(guild).delete_repeats()
if not cur_setting:
@ -258,11 +271,12 @@ class Mod(commands.Cog):
@modset.command()
@commands.guild_only()
async def deletedelay(self, ctx: commands.Context, time: int = None):
"""Sets the delay until the bot removes the command message.
"""Set the delay until the bot removes the command message.
Must be between -1 and 60.
A delay of -1 means the bot will not remove the message."""
Set to -1 to disable this feature.
"""
guild = ctx.guild
if time is not None:
time = min(max(time, -1), 60) # Enforces the time limits
@ -287,10 +301,11 @@ class Mod(commands.Cog):
@modset.command()
@commands.guild_only()
async def reinvite(self, ctx: commands.Context):
"""Toggles whether an invite will be sent when a user is unbanned via [p]unban.
"""Toggle whether an invite will be sent to a user when unbanned.
If this is True, the bot will attempt to create and send a single-use invite
to the newly-unbanned user"""
to the newly-unbanned user.
"""
guild = ctx.guild
cur_setting = await self.settings.guild(guild).reinvite_on_unban()
if not cur_setting:
@ -308,12 +323,14 @@ class Mod(commands.Cog):
@commands.command()
@commands.guild_only()
@commands.bot_has_permissions(kick_members=True)
@checks.admin_or_permissions(kick_members=True)
async def kick(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Kicks user.
"""Kick a user.
If a reason is specified, it will be the reason that shows up
in the audit log"""
in the audit log.
"""
author = ctx.author
guild = ctx.guild
@ -364,14 +381,18 @@ class Mod(commands.Cog):
@commands.command()
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True)
async def ban(
self, ctx: commands.Context, user: discord.Member, days: str = None, *, reason: str = None
):
"""Bans user and deletes last X days worth of messages.
"""Ban a user from the current server.
If days is not a number, it's treated as the first word of the reason.
Minimum 0 days, maximum 7. Defaults to 0."""
Deletes `<days>` worth of messages.
If `<days>` is not a number, it's treated as the first word of
the reason. Minimum 0 days, maximum 7. Defaults to 0.
"""
author = ctx.author
guild = ctx.guild
@ -445,16 +466,16 @@ class Mod(commands.Cog):
@commands.command()
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True)
async def hackban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
"""Preemptively bans user from the server
"""Pre-emptively ban a user from the current server.
A user ID needs to be provided in order to ban
using this command"""
using this command.
"""
author = ctx.author
guild = ctx.guild
if not guild.me.guild_permissions.ban_members:
return await ctx.send(_("I lack the permissions to do this."))
is_banned = False
ban_list = await guild.bans()
for entry in ban_list:
@ -505,31 +526,30 @@ class Mod(commands.Cog):
@commands.command()
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True)
async def tempban(
self, ctx: commands.Context, user: discord.Member, days: int = 1, *, reason: str = None
):
"""Tempbans the user for the specified number of days"""
"""Temporarily ban a user from the current server."""
guild = ctx.guild
author = ctx.author
days_delta = timedelta(days=int(days))
unban_time = datetime.utcnow() + days_delta
channel = ctx.channel
can_ban = channel.permissions_for(guild.me).ban_members
invite = await self.get_invite_for_reinvite(ctx, int(days_delta.total_seconds() + 86400))
if invite is None:
invite = ""
if can_ban:
queue_entry = (guild.id, user.id)
await self.settings.member(user).banned_until.set(unban_time.timestamp())
cur_tbans = await self.settings.guild(guild).current_tempbans()
cur_tbans.append(user.id)
await self.settings.guild(guild).current_tempbans.set(cur_tbans)
try: # We don't want blocked DMs preventing us from banning
msg = await user.send(
with contextlib.suppress(discord.HTTPException):
# We don't want blocked DMs preventing us from banning
await user.send(
_(
"You have been temporarily banned from {server_name} until {date}. "
"Here is an invite for when your ban expires: {invite_link}"
@ -539,8 +559,6 @@ class Mod(commands.Cog):
invite_link=invite,
)
)
except discord.HTTPException:
msg = None
self.ban_queue.append(queue_entry)
try:
await guild.ban(user)
@ -567,12 +585,11 @@ class Mod(commands.Cog):
@commands.command()
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True)
async def softban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Kicks the user, deleting 1 day worth of messages."""
"""Kick a user and delete 1 day's worth of their messages."""
guild = ctx.guild
channel = ctx.channel
can_ban = channel.permissions_for(guild.me).ban_members
author = ctx.author
if author == user:
@ -598,7 +615,6 @@ class Mod(commands.Cog):
if invite is None:
invite = ""
if can_ban:
queue_entry = (guild.id, user.id)
try: # We don't want blocked DMs preventing us from banning
msg = await user.send(
@ -650,23 +666,18 @@ class Mod(commands.Cog):
)
except RuntimeError as e:
await ctx.send(e)
else:
await ctx.send(_("I'm not allowed to do that."))
@commands.command()
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@checks.admin_or_permissions(ban_members=True)
async def unban(self, ctx: commands.Context, user_id: int, *, reason: str = None):
"""Unbans the target user.
"""Unban a user from the current server.
Requires specifying the target user's ID. To find this, you may either:
1. Copy it from the mod log case (if one was created), or
2. enable developer mode, go to Bans in this server's settings, right-
click the user and select 'Copy ID'."""
channel = ctx.channel
if not channel.permissions_for(ctx.guild.me).ban_members:
await ctx.send("I need the Ban Members permission to do this.")
return
guild = ctx.guild
author = ctx.author
user = await self.bot.get_user_info(user_id)
@ -772,7 +783,7 @@ class Mod(commands.Cog):
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
async def voiceban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Bans the target user from speaking and listening in voice channels in the server"""
"""Ban a user from speaking and listening in the server's voice channels."""
user_voice_state = user.voice
if user_voice_state is None:
await ctx.send(_("No voice state for that user!"))
@ -813,7 +824,7 @@ class Mod(commands.Cog):
@admin_or_voice_permissions(mute_members=True, deafen_members=True)
@bot_has_voice_permissions(mute_members=True, deafen_members=True)
async def voiceunban(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Unbans the user from speaking/listening in the server's voice channels"""
"""Unban a the user from speaking and listening in the server's voice channels."""
user_voice_state = user.voice
if user_voice_state is None:
await ctx.send(_("No voice state for that user!"))
@ -850,29 +861,24 @@ class Mod(commands.Cog):
@commands.command()
@commands.guild_only()
@commands.bot_has_permissions(manage_nicknames=True)
@checks.admin_or_permissions(manage_nicknames=True)
async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname=""):
"""Changes user's nickname
"""Change a user's nickname.
Leaving the nickname empty will remove it."""
Leaving the nickname empty will remove it.
"""
nickname = nickname.strip()
if nickname == "":
nickname = None
try:
await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
await ctx.send("Done.")
except discord.Forbidden:
await ctx.send(
_("I cannot do that, I lack the '{perm}' permission.").format(
perm="Manage Nicknames"
)
)
@commands.group()
@commands.guild_only()
@checks.mod_or_permissions(manage_channel=True)
async def mute(self, ctx: commands.Context):
"""Mutes user in the channel/server"""
"""Mute users."""
pass
@mute.command(name="voice")
@ -880,7 +886,7 @@ class Mod(commands.Cog):
@mod_or_voice_permissions(mute_members=True)
@bot_has_voice_permissions(mute_members=True)
async def voice_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Mutes the user in a voice channel"""
"""Mute a user in their current voice channel."""
user_voice_state = user.voice
guild = ctx.guild
author = ctx.author
@ -920,13 +926,14 @@ class Mod(commands.Cog):
await ctx.send(_("No voice state for the target!"))
return
@checks.mod_or_permissions(administrator=True)
@mute.command(name="channel")
@commands.guild_only()
@commands.bot_has_permissions(manage_roles=True)
@checks.mod_or_permissions(administrator=True)
async def channel_mute(
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
):
"""Mutes user in the current channel"""
"""Mute a user in the current text channel."""
author = ctx.message.author
channel = ctx.message.channel
guild = ctx.guild
@ -959,14 +966,14 @@ class Mod(commands.Cog):
else:
await channel.send(issue)
@checks.mod_or_permissions(administrator=True)
@mute.command(name="server", aliases=["guild"])
@commands.guild_only()
@commands.bot_has_permissions(manage_roles=True)
@checks.mod_or_permissions(administrator=True)
async def guild_mute(self, ctx: commands.Context, user: discord.Member, *, reason: str = None):
"""Mutes user in the server"""
author = ctx.message.author
guild = ctx.guild
user_voice_state = user.voice
if reason is None:
audit_reason = "server mute requested by {author} (ID {author.id})".format(
author=author
@ -1018,10 +1025,10 @@ class Mod(commands.Cog):
perms_cache = await self.settings.member(user).perms_cache()
if overwrites.send_messages is False or permissions.send_messages is False:
return False, mute_unmute_issues["already_muted"]
return False, T_(mute_unmute_issues["already_muted"])
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
return False, mute_unmute_issues["hierarchy_problem"]
return False, T_(mute_unmute_issues["hierarchy_problem"])
perms_cache[str(channel.id)] = {
"send_messages": overwrites.send_messages,
@ -1031,28 +1038,27 @@ class Mod(commands.Cog):
try:
await channel.set_permissions(user, overwrite=overwrites, reason=reason)
except discord.Forbidden:
return False, mute_unmute_issues["permissions_issue"]
return False, T_(mute_unmute_issues["permissions_issue"])
else:
await self.settings.member(user).perms_cache.set(perms_cache)
return True, None
@commands.group()
@commands.guild_only()
@commands.bot_has_permissions(manage_roles=True)
@checks.mod_or_permissions(manage_channel=True)
async def unmute(self, ctx: commands.Context):
"""Unmutes user in the channel/server
Defaults to channel"""
"""Unmute users."""
pass
@unmute.command(name="voice")
@commands.guild_only()
@mod_or_voice_permissions(mute_members=True)
@bot_has_voice_permissions(mute_members=True)
async def voice_unmute(
async def unmute_voice(
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
):
"""Unmutes the user in a voice channel"""
"""Unmute a user in their current voice channel."""
user_voice_state = user.voice
if user_voice_state:
channel = user_voice_state.channel
@ -1093,11 +1099,12 @@ class Mod(commands.Cog):
@checks.mod_or_permissions(administrator=True)
@unmute.command(name="channel")
@commands.bot_has_permissions(manage_roles=True)
@commands.guild_only()
async def channel_unmute(
async def unmute_channel(
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
):
"""Unmutes user in the current channel"""
"""Unmute a user in the current channel."""
channel = ctx.channel
author = ctx.author
guild = ctx.guild
@ -1125,14 +1132,14 @@ class Mod(commands.Cog):
@checks.mod_or_permissions(administrator=True)
@unmute.command(name="server", aliases=["guild"])
@commands.bot_has_permissions(manage_roles=True)
@commands.guild_only()
async def guild_unmute(
async def unmute_guild(
self, ctx: commands.Context, user: discord.Member, *, reason: str = None
):
"""Unmutes user in the server"""
"""Unmute a user in the current server."""
guild = ctx.guild
author = ctx.author
channel = ctx.channel
unmute_success = []
for channel in guild.channels:
@ -1172,10 +1179,10 @@ class Mod(commands.Cog):
perms_cache = await self.settings.member(user).perms_cache()
if overwrites.send_messages or permissions.send_messages:
return False, mute_unmute_issues["already_unmuted"]
return False, T_(mute_unmute_issues["already_unmuted"])
elif not await is_allowed_by_hierarchy(self.bot, self.settings, guild, author, user):
return False, mute_unmute_issues["hierarchy_problem"]
return False, T_(mute_unmute_issues["hierarchy_problem"])
if channel.id in perms_cache:
old_values = perms_cache[channel.id]
@ -1190,9 +1197,11 @@ class Mod(commands.Cog):
if not is_empty:
await channel.set_permissions(user, overwrite=overwrites)
else:
await channel.set_permissions(user, overwrite=None)
await channel.set_permissions(
user, overwrite=cast(discord.PermissionOverwrite, None)
)
except discord.Forbidden:
return False, mute_unmute_issues["permissions_issue"]
return False, T_(mute_unmute_issues["permissions_issue"])
else:
try:
del perms_cache[channel.id]
@ -1206,15 +1215,16 @@ class Mod(commands.Cog):
@commands.guild_only()
@checks.admin_or_permissions(manage_channels=True)
async def ignore(self, ctx: commands.Context):
"""Adds servers/channels to ignorelist"""
"""Add servers or channels to the ignore list."""
if ctx.invoked_subcommand is None:
await ctx.send(await self.count_ignored())
@ignore.command(name="channel")
async def ignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None):
"""Ignores channel
"""Ignore commands in the channel.
Defaults to current one"""
Defaults to the current channel.
"""
if not channel:
channel = ctx.channel
if not await self.settings.channel(channel).ignored():
@ -1226,7 +1236,7 @@ class Mod(commands.Cog):
@ignore.command(name="server", aliases=["guild"])
@checks.admin_or_permissions(manage_guild=True)
async def ignore_guild(self, ctx: commands.Context):
"""Ignores current server"""
"""Ignore commands in the current server."""
guild = ctx.guild
if not await self.settings.guild(guild).ignored():
await self.settings.guild(guild).ignored.set(True)
@ -1238,15 +1248,16 @@ class Mod(commands.Cog):
@commands.guild_only()
@checks.admin_or_permissions(manage_channels=True)
async def unignore(self, ctx: commands.Context):
"""Removes servers/channels from ignorelist"""
"""Remove servers or channels from the ignore list."""
if ctx.invoked_subcommand is None:
await ctx.send(await self.count_ignored())
@unignore.command(name="channel")
async def unignore_channel(self, ctx: commands.Context, channel: discord.TextChannel = None):
"""Removes channel from ignore list
"""Remove a channel from ignore the list.
Defaults to current one"""
Defaults to the current channel.
"""
if not channel:
channel = ctx.channel
@ -1259,7 +1270,7 @@ class Mod(commands.Cog):
@unignore.command(name="server", aliases=["guild"])
@checks.admin_or_permissions(manage_guild=True)
async def unignore_guild(self, ctx: commands.Context):
"""Removes current guild from ignore list"""
"""Remove the current server from the ignore list."""
guild = ctx.message.guild
if await self.settings.guild(guild).ignored():
await self.settings.guild(guild).ignored.set(False)
@ -1284,7 +1295,8 @@ class Mod(commands.Cog):
"""Global check to see if a channel or server is ignored.
Any users who have permission to use the `ignore` or `unignore` commands
surpass the check."""
surpass the check.
"""
perms = ctx.channel.permissions_for(ctx.author)
surpass_ignore = (
isinstance(ctx.channel, discord.abc.PrivateChannel)
@ -1300,14 +1312,15 @@ class Mod(commands.Cog):
@commands.command()
@commands.guild_only()
@commands.bot_has_permissions(embed_links=True)
async def userinfo(self, ctx, *, user: discord.Member = None):
"""Shows information for a user.
"""Show information about a user.
This includes fields for status, discord join date, server
join date, voice state and previous names/nicknames.
If the user has none of roles, previous names or previous
nicknames, these fields will be omitted.
If the user has no roles, previous names or previous nicknames,
these fields will be omitted.
"""
author = ctx.author
guild = ctx.guild
@ -1383,14 +1396,11 @@ class Mod(commands.Cog):
else:
data.set_author(name=name)
try:
await ctx.send(embed=data)
except discord.HTTPException:
await ctx.send(_("I need the `Embed links` permission to send this."))
@commands.command()
async def names(self, ctx: commands.Context, user: discord.Member):
"""Show previous names/nicknames of a user"""
"""Show previous names and nicknames of a user."""
names, nicks = await self.get_names_and_nicks(user)
msg = ""
if names:
@ -1433,7 +1443,7 @@ class Mod(commands.Cog):
queue_entry = (guild.id, user.id)
self.unban_queue.append(queue_entry)
try:
await guild.unban(user, reason="Tempban finished")
await guild.unban(user, reason=_("Tempban finished"))
guild_tempbans.remove(uid)
except discord.Forbidden:
self.unban_queue.remove(queue_entry)
@ -1463,12 +1473,12 @@ class Mod(commands.Cog):
guild = message.guild
author = message.author
if await self.settings.guild(guild).ban_mention_spam():
max_mentions = await self.settings.guild(guild).ban_mention_spam()
if max_mentions:
mentions = set(message.mentions)
if len(mentions) >= max_mentions:
try:
await guild.ban(author, reason="Mention spam (Autoban)")
await guild.ban(author, reason=_("Mention spam (Autoban)"))
except discord.HTTPException:
log.info(
"Failed to ban member for mention spam in server {}.".format(guild.id)
@ -1482,7 +1492,7 @@ class Mod(commands.Cog):
"ban",
author,
guild.me,
"Mention spam (Autoban)",
_("Mention spam (Autoban)"),
until=None,
channel=None,
)
@ -1495,6 +1505,7 @@ class Mod(commands.Cog):
async def on_command_completion(self, ctx: commands.Context):
await self._delete_delay(ctx)
# noinspection PyUnusedLocal
async def on_command_error(self, ctx: commands.Context, error):
await self._delete_delay(ctx)
@ -1511,11 +1522,9 @@ class Mod(commands.Cog):
return
async def _delete_helper(m):
try:
with contextlib.suppress(discord.HTTPException):
await m.delete()
log.debug("Deleted command msg {}".format(m.id))
except:
pass # We don't really care if it fails or not
await asyncio.sleep(delay)
await _delete_helper(message)
@ -1537,7 +1546,7 @@ class Mod(commands.Cog):
return
deleted = await self.check_duplicates(message)
if not deleted:
deleted = await self.check_mention_spam(message)
await self.check_mention_spam(message)
async def on_member_ban(self, guild: discord.Guild, member: discord.Member):
if (guild.id, member.id) in self.ban_queue:
@ -1577,7 +1586,8 @@ class Mod(commands.Cog):
except RuntimeError as e:
print(e)
async def on_modlog_case_create(self, case: modlog.Case):
@staticmethod
async def on_modlog_case_create(case: modlog.Case):
"""
An event for modlog case creation
"""
@ -1592,7 +1602,8 @@ class Mod(commands.Cog):
msg = await mod_channel.send(case_content)
await case.edit({"message": msg})
async def on_modlog_case_edit(self, case: modlog.Case):
@staticmethod
async def on_modlog_case_edit(case: modlog.Case):
"""
Event for modlog case edits
"""
@ -1605,7 +1616,10 @@ class Mod(commands.Cog):
else:
await case.message.edit(content=case_content)
async def get_audit_entry_info(self, guild: discord.Guild, action: int, target):
@classmethod
async def get_audit_entry_info(
cls, guild: discord.Guild, action: discord.AuditLogAction, target
):
"""Get info about an audit log entry.
Parameters
@ -1625,14 +1639,15 @@ class Mod(commands.Cog):
if the audit log entry could not be found.
"""
try:
entry = await self.get_audit_log_entry(guild, action=action, target=target)
entry = await cls.get_audit_log_entry(guild, action=action, target=target)
except discord.HTTPException:
entry = None
if entry is None:
return None, None, None
return entry.user, entry.reason, entry.created_at
async def get_audit_log_entry(self, guild: discord.Guild, action: int, target):
@staticmethod
async def get_audit_log_entry(guild: discord.Guild, action: discord.AuditLogAction, target):
"""Get an audit log entry.
Any exceptions encountered when looking through the audit log will be
@ -1686,12 +1701,16 @@ class Mod(commands.Cog):
return [p for p in iter(overwrites)] == [p for p in iter(discord.PermissionOverwrite())]
_ = lambda s: s
mute_unmute_issues = {
"already_muted": "That user can't send messages in this channel.",
"already_unmuted": "That user isn't muted in this channel!",
"hierarchy_problem": "I cannot let you do that. You are not higher than "
"the user in the role hierarchy.",
"permissions_issue": "Failed to mute user. I need the manage roles "
"already_muted": _("That user can't send messages in this channel."),
"already_unmuted": _("That user isn't muted in this channel!"),
"hierarchy_problem": _(
"I cannot let you do that. You are not higher than " "the user in the role hierarchy."
),
"permissions_issue": _(
"Failed to mute user. I need the manage roles "
"permission and the user I'm muting must be "
"lower than myself in the role hierarchy.",
"lower than myself in the role hierarchy."
),
}

View File

@ -1,6 +1,6 @@
import datetime
import os
from typing import Union, List
from typing import Union, List, Optional
import discord
@ -296,12 +296,20 @@ async def transfer_credits(from_: discord.Member, to: discord.Member, amount: in
return await deposit_credits(to, amount)
async def wipe_bank():
"""Delete all accounts from the bank."""
async def wipe_bank(guild: Optional[discord.Guild] = None) -> None:
"""Delete all accounts from the bank.
Parameters
----------
guild : discord.Guild
The guild to clear accounts for. If unsupplied and the bank is
per-server, all accounts in every guild will be wiped.
"""
if await is_global():
await _conf.clear_all_users()
else:
await _conf.clear_all_members()
await _conf.clear_all_members(guild)
async def get_leaderboard(positions: int = None, guild: discord.Guild = None) -> List[tuple]:

View File

@ -838,7 +838,7 @@ class Config:
"""
return self._get_base_group(self.ROLE, role.id)
def user(self, user: discord.User) -> Group:
def user(self, user: discord.abc.User) -> Group:
"""Returns a `Group` for the given user.
Parameters

View File

@ -1,5 +1,7 @@
import os
import re
from pathlib import Path
from typing import Callable, Union
from . import commands
@ -113,9 +115,9 @@ def _normalize(string, remove_newline=False):
ends_with_space = s[-1] in " \n\t\r"
if remove_newline:
newline_re = re.compile("[\r\n]+")
s = " ".join(filter(bool, newline_re.split(s)))
s = " ".join(filter(bool, s.split("\t")))
s = " ".join(filter(bool, s.split(" ")))
s = " ".join(filter(None, newline_re.split(s)))
s = " ".join(filter(None, s.split("\t")))
s = " ".join(filter(None, s.split(" ")))
if starts_with_space:
s = " " + s
if ends_with_space:
@ -149,10 +151,10 @@ def get_locale_path(cog_folder: Path, extension: str) -> Path:
return cog_folder / "locales" / "{}.{}".format(get_locale(), extension)
class Translator:
class Translator(Callable[[str], str]):
"""Function to get translated strings at runtime."""
def __init__(self, name, file_location):
def __init__(self, name: str, file_location: Union[str, Path, os.PathLike]):
"""
Initializes an internationalization object.
@ -173,7 +175,7 @@ class Translator:
self.load_translations()
def __call__(self, untranslated: str):
def __call__(self, untranslated: str) -> str:
"""Translate the given string.
This will look for the string in the translator's :code:`.pot` file,