-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathadd_users_to_threads_and_channels.py
More file actions
301 lines (261 loc) · 10.8 KB
/
add_users_to_threads_and_channels.py
File metadata and controls
301 lines (261 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
"""Contains Cog classes for adding users and roles to threads."""
import logging
from collections.abc import Iterable
from typing import TYPE_CHECKING
import discord
from config import settings
from exceptions import GuestRoleDoesNotExistError, GuildDoesNotExistError
from utils import CommandChecks, TeXBotBaseCog
from utils.error_capture_decorators import capture_guild_does_not_exist_error
if TYPE_CHECKING:
from collections.abc import Sequence
from collections.abc import Set as AbstractSet
from logging import Logger
from typing import Final
from utils import TeXBotApplicationContext, TeXBotAutocompleteContext
__all__: "Sequence[str]" = ("AddUsersToThreadsAndChannelsCommandsCog",)
logger: "Final[Logger]" = logging.getLogger("TeX-Bot")
class AddUsersToThreadsAndChannelsCommandsCog(TeXBotBaseCog):
"""Cog for adding users to threads."""
@staticmethod
async def autocomplete_get_members(
ctx: "TeXBotAutocompleteContext",
) -> "AbstractSet[discord.OptionChoice] | AbstractSet[str]":
"""Autocomplete callable that generates the set of available selectable members."""
try:
main_guild: discord.Guild = ctx.bot.main_guild
guest_role: discord.Role = await ctx.bot.guest_role
except (GuildDoesNotExistError, GuestRoleDoesNotExistError):
return set()
members: set[discord.Member] = {
member
for member in main_guild.members
if not member.bot and guest_role in member.roles
}
if not ctx.value or ctx.value.startswith("@"):
return {
discord.OptionChoice(name=f"@{member.name}", value=str(member.id))
for member in members
}
return {
discord.OptionChoice(name=member.name, value=str(member.id)) for member in members
}
@staticmethod
async def autocomplete_get_roles(
ctx: "TeXBotAutocompleteContext",
) -> "AbstractSet[discord.OptionChoice] | AbstractSet[str]":
"""Autocomplete callable that generates the set of available selectable roles."""
try:
main_guild: discord.Guild = ctx.bot.main_guild
except GuildDoesNotExistError:
return set()
if not ctx.value or ctx.value.startswith("@"):
return {
discord.OptionChoice(name=f"@{role.name}", value=str(role.id))
for role in main_guild.roles
}
return {
discord.OptionChoice(name=role.name, value=str(role.id))
for role in main_guild.roles
}
async def add_users_or_roles_silently(
self,
users_or_roles: discord.Member
| discord.Role
| Iterable[discord.Member]
| Iterable[discord.Role],
channel_or_thread: discord.Thread | discord.TextChannel,
) -> None:
"""Add a user or role to a thread without pinging them."""
if isinstance(users_or_roles, Iterable):
user_or_role: discord.Role | discord.Member
for user_or_role in users_or_roles:
await self.add_users_or_roles_silently(
users_or_roles=user_or_role, channel_or_thread=channel_or_thread
)
return
if isinstance(channel_or_thread, discord.Thread):
message: discord.Message = await channel_or_thread.send(
content=f"Adding {users_or_roles!r} to thread...", silent=True
)
await message.edit(content=f"{users_or_roles.mention}")
await message.delete(delay=1)
return
await channel_or_thread.set_permissions(
target=users_or_roles,
read_messages=True,
send_messages=True,
reason=f"User {self.bot.user} used TeX-Bot slash-command `add_users_to_channel`.",
)
async def add_users_or_roles_with_ping(
self,
users_or_roles: discord.Member
| discord.Role
| Iterable[discord.Member]
| Iterable[discord.Role],
channel_or_thread: discord.Thread | discord.TextChannel,
) -> None:
"""Add a user or role to a thread and ping them."""
if isinstance(users_or_roles, Iterable):
user_or_role: discord.Role | discord.Member
for user_or_role in users_or_roles:
await self.add_users_or_roles_with_ping(
users_or_roles=user_or_role, channel_or_thread=channel_or_thread
)
return
if isinstance(channel_or_thread, discord.Thread):
if isinstance(users_or_roles, discord.Member):
try:
await channel_or_thread.add_user(user=users_or_roles)
except discord.NotFound:
logger.debug(
"User: %s has blocked the bot and "
"therefore could not be added to thread: %s.",
users_or_roles,
channel_or_thread,
)
return
member: discord.Member
for member in users_or_roles.members:
try:
await channel_or_thread.add_user(member)
except discord.NotFound:
logger.debug(
"User: %s has blocked the bot and "
"therefore could not be added to thread: %s.",
member,
channel_or_thread,
)
return
await channel_or_thread.set_permissions(
target=users_or_roles,
read_messages=True,
send_messages=True,
reason=f"User {self.bot.user} used TeX-Bot slash-command `add_users_to_channel`.",
)
await channel_or_thread.send(
content=f"{users_or_roles.mention} has been added to the channel."
)
@TeXBotBaseCog.listener()
@capture_guild_does_not_exist_error
async def on_thread_create(self, thread: discord.Thread) -> None:
"""Add users to a thread when it is created."""
# NOTE: Shortcut accessors are placed at the top of the function so that the exceptions they raise are displayed before any further errors may be sent
committee_role: discord.Role = await self.bot.committee_role
committee_elect_role: discord.Role = await self.bot.committee_elect_role
if (
thread.parent is None # noqa: CAR180
or thread.parent.category is None
or "committee" not in thread.parent.category.name.lower()
or not settings["AUTO_ADD_COMMITTEE_TO_THREADS"]
):
return
await self.add_users_or_roles_silently(
users_or_roles=(committee_role, committee_elect_role), channel_or_thread=thread
)
@discord.slash_command(
name="add-users-to-channel", description="Adds selected users to a channel or thread."
)
@discord.option(
name="user",
description="The user to add to the channel.",
input_type=str,
autocomplete=discord.utils.basic_autocomplete(autocomplete_get_members),
required=True,
parameter_name="user_id_str",
)
@discord.option(
name="silent",
description="Whether the users being added should be pinged or not.",
input_type=bool,
required=False,
parameter_name="silent",
)
@CommandChecks.check_interaction_user_has_committee_role
@CommandChecks.check_interaction_user_in_main_guild
async def add_user_to_channel(
self,
ctx: "TeXBotApplicationContext",
user_id_str: str,
silent: bool, # noqa: FBT001
) -> None:
"""Add users or roles to a channel."""
if not isinstance(ctx.channel, (discord.TextChannel, discord.Thread)):
await self.command_send_error(
ctx,
message="This command currently only supports text channels or threads.",
)
return
try:
user_to_add: discord.Member = await self.bot.get_member_from_str_id(user_id_str)
except ValueError:
logger.debug("User ID: %s is not a valid ID.", user_id_str)
await ctx.respond(content=f"The user: {user_id_str} is not valid.")
return
if silent:
await self.add_users_or_roles_silently(user_to_add, ctx.channel)
else:
await self.add_users_or_roles_with_ping(user_to_add, ctx.channel)
await ctx.respond(
content=(
f"Successfully added {user_to_add.mention} "
f"to the channel: {ctx.channel.mention}."
),
ephemeral=True,
)
@discord.slash_command(
name="add-role-to-channel",
description="Adds the selected role and it's users to a channel or thread.",
)
@discord.option(
name="role",
description="The role to add to the channel.",
input_type=str,
autocomplete=discord.utils.basic_autocomplete(autocomplete_get_roles),
required=True,
parameter_name="role_id_str",
)
@discord.option(
name="silent",
description="Whether the users being added should be pinged or not.",
input_type=bool,
required=False,
parameter_name="silent",
)
@CommandChecks.check_interaction_user_has_committee_role
@CommandChecks.check_interaction_user_in_main_guild
async def add_role_to_channel(
self,
ctx: "TeXBotApplicationContext",
role_id_str: str,
silent: bool, # noqa: FBT001
) -> None:
"""Command to add a role to a channel."""
if not isinstance(ctx.channel, discord.Thread) and not isinstance(
ctx.channel, discord.TextChannel
):
await self.command_send_error(
ctx, message="This command can only be used in a text channel or thread."
)
return
main_guild: discord.Guild = ctx.bot.main_guild
try:
role_id: int = int(role_id_str)
except ValueError:
logger.debug("Role ID: %s is not a valid ID.", role_id_str)
await ctx.respond(content=f"The role: {role_id_str} is not valid.")
return
role_to_add: discord.Role | None = discord.utils.get(main_guild.roles, id=role_id)
if role_to_add is None:
await self.command_send_error(
ctx, message=f"The role: <@{role_id}> is not valid or couldn't be found."
)
return
if silent:
await self.add_users_or_roles_silently(role_to_add, ctx.channel)
else:
await self.add_users_or_roles_with_ping(role_to_add, ctx.channel)
await ctx.respond(
content=f"Role {role_to_add.mention} has been added to the channel.",
ephemeral=True,
)