Skip to content

Commit 27d0288

Browse files
committed
[adminutils] add support for 3.5
1 parent b147a06 commit 27d0288

File tree

5 files changed

+394
-249
lines changed

5 files changed

+394
-249
lines changed

adminutils/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ async def setup_after_ready(bot):
1717
for alias in command.aliases:
1818
if bot.get_command(alias):
1919
command.aliases[command.aliases.index(alias)] = f"a{alias}"
20-
bot.add_cog(cog)
20+
await bot.add_cog(cog)
2121

2222

23-
def setup(bot):
23+
async def setup(bot):
2424
create_task(setup_after_ready(bot))

adminutils/adminutils.py

Lines changed: 94 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,60 @@
1+
import contextlib
12
import re
23
from asyncio import TimeoutError as AsyncTimeoutError
34
from random import choice
4-
from typing import Optional, Union
5+
from typing import Literal, Optional, Union
56

67
import aiohttp
78
import discord
9+
from red_commons.logging import getLogger
810
from redbot.core import commands
911
from redbot.core.i18n import Translator, cog_i18n
1012
from redbot.core.utils import chat_formatting as chat
1113
from redbot.core.utils.mod import get_audit_reason
1214
from redbot.core.utils.predicates import MessagePredicate
1315

14-
try:
15-
from redbot import json # support of Draper's branch
16-
except ImportError:
17-
import json
18-
1916
_ = Translator("AdminUtils", __file__)
2017

2118
EMOJI_RE = re.compile(r"(<(a)?:[a-zA-Z0-9_]+:([0-9]+)>)")
2219

20+
CHANNEL_REASONS = {
21+
discord.CategoryChannel: _("You are not allowed to edit this category."),
22+
discord.TextChannel: _("You are not allowed to edit this channel."),
23+
discord.VoiceChannel: _("You are not allowed to edit this channel."),
24+
discord.StageChannel: _("You are not allowed to edit this channel."),
25+
}
26+
27+
28+
async def check_regions(ctx):
29+
"""Check if regions list is populated"""
30+
return ctx.cog.regions
31+
2332

2433
@cog_i18n(_)
2534
class AdminUtils(commands.Cog):
2635
"""Useful commands for server administrators."""
2736

28-
__version__ = "2.5.11"
37+
__version__ = "3.0.0"
2938

3039
# noinspection PyMissingConstructor
3140
def __init__(self, bot):
3241
self.bot = bot
33-
self.session = aiohttp.ClientSession(json_serialize=json.dumps)
42+
self.session = aiohttp.ClientSession()
43+
self.log = getLogger("red.fixator10-cogs.adminutils")
44+
self.regions = []
45+
46+
async def cog_load(self):
47+
try:
48+
regions = await self.bot.http.request(discord.http.Route("GET", "/voice/regions"))
49+
self.regions = [region["id"] for region in regions]
50+
except Exception as e:
51+
self.log.warning(
52+
"Unable to get list of rtc_regions. [p]restartvoice command will be unavailable",
53+
exc_info=e,
54+
)
3455

35-
def cog_unload(self):
36-
self.bot.loop.create_task(self.session.close())
56+
async def cog_unload(self):
57+
await self.session.close()
3758

3859
def format_help_for_context(self, ctx: commands.Context) -> str: # Thanks Sinbad!
3960
pre_processed = super().format_help_for_context(ctx)
@@ -45,20 +66,19 @@ async def red_delete_data_for_user(self, **kwargs):
4566
@staticmethod
4667
def check_channel_permission(
4768
ctx: commands.Context,
48-
channel_or_category: Union[discord.TextChannel, discord.CategoryChannel],
69+
channel_or_category: Union[
70+
discord.TextChannel,
71+
discord.CategoryChannel,
72+
discord.VoiceChannel,
73+
discord.StageChannel,
74+
],
4975
) -> bool:
5076
"""
5177
Check user's permission in a channel, to be sure he can edit it.
5278
"""
53-
mc = channel_or_category.permissions_for(ctx.author).manage_channels
54-
if mc:
79+
if channel_or_category.permissions_for(ctx.author).manage_channels:
5580
return True
56-
reason = (
57-
_("You are not allowed to edit this channel.")
58-
if not isinstance(channel_or_category, discord.CategoryChannel)
59-
else _("You are not allowed to edit in this category.")
60-
)
61-
raise commands.UserFeedbackCheckFailure(reason)
81+
raise commands.UserFeedbackCheckFailure(CHANNEL_REASONS.get(type(channel_or_category)))
6282

6383
@commands.command(name="prune")
6484
@commands.guild_only()
@@ -92,10 +112,8 @@ async def cleanup_users(self, ctx, days: Optional[int] = 1, *roles: discord.Role
92112
).format(to_kick=to_kick, days=days, roles=roles_text if roles else "")
93113
)
94114
)
95-
try:
115+
with contextlib.suppress(AsyncTimeoutError):
96116
await self.bot.wait_for("message", check=pred, timeout=30)
97-
except AsyncTimeoutError:
98-
pass
99117
if ctx.assume_yes or pred.result:
100118
cleanup = await ctx.guild.prune_members(
101119
days=days, reason=get_audit_reason(ctx.author), roles=roles or None
@@ -113,23 +131,20 @@ async def cleanup_users(self, ctx, days: Optional[int] = 1, *roles: discord.Role
113131

114132
@commands.command()
115133
@commands.guild_only()
116-
@commands.admin_or_permissions(manage_guild=True)
117-
@commands.bot_has_permissions(manage_guild=True)
118-
async def restartvoice(self, ctx: commands.Context):
119-
"""Change server's voice region to random and back
134+
@commands.check(check_regions)
135+
@commands.admin_or_permissions(manage_channels=True)
136+
@commands.bot_has_permissions(manage_channels=True)
137+
async def restartvoice(
138+
self, ctx: commands.Context, channel: Union[discord.VoiceChannel, discord.StageChannel]
139+
):
140+
"""Change voice channel's region to random and back
120141
121142
Useful to reinitate all voice connections"""
122-
current_region = ctx.guild.region
123-
random_region = choice(
124-
[
125-
r
126-
for r in discord.VoiceRegion
127-
if not r.value.startswith("vip") and current_region != r
128-
]
129-
)
130-
await ctx.guild.edit(region=random_region)
131-
await ctx.guild.edit(
132-
region=current_region,
143+
current_region = channel.rtc_region
144+
random_region = choice([r for r in self.regions if current_region != r])
145+
await channel.edit(rtc_region=random_region)
146+
await channel.edit(
147+
rtc_region=current_region,
133148
reason=get_audit_reason(ctx.author, _("Voice restart")),
134149
)
135150
await ctx.tick()
@@ -142,8 +157,8 @@ async def restartvoice(self, ctx: commands.Context):
142157
async def massmove(
143158
self,
144159
ctx: commands.Context,
145-
from_channel: discord.VoiceChannel,
146-
to_channel: discord.VoiceChannel = None,
160+
from_channel: Union[discord.VoiceChannel, discord.StageChannel],
161+
to_channel: Union[discord.VoiceChannel, discord.StageChannel] = None,
147162
):
148163
"""Move all members from one voice channel to another
149164
@@ -171,6 +186,7 @@ async def massmove(
171186
continue
172187
await ctx.send(_("Finished moving users. {} members could not be moved.").format(fails))
173188

189+
# TODO: Stickers?
174190
@commands.group()
175191
@commands.guild_only()
176192
@commands.admin_or_permissions(manage_emojis=True)
@@ -207,8 +223,6 @@ async def emoji_add(self, ctx, name: str, url: str, *roles: discord.Role):
207223
else None,
208224
),
209225
)
210-
except discord.InvalidArgument:
211-
await ctx.send(chat.error(_("This image type is unsupported, or link is incorrect")))
212226
except discord.HTTPException as e:
213227
await ctx.send(chat.error(_("An error occurred on adding an emoji: {}").format(e)))
214228
else:
@@ -251,13 +265,6 @@ async def emote_steal(
251265
),
252266
)
253267
await ctx.tick()
254-
except discord.InvalidArgument:
255-
await ctx.send(
256-
_(
257-
"This image type is not supported anymore or Discord returned incorrect data. Try again later."
258-
)
259-
)
260-
return
261268
except discord.HTTPException as e:
262269
await ctx.send(chat.error(_("An error occurred on adding an emoji: {}").format(e)))
263270

@@ -301,6 +308,7 @@ async def emoji_remove(self, ctx: commands.Context, *, emoji: discord.Emoji):
301308
await emoji.delete(reason=get_audit_reason(ctx.author))
302309
await ctx.tick()
303310

311+
# TODO: Threads?
304312
@commands.group()
305313
@commands.guild_only()
306314
@commands.admin_or_permissions(manage_channels=True)
@@ -360,8 +368,8 @@ async def channel_create_voice(
360368
Use double quotes if category has spaces
361369
362370
Examples:
363-
`[p]channel add voice "The Zoo" Awesome Channel` will create under the "The Zoo" category.
364-
`[p]channel add voice Awesome Channel` will create under no category, at the top.
371+
`[p]channel add voice "The Zoo" Awesome Channel` will create voice channel under the "The Zoo" category.
372+
`[p]channel add voice Awesome Channel` will create stage channel under no category, at the top.
365373
"""
366374
if category:
367375
self.check_channel_permission(ctx, category)
@@ -376,11 +384,41 @@ async def channel_create_voice(
376384
else:
377385
await ctx.tick()
378386

387+
@channel_create.command(name="stage")
388+
async def channel_create_stage(
389+
self,
390+
ctx: commands.Context,
391+
category: Optional[discord.CategoryChannel] = None,
392+
*,
393+
name: str,
394+
):
395+
"""Create a stage channel
396+
397+
You can create the channel under a category if passed, else it is created under no category
398+
Use double quotes if category has spaces
399+
400+
Examples:
401+
`[p]channel add voice "The Zoo" Awesome Channel` will create voice channel under the "The Zoo" category.
402+
`[p]channel add voice Awesome Channel` will create stage channel under no category, at the top.
403+
"""
404+
if category:
405+
self.check_channel_permission(ctx, category)
406+
try:
407+
await ctx.guild.create_stage_channel(
408+
name, category=category, reason=get_audit_reason(ctx.author)
409+
)
410+
except discord.Forbidden:
411+
await ctx.send(chat.error(_("I can't create channel in this category")))
412+
except discord.HTTPException as e:
413+
await ctx.send(chat.error(_("I am unable to create a channel: {}").format(e)))
414+
else:
415+
await ctx.tick()
416+
379417
@channel.command(name="rename")
380418
async def channel_rename(
381419
self,
382420
ctx: commands.Context,
383-
channel: Union[discord.TextChannel, discord.VoiceChannel],
421+
channel: Union[discord.TextChannel, discord.VoiceChannel, discord.StageChannel],
384422
*,
385423
name: str,
386424
):
@@ -403,7 +441,10 @@ async def channel_rename(
403441

404442
@channel.command(name="delete", aliases=["remove"])
405443
async def channel_delete(
406-
self, ctx: commands.Context, *, channel: Union[discord.TextChannel, discord.VoiceChannel]
444+
self,
445+
ctx: commands.Context,
446+
*,
447+
channel: Union[discord.TextChannel, discord.VoiceChannel, discord.StageChannel],
407448
):
408449
"""Remove a channel from server
409450
@@ -421,10 +462,8 @@ async def channel_delete(
421462
).format(channel=channel.mention)
422463
)
423464
)
424-
try:
465+
with contextlib.suppress(AsyncTimeoutError):
425466
await self.bot.wait_for("message", check=pred, timeout=30)
426-
except AsyncTimeoutError:
427-
pass
428467
if ctx.assume_yes or pred.result:
429468
try:
430469
await channel.delete(reason=get_audit_reason(ctx.author))

adminutils/info.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
"short": "Useful commands for server administrators.",
1010
"description": "Useful commands for server administrators.",
1111
"min_bot_version": "3.4.0",
12-
"max_bot_version": "3.4.99",
1312
"tags": [
1413
"admin",
1514
"emoji",

0 commit comments

Comments
 (0)