mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 03:08:55 -05:00
Merge branch 'V3/release/3.0.0' into V3/develop
# Conflicts: # redbot/cogs/audio/audio.py
This commit is contained in:
commit
bdcb69ad37
@ -1285,6 +1285,9 @@ class Audio(commands.Cog):
|
||||
url_check = self._url_check(track["info"]["uri"])
|
||||
if not url_check:
|
||||
continue
|
||||
if track["info"]["uri"].startswith("localtracks/"):
|
||||
if not os.path.isfile(track["info"]["uri"]):
|
||||
continue
|
||||
player.add(author_obj, lavalink.rest_api.Track(data=track))
|
||||
track_count = track_count + 1
|
||||
embed = discord.Embed(
|
||||
@ -2005,6 +2008,7 @@ class Audio(commands.Cog):
|
||||
async def seek(self, ctx, seconds: int = 30):
|
||||
"""Seek ahead or behind on a track by seconds."""
|
||||
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
|
||||
vote_enabled = await self.config.guild(ctx.guild).vote_enabled()
|
||||
if not self._player_check(ctx):
|
||||
return await self._embed_msg(ctx, _("Nothing playing."))
|
||||
player = lavalink.get_player(ctx.guild.id)
|
||||
@ -2017,6 +2021,13 @@ class Audio(commands.Cog):
|
||||
ctx, ctx.author
|
||||
):
|
||||
return await self._embed_msg(ctx, _("You need the DJ role to use seek."))
|
||||
if vote_enabled:
|
||||
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 - vote to skip instead.")
|
||||
)
|
||||
if player.current:
|
||||
if player.current.is_stream:
|
||||
return await self._embed_msg(ctx, _("Can't seek on a stream."))
|
||||
|
||||
@ -71,13 +71,19 @@ async def get_java_version(loop) -> _JavaVersion:
|
||||
# ... version "MAJOR.MINOR.PATCH[_BUILD]" ...
|
||||
# ...
|
||||
# We only care about the major and minor parts though.
|
||||
version_line_re = re.compile(r'version "(?P<major>\d+).(?P<minor>\d+).\d+(?:_\d+)?"')
|
||||
version_line_re = re.compile(
|
||||
r'version "(?P<major>\d+).(?P<minor>\d+).\d+(?:_\d+)?(?:-[A-Za-z0-9]+)?"'
|
||||
)
|
||||
short_version_re = re.compile(r'version "(?P<major>\d+)"')
|
||||
|
||||
lines = version_info.splitlines()
|
||||
for line in lines:
|
||||
match = version_line_re.search(line)
|
||||
short_match = short_version_re.search(line)
|
||||
if match:
|
||||
return int(match["major"]), int(match["minor"])
|
||||
elif short_match:
|
||||
return int(short_match["major"]), 0
|
||||
|
||||
raise RuntimeError(
|
||||
"The output of `java -version` was unexpected. Please report this issue on Red's "
|
||||
|
||||
@ -133,8 +133,6 @@ class Cleanup(commands.Cog):
|
||||
def check(m):
|
||||
if text in m.content:
|
||||
return True
|
||||
elif m == ctx.message:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@ -145,6 +143,7 @@ class Cleanup(commands.Cog):
|
||||
before=ctx.message,
|
||||
delete_pinned=delete_pinned,
|
||||
)
|
||||
to_delete.append(ctx.message)
|
||||
|
||||
reason = "{}({}) deleted {} messages containing '{}' in channel {}.".format(
|
||||
author.name, author.id, len(to_delete), text, channel.id
|
||||
@ -188,8 +187,6 @@ class Cleanup(commands.Cog):
|
||||
def check(m):
|
||||
if m.author.id == _id:
|
||||
return True
|
||||
elif m == ctx.message:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
@ -200,6 +197,8 @@ class Cleanup(commands.Cog):
|
||||
before=ctx.message,
|
||||
delete_pinned=delete_pinned,
|
||||
)
|
||||
to_delete.append(ctx.message)
|
||||
|
||||
reason = (
|
||||
"{}({}) deleted {} messages "
|
||||
" made by {}({}) in channel {}."
|
||||
@ -231,6 +230,7 @@ class Cleanup(commands.Cog):
|
||||
to_delete = await self.get_messages_for_deletion(
|
||||
channel=channel, number=None, after=after, delete_pinned=delete_pinned
|
||||
)
|
||||
to_delete.append(ctx.message)
|
||||
|
||||
reason = "{}({}) deleted {} messages in channel {}.".format(
|
||||
author.name, author.id, len(to_delete), channel.name
|
||||
@ -263,6 +263,7 @@ class Cleanup(commands.Cog):
|
||||
to_delete = await self.get_messages_for_deletion(
|
||||
channel=channel, number=number, before=before, delete_pinned=delete_pinned
|
||||
)
|
||||
to_delete.append(ctx.message)
|
||||
|
||||
reason = "{}({}) deleted {} messages in channel {}.".format(
|
||||
author.name, author.id, len(to_delete), channel.name
|
||||
|
||||
@ -502,7 +502,7 @@ class Downloader(commands.Cog):
|
||||
if isinstance(cog_installable, Installable):
|
||||
made_by = ", ".join(cog_installable.author) or _("Missing from info.json")
|
||||
repo = self._repo_manager.get_repo(cog_installable.repo_name)
|
||||
repo_url = repo.url
|
||||
repo_url = _("Missing from installed repos") if repo is None else repo.url
|
||||
cog_name = cog_installable.name
|
||||
else:
|
||||
made_by = "26 & co."
|
||||
|
||||
@ -388,7 +388,7 @@ class Economy(commands.Cog):
|
||||
@guild_only_check()
|
||||
async def payouts(self, ctx: commands.Context):
|
||||
"""Show the payouts for the slot machine."""
|
||||
await ctx.author.send(SLOT_PAYOUTS_MSG())
|
||||
await ctx.author.send(SLOT_PAYOUTS_MSG)
|
||||
|
||||
@commands.command()
|
||||
@guild_only_check()
|
||||
|
||||
@ -28,7 +28,7 @@ class RPSParser:
|
||||
elif argument == "scissors":
|
||||
self.choice = RPS.scissors
|
||||
else:
|
||||
raise ValueError
|
||||
self.choice = None
|
||||
|
||||
|
||||
@cog_i18n(_)
|
||||
@ -121,6 +121,8 @@ class General(commands.Cog):
|
||||
"""Play Rock Paper Scissors."""
|
||||
author = ctx.author
|
||||
player_choice = your_choice.choice
|
||||
if not player_choice:
|
||||
return await ctx.send("This isn't a valid option. Try rock, paper, or scissors.")
|
||||
red_choice = choice((RPS.rock, RPS.paper, RPS.scissors))
|
||||
cond = {
|
||||
(RPS.rock, RPS.paper): False,
|
||||
@ -263,12 +265,13 @@ class General(commands.Cog):
|
||||
|
||||
except aiohttp.ClientError:
|
||||
await ctx.send(
|
||||
_("No Urban dictionary entries were found, or there was an error in the process")
|
||||
_("No Urban Dictionary entries were found, or there was an error in the process.")
|
||||
)
|
||||
return
|
||||
|
||||
if data.get("error") != 404:
|
||||
|
||||
if not data["list"]:
|
||||
return await ctx.send(_("No Urban Dictionary entries were found."))
|
||||
if await ctx.embed_requested():
|
||||
# a list of embeds
|
||||
embeds = []
|
||||
@ -303,14 +306,14 @@ class General(commands.Cog):
|
||||
else:
|
||||
messages = []
|
||||
for ud in data["list"]:
|
||||
ud.set_default("example", "N/A")
|
||||
ud.setdefault("example", "N/A")
|
||||
description = _("{definition}\n\n**Example:** {example}").format(**ud)
|
||||
if len(description) > 2048:
|
||||
description = "{}...".format(description[:2045])
|
||||
|
||||
message = _(
|
||||
"<{permalink}>\n {word} by {author}\n\n{description}\n\n"
|
||||
"{thumbs_down} Down / {thumbs_up} Up, Powered by urban dictionary"
|
||||
"{thumbs_down} Down / {thumbs_up} Up, Powered by Urban Dictionary."
|
||||
).format(word=ud.pop("word").capitalize(), description=description, **ud)
|
||||
messages.append(message)
|
||||
|
||||
@ -325,6 +328,5 @@ class General(commands.Cog):
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
_("No Urban dictionary entries were found, or there was an error in the process.")
|
||||
_("No Urban Dictionary entries were found, or there was an error in the process.")
|
||||
)
|
||||
return
|
||||
|
||||
@ -311,13 +311,15 @@ class Mod(commands.Cog):
|
||||
if not cur_setting:
|
||||
await self.settings.guild(guild).reinvite_on_unban.set(True)
|
||||
await ctx.send(
|
||||
_("Users unbanned with {command} will be reinvited.").format(f"{ctx.prefix}unban")
|
||||
_("Users unbanned with {command} will be reinvited.").format(
|
||||
command=f"{ctx.prefix}unban"
|
||||
)
|
||||
)
|
||||
else:
|
||||
await self.settings.guild(guild).reinvite_on_unban.set(False)
|
||||
await ctx.send(
|
||||
_("Users unbanned with {command} will not be reinvited.").format(
|
||||
f"{ctx.prefix}unban"
|
||||
command=f"{ctx.prefix}unban"
|
||||
)
|
||||
)
|
||||
|
||||
@ -864,20 +866,46 @@ class Mod(commands.Cog):
|
||||
@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=""):
|
||||
async def rename(self, ctx: commands.Context, user: discord.Member, *, nickname: str = ""):
|
||||
"""Change a user's nickname.
|
||||
|
||||
Leaving the nickname empty will remove it.
|
||||
"""
|
||||
nickname = nickname.strip()
|
||||
if nickname == "":
|
||||
me = cast(discord.Member, ctx.me)
|
||||
if not nickname:
|
||||
nickname = None
|
||||
await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
|
||||
await ctx.send("Done.")
|
||||
elif not 2 <= len(nickname) <= 32:
|
||||
await ctx.send(_("Nicknames must be between 2 and 32 characters long."))
|
||||
return
|
||||
if not (
|
||||
(me.guild_permissions.manage_nicknames or me.guild_permissions.administrator)
|
||||
and me.top_role > user.top_role
|
||||
and user != ctx.guild.owner
|
||||
):
|
||||
await ctx.send(
|
||||
_(
|
||||
"I do not have permission to rename that member. They may be higher than or "
|
||||
"equal to me in the role hierarchy."
|
||||
)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
await user.edit(reason=get_audit_reason(ctx.author, None), nick=nickname)
|
||||
except discord.Forbidden:
|
||||
# Just in case we missed something in the permissions check above
|
||||
await ctx.send(_("I do not have permission to rename that member."))
|
||||
except discord.HTTPException as exc:
|
||||
if exc.status == 400: # BAD REQUEST
|
||||
await ctx.send(_("That nickname is invalid."))
|
||||
else:
|
||||
await ctx.send(_("An unexpected error has occured."))
|
||||
else:
|
||||
await ctx.send(_("Done."))
|
||||
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@checks.mod_or_permissions(manage_channel=True)
|
||||
@checks.mod_or_permissions(manage_channels=True)
|
||||
async def mute(self, ctx: commands.Context):
|
||||
"""Mute users."""
|
||||
pass
|
||||
@ -1033,7 +1061,7 @@ class Mod(commands.Cog):
|
||||
@commands.group()
|
||||
@commands.guild_only()
|
||||
@commands.bot_has_permissions(manage_roles=True)
|
||||
@checks.mod_or_permissions(manage_channel=True)
|
||||
@checks.mod_or_permissions(manage_channels=True)
|
||||
async def unmute(self, ctx: commands.Context):
|
||||
"""Unmute users."""
|
||||
pass
|
||||
@ -1306,8 +1334,8 @@ class Mod(commands.Cog):
|
||||
user = author
|
||||
|
||||
# A special case for a special someone :^)
|
||||
special_date = datetime(2016, 1, 10, 6, 8, 4, 443000)
|
||||
is_special = user.id == 96130341705637888 and guild.id == 133049272517001216
|
||||
special_date = datetime(2016, 1, 10, 6, 8, 4, 443_000)
|
||||
is_special = user.id == 96_130_341_705_637_888 and guild.id == 133_049_272_517_001_216
|
||||
|
||||
roles = sorted(user.roles)[1:]
|
||||
names, nicks = await self.get_names_and_nicks(user)
|
||||
@ -1567,8 +1595,9 @@ class Mod(commands.Cog):
|
||||
"""
|
||||
An event for modlog case creation
|
||||
"""
|
||||
mod_channel = await modlog.get_modlog_channel(case.guild)
|
||||
if mod_channel is None:
|
||||
try:
|
||||
mod_channel = await modlog.get_modlog_channel(case.guild)
|
||||
except RuntimeError:
|
||||
return
|
||||
use_embeds = await case.bot.embed_requested(mod_channel, case.guild.me)
|
||||
case_content = await case.message_content(use_embeds)
|
||||
|
||||
@ -1,10 +1,133 @@
|
||||
from typing import NamedTuple, Union, Optional, cast, Type
|
||||
import itertools
|
||||
import re
|
||||
from typing import NamedTuple, Union, Optional
|
||||
|
||||
import discord
|
||||
|
||||
from redbot.core import commands
|
||||
from redbot.core.i18n import Translator
|
||||
|
||||
_ = Translator("PermissionsConverters", __file__)
|
||||
|
||||
MENTION_RE = re.compile(r"^<?(?:(?:@[!&]?)?|#)(\d{15,21})>?$")
|
||||
|
||||
|
||||
def _match_id(arg: str) -> Optional[int]:
|
||||
m = MENTION_RE.match(arg)
|
||||
if m:
|
||||
return int(m.group(1))
|
||||
|
||||
|
||||
class GlobalUniqueObjectFinder(commands.Converter):
|
||||
async def convert(
|
||||
self, ctx: commands.Context, arg: str
|
||||
) -> Union[discord.Guild, discord.abc.GuildChannel, discord.abc.User, discord.Role]:
|
||||
bot: commands.Bot = ctx.bot
|
||||
_id = _match_id(arg)
|
||||
|
||||
if _id is not None:
|
||||
guild: discord.Guild = bot.get_guild(_id)
|
||||
if guild is not None:
|
||||
return guild
|
||||
channel: discord.abc.GuildChannel = bot.get_channel(_id)
|
||||
if channel is not None:
|
||||
return channel
|
||||
|
||||
user: discord.User = bot.get_user(_id)
|
||||
if user is not None:
|
||||
return user
|
||||
|
||||
for guild in bot.guilds:
|
||||
role: discord.Role = guild.get_role(_id)
|
||||
if role is not None:
|
||||
return role
|
||||
|
||||
objects = itertools.chain(
|
||||
bot.get_all_channels(),
|
||||
bot.users,
|
||||
bot.guilds,
|
||||
*(filter(lambda r: not r.is_default(), guild.roles) for guild in bot.guilds),
|
||||
)
|
||||
|
||||
maybe_matches = []
|
||||
for obj in objects:
|
||||
if obj.name == arg or str(obj) == arg:
|
||||
maybe_matches.append(obj)
|
||||
|
||||
if ctx.guild is not None:
|
||||
for member in ctx.guild.members:
|
||||
if member.nick == arg and not any(obj.id == member.id for obj in maybe_matches):
|
||||
maybe_matches.append(member)
|
||||
|
||||
if not maybe_matches:
|
||||
raise commands.BadArgument(
|
||||
_(
|
||||
'"{arg}" was not found. It must be the ID, mention, or name of a server, '
|
||||
"channel, user or role which the bot can see."
|
||||
).format(arg=arg)
|
||||
)
|
||||
elif len(maybe_matches) == 1:
|
||||
return maybe_matches[0]
|
||||
else:
|
||||
raise commands.BadArgument(
|
||||
_(
|
||||
'"{arg}" does not refer to a unique server, channel, user or role. Please use '
|
||||
"the ID for whatever/whoever you're trying to specify, or mention it/them."
|
||||
).format(arg=arg)
|
||||
)
|
||||
|
||||
|
||||
class GuildUniqueObjectFinder(commands.Converter):
|
||||
async def convert(
|
||||
self, ctx: commands.Context, arg: str
|
||||
) -> Union[discord.abc.GuildChannel, discord.Member, discord.Role]:
|
||||
guild: discord.Guild = ctx.guild
|
||||
_id = _match_id(arg)
|
||||
|
||||
if _id is not None:
|
||||
channel: discord.abc.GuildChannel = guild.get_channel(_id)
|
||||
if channel is not None:
|
||||
return channel
|
||||
|
||||
member: discord.Member = guild.get_member(_id)
|
||||
if member is not None:
|
||||
return member
|
||||
|
||||
role: discord.Role = guild.get_role(_id)
|
||||
if role is not None and not role.is_default():
|
||||
return role
|
||||
|
||||
objects = itertools.chain(
|
||||
guild.channels, guild.members, filter(lambda r: not r.is_default(), guild.roles)
|
||||
)
|
||||
|
||||
maybe_matches = []
|
||||
for obj in objects:
|
||||
if obj.name == arg or str(obj) == arg:
|
||||
maybe_matches.append(obj)
|
||||
try:
|
||||
if obj.nick == arg:
|
||||
maybe_matches.append(obj)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
if not maybe_matches:
|
||||
raise commands.BadArgument(
|
||||
_(
|
||||
'"{arg}" was not found. It must be the ID, mention, or name of a channel, '
|
||||
"user or role in this server."
|
||||
).format(arg=arg)
|
||||
)
|
||||
elif len(maybe_matches) == 1:
|
||||
return maybe_matches[0]
|
||||
else:
|
||||
raise commands.BadArgument(
|
||||
_(
|
||||
'"{arg}" does not refer to a unique channel, user or role. Please use the ID '
|
||||
"for whatever/whoever you're trying to specify, or mention it/them."
|
||||
).format(arg=arg)
|
||||
)
|
||||
|
||||
|
||||
class CogOrCommand(NamedTuple):
|
||||
type: str
|
||||
|
||||
@ -14,7 +14,13 @@ from redbot.core.utils.chat_formatting import box
|
||||
from redbot.core.utils.menus import start_adding_reactions
|
||||
from redbot.core.utils.predicates import ReactionPredicate, MessagePredicate
|
||||
|
||||
from .converters import CogOrCommand, RuleType, ClearableRuleType
|
||||
from .converters import (
|
||||
CogOrCommand,
|
||||
RuleType,
|
||||
ClearableRuleType,
|
||||
GuildUniqueObjectFinder,
|
||||
GlobalUniqueObjectFinder,
|
||||
)
|
||||
|
||||
_ = Translator("Permissions", __file__)
|
||||
|
||||
@ -142,23 +148,20 @@ class Permissions(commands.Cog):
|
||||
if not command:
|
||||
return await ctx.send_help()
|
||||
|
||||
message = copy(ctx.message)
|
||||
message.author = user
|
||||
message.content = "{}{}".format(ctx.prefix, command)
|
||||
fake_message = copy(ctx.message)
|
||||
fake_message.author = user
|
||||
fake_message.content = "{}{}".format(ctx.prefix, command)
|
||||
|
||||
com = ctx.bot.get_command(command)
|
||||
if com is None:
|
||||
out = _("No such command")
|
||||
else:
|
||||
fake_context = await ctx.bot.get_context(fake_message)
|
||||
try:
|
||||
testcontext = await ctx.bot.get_context(message, cls=commands.Context)
|
||||
to_check = [*reversed(com.parents)] + [com]
|
||||
can = False
|
||||
for cmd in to_check:
|
||||
can = await cmd.can_run(testcontext)
|
||||
if can is False:
|
||||
break
|
||||
except commands.CheckFailure:
|
||||
can = await com.can_run(
|
||||
fake_context, check_all_parents=True, change_permission_state=False
|
||||
)
|
||||
except commands.CommandError:
|
||||
can = False
|
||||
|
||||
out = (
|
||||
@ -275,7 +278,7 @@ class Permissions(commands.Cog):
|
||||
ctx: commands.Context,
|
||||
allow_or_deny: RuleType,
|
||||
cog_or_command: CogOrCommand,
|
||||
who_or_what: commands.GlobalPermissionModel,
|
||||
who_or_what: GlobalUniqueObjectFinder,
|
||||
):
|
||||
"""Add a global rule to a command.
|
||||
|
||||
@ -303,7 +306,7 @@ class Permissions(commands.Cog):
|
||||
ctx: commands.Context,
|
||||
allow_or_deny: RuleType,
|
||||
cog_or_command: CogOrCommand,
|
||||
who_or_what: commands.GuildPermissionModel,
|
||||
who_or_what: GuildUniqueObjectFinder,
|
||||
):
|
||||
"""Add a rule to a command in this server.
|
||||
|
||||
@ -328,7 +331,7 @@ class Permissions(commands.Cog):
|
||||
self,
|
||||
ctx: commands.Context,
|
||||
cog_or_command: CogOrCommand,
|
||||
who_or_what: commands.GlobalPermissionModel,
|
||||
who_or_what: GlobalUniqueObjectFinder,
|
||||
):
|
||||
"""Remove a global rule from a command.
|
||||
|
||||
@ -351,7 +354,7 @@ class Permissions(commands.Cog):
|
||||
ctx: commands.Context,
|
||||
cog_or_command: CogOrCommand,
|
||||
*,
|
||||
who_or_what: commands.GuildPermissionModel,
|
||||
who_or_what: GuildUniqueObjectFinder,
|
||||
):
|
||||
"""Remove a server rule from a command.
|
||||
|
||||
|
||||
@ -316,7 +316,7 @@ class Reports(commands.Cog):
|
||||
self.tunnel_store[k]["msgs"] = msgs
|
||||
|
||||
@commands.guild_only()
|
||||
@checks.mod_or_permissions(manage_members=True)
|
||||
@checks.mod_or_permissions(manage_roles=True)
|
||||
@report.command(name="interact")
|
||||
async def response(self, ctx, ticket_number: int):
|
||||
"""Open a message tunnel.
|
||||
|
||||
@ -28,7 +28,7 @@ from . import streamtypes as _streamtypes
|
||||
from collections import defaultdict
|
||||
import asyncio
|
||||
import re
|
||||
from typing import Optional, List
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
CHECK_DELAY = 60
|
||||
|
||||
@ -320,6 +320,7 @@ class Streams(commands.Cog):
|
||||
@commands.group()
|
||||
@checks.mod()
|
||||
async def streamset(self, ctx: commands.Context):
|
||||
"""Set tokens for accessing streams."""
|
||||
pass
|
||||
|
||||
@streamset.command()
|
||||
@ -396,9 +397,6 @@ class Streams(commands.Cog):
|
||||
async def role(self, ctx: commands.Context, *, role: discord.Role):
|
||||
"""Toggle a role mention."""
|
||||
current_setting = await self.db.role(role).mention()
|
||||
if not role.mentionable:
|
||||
await ctx.send("That role is not mentionable!")
|
||||
return
|
||||
if current_setting:
|
||||
await self.db.role(role).mention.set(False)
|
||||
await ctx.send(
|
||||
@ -408,11 +406,17 @@ class Streams(commands.Cog):
|
||||
)
|
||||
else:
|
||||
await self.db.role(role).mention.set(True)
|
||||
await ctx.send(
|
||||
_(
|
||||
"When a stream or community is live, `@\u200b{role.name}` will be mentioned."
|
||||
).format(role=role)
|
||||
)
|
||||
msg = _(
|
||||
"When a stream or community is live, `@\u200b{role.name}` will be mentioned."
|
||||
).format(role=role)
|
||||
if not role.mentionable:
|
||||
msg += " " + _(
|
||||
"Since the role is not mentionable, it will be momentarily made mentionable "
|
||||
"when announcing a streamalert. Please make sure I have the correct "
|
||||
"permissions to manage this role, or else members of this role won't receive "
|
||||
"a notification."
|
||||
)
|
||||
await ctx.send(msg)
|
||||
|
||||
@streamset.command()
|
||||
@commands.guild_only()
|
||||
@ -535,30 +539,46 @@ class Streams(commands.Cog):
|
||||
continue
|
||||
for channel_id in stream.channels:
|
||||
channel = self.bot.get_channel(channel_id)
|
||||
mention_str = await self._get_mention_str(channel.guild)
|
||||
mention_str, edited_roles = await self._get_mention_str(channel.guild)
|
||||
|
||||
if mention_str:
|
||||
content = _("{mention}, {stream.name} is live!").format(
|
||||
mention=mention_str, stream=stream
|
||||
)
|
||||
else:
|
||||
content = _("{stream.name} is live!").format(stream=stream.name)
|
||||
content = _("{stream.name} is live!").format(stream=stream)
|
||||
|
||||
m = await channel.send(content, embed=embed)
|
||||
stream._messages_cache.append(m)
|
||||
if edited_roles:
|
||||
for role in edited_roles:
|
||||
await role.edit(mentionable=False)
|
||||
await self.save_streams()
|
||||
|
||||
async def _get_mention_str(self, guild: discord.Guild):
|
||||
async def _get_mention_str(self, guild: discord.Guild) -> Tuple[str, List[discord.Role]]:
|
||||
"""Returns a 2-tuple with the string containing the mentions, and a list of
|
||||
all roles which need to have their `mentionable` property set back to False.
|
||||
"""
|
||||
settings = self.db.guild(guild)
|
||||
mentions = []
|
||||
edited_roles = []
|
||||
if await settings.mention_everyone():
|
||||
mentions.append("@everyone")
|
||||
if await settings.mention_here():
|
||||
mentions.append("@here")
|
||||
can_manage_roles = guild.me.guild_permissions.manage_roles
|
||||
for role in guild.roles:
|
||||
if await self.db.role(role).mention():
|
||||
if can_manage_roles and not role.mentionable:
|
||||
try:
|
||||
await role.edit(mentionable=True)
|
||||
except discord.Forbidden:
|
||||
# Might still be unable to edit role based on hierarchy
|
||||
pass
|
||||
else:
|
||||
edited_roles.append(role)
|
||||
mentions.append(role.mention)
|
||||
return " ".join(mentions)
|
||||
return " ".join(mentions), edited_roles
|
||||
|
||||
async def check_communities(self):
|
||||
for community in self.communities:
|
||||
@ -589,12 +609,15 @@ class Streams(commands.Cog):
|
||||
emb = await community.make_embed(streams)
|
||||
chn_msg = [m for m in community._messages_cache if m.channel == chn]
|
||||
if not chn_msg:
|
||||
mentions = await self._get_mention_str(chn.guild)
|
||||
mentions, roles = await self._get_mention_str(chn.guild)
|
||||
if mentions:
|
||||
msg = await chn.send(mentions, embed=emb)
|
||||
else:
|
||||
msg = await chn.send(embed=emb)
|
||||
community._messages_cache.append(msg)
|
||||
if roles:
|
||||
for role in roles:
|
||||
await role.edit(mentionable=False)
|
||||
await self.save_communities()
|
||||
else:
|
||||
chn_msg = sorted(chn_msg, key=lambda x: x.created_at, reverse=True)[0]
|
||||
|
||||
@ -114,7 +114,7 @@ class TriviaSession:
|
||||
async with self.ctx.typing():
|
||||
await asyncio.sleep(3)
|
||||
self.count += 1
|
||||
msg = bold(_("**Question number {num}!").format(num=self.count)) + "\n\n" + question
|
||||
msg = bold(_("Question number {num}!").format(num=self.count)) + "\n\n" + question
|
||||
await self.ctx.send(msg)
|
||||
continue_ = await self.wait_for_answer(answers, delay, timeout)
|
||||
if continue_ is False:
|
||||
|
||||
@ -111,16 +111,14 @@ class Trivia(commands.Cog):
|
||||
await settings.allow_override.set(enabled)
|
||||
if enabled:
|
||||
await ctx.send(
|
||||
_(
|
||||
"Done. Trivia lists can now override the trivia settings for this server."
|
||||
).format(now=enabled)
|
||||
_("Done. Trivia lists can now override the trivia settings for this server.")
|
||||
)
|
||||
else:
|
||||
await ctx.send(
|
||||
_(
|
||||
"Done. Trivia lists can no longer override the trivia settings for this "
|
||||
"server."
|
||||
).format(now=enabled)
|
||||
)
|
||||
)
|
||||
|
||||
@triviaset.command(name="botplays", usage="<true_or_false>")
|
||||
@ -506,7 +504,7 @@ class Trivia(commands.Cog):
|
||||
|
||||
with path.open(encoding="utf-8") as file:
|
||||
try:
|
||||
dict_ = yaml.load(file)
|
||||
dict_ = yaml.safe_load(file)
|
||||
except yaml.error.YAMLError as exc:
|
||||
raise InvalidListError("YAML parsing failed.") from exc
|
||||
else:
|
||||
|
||||
@ -186,13 +186,23 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
async def is_admin(self, member: discord.Member):
|
||||
"""Checks if a member is an admin of their guild."""
|
||||
admin_role = await self.db.guild(member.guild).admin_role()
|
||||
return any(role.id == admin_role for role in member.roles)
|
||||
try:
|
||||
if any(role.id == admin_role for role in member.roles):
|
||||
return True
|
||||
except AttributeError: # someone passed a webhook to this
|
||||
pass
|
||||
return False
|
||||
|
||||
async def is_mod(self, member: discord.Member):
|
||||
"""Checks if a member is a mod or admin of their guild."""
|
||||
mod_role = await self.db.guild(member.guild).mod_role()
|
||||
admin_role = await self.db.guild(member.guild).admin_role()
|
||||
return any(role.id in (mod_role, admin_role) for role in member.roles)
|
||||
try:
|
||||
if any(role.id in (mod_role, admin_role) for role in member.roles):
|
||||
return True
|
||||
except AttributeError: # someone passed a webhook to this
|
||||
pass
|
||||
return False
|
||||
|
||||
async def get_context(self, message, *, cls=commands.Context):
|
||||
return await super().get_context(message, cls=cls)
|
||||
@ -334,8 +344,14 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
|
||||
ids_to_check = [to_check.id]
|
||||
else:
|
||||
author = getattr(to_check, "author", to_check)
|
||||
ids_to_check = [r.id for r in author.roles]
|
||||
ids_to_check.append(author.id)
|
||||
try:
|
||||
ids_to_check = [r.id for r in author.roles]
|
||||
except AttributeError:
|
||||
# webhook messages are a user not member,
|
||||
# cheaper than isinstance
|
||||
return True # webhooks require significant permissions to enable.
|
||||
else:
|
||||
ids_to_check.append(author.id)
|
||||
|
||||
immune_ids = await self.db.guild(guild).autoimmune_ids()
|
||||
|
||||
|
||||
@ -157,12 +157,31 @@ class Command(CogCommandMixin, commands.Command):
|
||||
cmd = cmd.parent
|
||||
return sorted(entries, key=lambda x: len(x.qualified_name), reverse=True)
|
||||
|
||||
async def can_run(self, ctx: "Context") -> bool:
|
||||
# noinspection PyMethodOverriding
|
||||
async def can_run(
|
||||
self,
|
||||
ctx: "Context",
|
||||
*,
|
||||
check_all_parents: bool = False,
|
||||
change_permission_state: bool = False,
|
||||
) -> bool:
|
||||
"""Check if this command can be run in the given context.
|
||||
|
||||
This function first checks if the command can be run using
|
||||
discord.py's method `discord.ext.commands.Command.can_run`,
|
||||
then will return the result of `Requires.verify`.
|
||||
|
||||
Keyword Arguments
|
||||
-----------------
|
||||
check_all_parents : bool
|
||||
If ``True``, this will check permissions for all of this
|
||||
command's parents and its cog as well as the command
|
||||
itself. Defaults to ``False``.
|
||||
change_permission_state : bool
|
||||
Whether or not the permission state should be changed as
|
||||
a result of this call. For most cases this should be
|
||||
``False``. Defaults to ``False``.
|
||||
|
||||
"""
|
||||
ret = await super().can_run(ctx)
|
||||
if ret is False:
|
||||
@ -171,8 +190,21 @@ class Command(CogCommandMixin, commands.Command):
|
||||
# This is so contexts invoking other commands can be checked with
|
||||
# this command as well
|
||||
original_command = ctx.command
|
||||
original_state = ctx.permission_state
|
||||
ctx.command = self
|
||||
|
||||
if check_all_parents is True:
|
||||
# Since we're starting from the beginning, we should reset the state to normal
|
||||
ctx.permission_state = PermState.NORMAL
|
||||
for parent in reversed(self.parents):
|
||||
try:
|
||||
result = await parent.can_run(ctx, change_permission_state=True)
|
||||
except commands.CommandError:
|
||||
result = False
|
||||
|
||||
if result is False:
|
||||
return False
|
||||
|
||||
if self.parent is None and self.instance is not None:
|
||||
# For top-level commands, we need to check the cog's requires too
|
||||
ret = await self.instance.requires.verify(ctx)
|
||||
@ -183,6 +215,17 @@ class Command(CogCommandMixin, commands.Command):
|
||||
return await self.requires.verify(ctx)
|
||||
finally:
|
||||
ctx.command = original_command
|
||||
if not change_permission_state:
|
||||
ctx.permission_state = original_state
|
||||
|
||||
async def _verify_checks(self, ctx):
|
||||
if not self.enabled:
|
||||
raise commands.DisabledCommand(f"{self.name} command is disabled")
|
||||
|
||||
if not (await self.can_run(ctx, change_permission_state=True)):
|
||||
raise commands.CheckFailure(
|
||||
f"The check functions for command {self.qualified_name} failed."
|
||||
)
|
||||
|
||||
async def do_conversion(
|
||||
self, ctx: "Context", converter, argument: str, param: inspect.Parameter
|
||||
@ -238,7 +281,9 @@ class Command(CogCommandMixin, commands.Command):
|
||||
if cmd.hidden:
|
||||
return False
|
||||
try:
|
||||
can_run = await self.can_run(ctx)
|
||||
can_run = await self.can_run(
|
||||
ctx, check_all_parents=True, change_permission_state=False
|
||||
)
|
||||
except commands.CheckFailure:
|
||||
return False
|
||||
else:
|
||||
|
||||
@ -281,12 +281,14 @@ class Requires:
|
||||
|
||||
if isinstance(user_perms, dict):
|
||||
self.user_perms: Optional[discord.Permissions] = discord.Permissions.none()
|
||||
_validate_perms_dict(user_perms)
|
||||
self.user_perms.update(**user_perms)
|
||||
else:
|
||||
self.user_perms = user_perms
|
||||
|
||||
if isinstance(bot_perms, dict):
|
||||
self.bot_perms: discord.Permissions = discord.Permissions.none()
|
||||
_validate_perms_dict(bot_perms)
|
||||
self.bot_perms.update(**bot_perms)
|
||||
else:
|
||||
self.bot_perms = bot_perms
|
||||
@ -311,6 +313,7 @@ class Requires:
|
||||
if user_perms is None:
|
||||
func.requires.user_perms = None
|
||||
else:
|
||||
_validate_perms_dict(user_perms)
|
||||
func.requires.user_perms.update(**user_perms)
|
||||
return func
|
||||
|
||||
@ -449,7 +452,20 @@ class Requires:
|
||||
should_invoke = await self._verify_user(ctx)
|
||||
elif isinstance(next_state, dict):
|
||||
# NORMAL to PASSIVE_ALLOW; should we proceed as normal or transition?
|
||||
next_state = next_state[await self._verify_user(ctx)]
|
||||
# We must check what would happen normally, if no explicit rules were set.
|
||||
default_rule = PermState.NORMAL
|
||||
if ctx.guild is not None:
|
||||
default_rule = self.get_default_guild_rule(guild_id=ctx.guild.id)
|
||||
if default_rule is PermState.NORMAL:
|
||||
default_rule = self.default_global_rule
|
||||
|
||||
if default_rule == PermState.ACTIVE_DENY:
|
||||
would_invoke = False
|
||||
elif default_rule == PermState.ACTIVE_ALLOW:
|
||||
would_invoke = True
|
||||
else:
|
||||
would_invoke = await self._verify_user(ctx)
|
||||
next_state = next_state[would_invoke]
|
||||
|
||||
ctx.permission_state = next_state
|
||||
return should_invoke
|
||||
@ -588,6 +604,7 @@ def bot_has_permissions(**perms: bool):
|
||||
if asyncio.iscoroutinefunction(func):
|
||||
func.__requires_bot_perms__ = perms
|
||||
else:
|
||||
_validate_perms_dict(perms)
|
||||
func.requires.bot_perms.update(**perms)
|
||||
return func
|
||||
|
||||
@ -599,6 +616,8 @@ def has_permissions(**perms: bool):
|
||||
|
||||
This check can be overridden by rules.
|
||||
"""
|
||||
if perms is None:
|
||||
raise TypeError("Must provide at least one keyword argument to has_permissions")
|
||||
return Requires.get_decorator(None, perms)
|
||||
|
||||
|
||||
@ -670,3 +689,20 @@ class _IntKeyDict(Dict[int, _T]):
|
||||
if not isinstance(key, int):
|
||||
raise TypeError("Keys must be of type `int`")
|
||||
return super().__setitem__(key, value)
|
||||
|
||||
|
||||
def _validate_perms_dict(perms: Dict[str, bool]) -> None:
|
||||
for perm, value in perms.items():
|
||||
try:
|
||||
attr = getattr(discord.Permissions, perm)
|
||||
except AttributeError:
|
||||
attr = None
|
||||
|
||||
if attr is None or not isinstance(attr, property):
|
||||
# We reject invalid permissions
|
||||
raise TypeError(f"Unknown permission name '{perm}'")
|
||||
|
||||
if value is not True:
|
||||
# We reject any permission not specified as 'True', since this is the only value which
|
||||
# makes practical sense.
|
||||
raise TypeError(f"Permission {perm} may only be specified as 'True', not {value}")
|
||||
|
||||
@ -468,7 +468,7 @@ class Core(commands.Cog, CoreLogic):
|
||||
|
||||
pred = MessagePredicate.yes_or_no(ctx)
|
||||
try:
|
||||
await self.bot.wait_for("message", check=MessagePredicate.yes_or_no(ctx))
|
||||
await self.bot.wait_for("message", check=pred)
|
||||
except asyncio.TimeoutError:
|
||||
await ctx.send("Response timed out.")
|
||||
return
|
||||
@ -1729,7 +1729,7 @@ class Core(commands.Cog, CoreLogic):
|
||||
await ctx.tick()
|
||||
|
||||
@commands.guild_only()
|
||||
@checks.guildowner_or_permissions(manage_server=True)
|
||||
@checks.guildowner_or_permissions(manage_guild=True)
|
||||
@commands.group(name="autoimmune")
|
||||
async def autoimmune_group(self, ctx: commands.Context):
|
||||
"""
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from copy import deepcopy
|
||||
import hashlib
|
||||
import shutil
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from copy import deepcopy
|
||||
from pathlib import Path
|
||||
|
||||
import appdirs
|
||||
import tempfile
|
||||
from discord.utils import deprecated
|
||||
|
||||
from . import commands
|
||||
from .json_io import JsonIO
|
||||
|
||||
__all__ = [
|
||||
@ -153,124 +153,28 @@ def core_data_path() -> Path:
|
||||
return core_path.resolve()
|
||||
|
||||
|
||||
def _find_data_files(init_location: str) -> (Path, List[Path]):
|
||||
"""
|
||||
Discovers all files in the bundled data folder of an installed cog.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
init_location
|
||||
|
||||
Returns
|
||||
-------
|
||||
(pathlib.Path, list of pathlib.Path)
|
||||
"""
|
||||
init_file = Path(init_location)
|
||||
if not init_file.is_file():
|
||||
return []
|
||||
|
||||
package_folder = init_file.parent.resolve() / "data"
|
||||
if not package_folder.is_dir():
|
||||
return []
|
||||
|
||||
all_files = list(package_folder.rglob("*"))
|
||||
|
||||
return package_folder, [p.resolve() for p in all_files if p.is_file()]
|
||||
|
||||
|
||||
def _compare_and_copy(to_copy: List[Path], bundled_data_dir: Path, cog_data_dir: Path):
|
||||
"""
|
||||
Filters out files from ``to_copy`` that already exist, and are the
|
||||
same, in ``data_dir``. The files that are different are copied into
|
||||
``data_dir``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
to_copy : list of pathlib.Path
|
||||
bundled_data_dir : pathlib.Path
|
||||
cog_data_dir : pathlib.Path
|
||||
"""
|
||||
|
||||
def hash_bytestr_iter(bytesiter, hasher, ashexstr=False):
|
||||
for block in bytesiter:
|
||||
hasher.update(block)
|
||||
return hasher.hexdigest() if ashexstr else hasher.digest()
|
||||
|
||||
def file_as_blockiter(afile, blocksize=65536):
|
||||
with afile:
|
||||
block = afile.read(blocksize)
|
||||
while len(block) > 0:
|
||||
yield block
|
||||
block = afile.read(blocksize)
|
||||
|
||||
lookup = {p: cog_data_dir.joinpath(p.relative_to(bundled_data_dir)) for p in to_copy}
|
||||
|
||||
for orig, poss_existing in lookup.items():
|
||||
if not poss_existing.is_file():
|
||||
poss_existing.parent.mkdir(exist_ok=True, parents=True)
|
||||
exists_checksum = None
|
||||
else:
|
||||
exists_checksum = hash_bytestr_iter(
|
||||
file_as_blockiter(poss_existing.open("rb")), hashlib.sha256()
|
||||
)
|
||||
|
||||
orig_checksum = ...
|
||||
if exists_checksum is not None:
|
||||
orig_checksum = hash_bytestr_iter(file_as_blockiter(orig.open("rb")), hashlib.sha256())
|
||||
|
||||
if exists_checksum != orig_checksum:
|
||||
shutil.copy(str(orig), str(poss_existing))
|
||||
log.debug("Copying {} to {}".format(orig, poss_existing))
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
@deprecated("bundled_data_path() without calling this function")
|
||||
def load_bundled_data(cog_instance, init_location: str):
|
||||
pass
|
||||
|
||||
|
||||
def bundled_data_path(cog_instance: commands.Cog) -> Path:
|
||||
"""
|
||||
This function copies (and overwrites) data from the ``data/`` folder
|
||||
of the installed cog.
|
||||
Get the path to the "data" directory bundled with this cog.
|
||||
|
||||
The bundled data folder must be located alongside the ``.py`` file
|
||||
which contains the cog class.
|
||||
|
||||
.. important::
|
||||
|
||||
This function MUST be called from the ``setup()`` function of your
|
||||
cog.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from redbot.core import data_manager
|
||||
>>>
|
||||
>>> def setup(bot):
|
||||
>>> cog = MyCog()
|
||||
>>> data_manager.load_bundled_data(cog, __file__)
|
||||
>>> bot.add_cog(cog)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cog_instance
|
||||
An instance of your cog class.
|
||||
init_location : str
|
||||
The ``__file__`` attribute of the file where your ``setup()``
|
||||
function exists.
|
||||
"""
|
||||
bundled_data_folder, to_copy = _find_data_files(init_location)
|
||||
|
||||
cog_data_folder = cog_data_path(cog_instance) / "bundled_data"
|
||||
|
||||
_compare_and_copy(to_copy, bundled_data_folder, cog_data_folder)
|
||||
|
||||
|
||||
def bundled_data_path(cog_instance) -> Path:
|
||||
"""
|
||||
The "data" directory that has been copied from installed cogs.
|
||||
|
||||
.. important::
|
||||
|
||||
You should *NEVER* write to this directory. Data manager will
|
||||
overwrite files in this directory each time `load_bundled_data`
|
||||
is called. You should instead write to the directory provided by
|
||||
`cog_data_path`.
|
||||
You should *NEVER* write to this directory.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
cog_instance
|
||||
An instance of your cog. If calling from a command or method of
|
||||
your cog, this should be ``self``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@ -280,10 +184,10 @@ def bundled_data_path(cog_instance) -> Path:
|
||||
Raises
|
||||
------
|
||||
FileNotFoundError
|
||||
If no bundled data folder exists or if it hasn't been loaded yet.
|
||||
"""
|
||||
If no bundled data folder exists.
|
||||
|
||||
bundled_path = cog_data_path(cog_instance) / "bundled_data"
|
||||
"""
|
||||
bundled_path = Path(inspect.getfile(cog_instance.__class__)).parent / "data"
|
||||
|
||||
if not bundled_path.is_dir():
|
||||
raise FileNotFoundError("No such directory {}".format(bundled_path))
|
||||
|
||||
@ -23,6 +23,7 @@ discord.py 1.0.0a
|
||||
|
||||
This help formatter contains work by Rapptz (Danny) and SirThane#1780.
|
||||
"""
|
||||
import contextlib
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Union
|
||||
|
||||
@ -224,8 +225,8 @@ class Help(dpy_formatter.HelpFormatter):
|
||||
|
||||
return ret
|
||||
|
||||
async def format_help_for(self, ctx, command_or_bot, reason: str = None):
|
||||
"""Formats the help page and handles the actual heavy lifting of how ### WTF HAPPENED?
|
||||
async def format_help_for(self, ctx, command_or_bot, reason: str = ""):
|
||||
"""Formats the help page and handles the actual heavy lifting of how
|
||||
the help command looks like. To change the behaviour, override the
|
||||
:meth:`~.HelpFormatter.format` method.
|
||||
|
||||
@ -244,10 +245,24 @@ class Help(dpy_formatter.HelpFormatter):
|
||||
"""
|
||||
self.context = ctx
|
||||
self.command = command_or_bot
|
||||
|
||||
# We want the permission state to be set as if the author had run the command he is
|
||||
# requesting help for. This is so the subcommands shown in the help menu correctly reflect
|
||||
# any permission rules set.
|
||||
if isinstance(self.command, commands.Command):
|
||||
with contextlib.suppress(commands.CommandError):
|
||||
await self.command.can_run(
|
||||
self.context, check_all_parents=True, change_permission_state=True
|
||||
)
|
||||
elif isinstance(self.command, commands.Cog):
|
||||
with contextlib.suppress(commands.CommandError):
|
||||
# Cog's don't have a `can_run` method, so we use the `Requires` object directly.
|
||||
await self.command.requires.verify(self.context)
|
||||
|
||||
emb = await self.format()
|
||||
|
||||
if reason:
|
||||
emb["embed"]["title"] = "{0}".format(reason)
|
||||
emb["embed"]["title"] = reason
|
||||
|
||||
ret = []
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import json
|
||||
import os
|
||||
import asyncio
|
||||
import logging
|
||||
from copy import deepcopy
|
||||
from uuid import uuid4
|
||||
|
||||
# This is basically our old DataIO and just a base for much more elaborate classes
|
||||
@ -69,7 +70,11 @@ class JsonIO:
|
||||
|
||||
async def _threadsafe_save_json(self, data, settings=PRETTY):
|
||||
loop = asyncio.get_event_loop()
|
||||
func = functools.partial(self._save_json, data, settings)
|
||||
# the deepcopy is needed here. otherwise,
|
||||
# the dict can change during serialization
|
||||
# and this will break the encoder.
|
||||
data_copy = deepcopy(data)
|
||||
func = functools.partial(self._save_json, data_copy, settings)
|
||||
async with self._lock:
|
||||
await loop.run_in_executor(None, func)
|
||||
|
||||
|
||||
@ -666,29 +666,30 @@ async def register_casetypes(new_types: List[dict]) -> List[CaseType]:
|
||||
return type_list
|
||||
|
||||
|
||||
async def get_modlog_channel(guild: discord.Guild) -> Union[discord.TextChannel, None]:
|
||||
async def get_modlog_channel(guild: discord.Guild) -> discord.TextChannel:
|
||||
"""
|
||||
Get the current modlog channel
|
||||
Get the current modlog channel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
guild: `discord.Guild`
|
||||
The guild to get the modlog channel for
|
||||
The guild to get the modlog channel for.
|
||||
|
||||
Returns
|
||||
-------
|
||||
`discord.TextChannel` or `None`
|
||||
The channel object representing the modlog channel
|
||||
`discord.TextChannel`
|
||||
The channel object representing the modlog channel.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
If the modlog channel is not found
|
||||
If the modlog channel is not found.
|
||||
|
||||
"""
|
||||
if hasattr(guild, "get_channel"):
|
||||
channel = guild.get_channel(await _conf.guild(guild).mod_log())
|
||||
else:
|
||||
# For unit tests only
|
||||
channel = await _conf.guild(guild).mod_log()
|
||||
if channel is None:
|
||||
raise RuntimeError("Failed to get the mod log channel!")
|
||||
|
||||
@ -10,7 +10,7 @@ def test_trivia_lists():
|
||||
for l in list_names:
|
||||
with l.open() as f:
|
||||
try:
|
||||
dict_ = yaml.load(f)
|
||||
dict_ = yaml.safe_load(f)
|
||||
except yaml.error.YAMLError as e:
|
||||
problem_lists.append((l.stem, "YAML error:\n{!s}".format(e)))
|
||||
else:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user