|
|
@@ -23,10 +23,12 @@ from rocketbot.utils import (
|
|
23
|
23
|
is_email_address,
|
|
24
|
24
|
)
|
|
25
|
25
|
|
|
26
|
|
-PERMITTED_GUILD_IDS = [
|
|
|
26
|
+_PERMITTED_GUILD_IDS = [
|
|
27
|
27
|
405011810937339905,
|
|
28
|
28
|
900805482825007104, # test server
|
|
29
|
29
|
]
|
|
|
30
|
+_EMAIL_MODE = True
|
|
|
31
|
+_USERNAME_MODE = False
|
|
30
|
32
|
class KickstarterCog(BaseCog):
|
|
31
|
33
|
"""
|
|
32
|
34
|
Provides a way for Discord users to self-identify the email address they
|
|
|
@@ -87,14 +89,26 @@ class KickstarterCog(BaseCog):
|
|
87
|
89
|
Self = KickstarterCog
|
|
88
|
90
|
self.set_guild_setting(role.guild, Self.SETTING_ROLE, role.id)
|
|
89
|
91
|
|
|
|
92
|
+ async def __misconfigured(self, interaction: Interaction):
|
|
|
93
|
+ await interaction.response.send_message(f"{CONFIG['failure_emoji']} Feature misconfigured", ephemeral=True)
|
|
|
94
|
+
|
|
|
95
|
+
|
|
90
|
96
|
# -- Member commands -----
|
|
91
|
97
|
|
|
92
|
98
|
@command(
|
|
93
|
|
- description='Links your Kickstarter email address to your Discord user'
|
|
|
99
|
+ description='Grants you access to Kickstarter backer-only areas'
|
|
94
|
100
|
)
|
|
95
|
101
|
@guild_only()
|
|
96
|
|
- @guilds(PERMITTED_GUILD_IDS)
|
|
97
|
|
- async def link_email(self, interaction: Interaction):
|
|
|
102
|
+ @guilds(*_PERMITTED_GUILD_IDS)
|
|
|
103
|
+ async def link(self, interaction: Interaction):
|
|
|
104
|
+ if _EMAIL_MODE:
|
|
|
105
|
+ await self.__link_email_command(interaction)
|
|
|
106
|
+ elif _USERNAME_MODE:
|
|
|
107
|
+ await self.__link_username_command(interaction)
|
|
|
108
|
+ else:
|
|
|
109
|
+ await self.__misconfigured(interaction)
|
|
|
110
|
+
|
|
|
111
|
+ async def __link_email_command(self, interaction: Interaction):
|
|
98
|
112
|
# If possible, try to check if they're already a backer before opening
|
|
99
|
113
|
# a modal.
|
|
100
|
114
|
guild = interaction.guild
|
|
|
@@ -112,12 +126,7 @@ class KickstarterCog(BaseCog):
|
|
112
|
126
|
|
|
113
|
127
|
await interaction.response.send_modal(_LinkModal())
|
|
114
|
128
|
|
|
115
|
|
- @command(
|
|
116
|
|
- description='Grants you access to Kickstarter backer-only areas'
|
|
117
|
|
- )
|
|
118
|
|
- @guild_only()
|
|
119
|
|
- @guilds(PERMITTED_GUILD_IDS)
|
|
120
|
|
- async def link_username(self, interaction: Interaction):
|
|
|
129
|
+ async def __link_username_command(self, interaction: Interaction):
|
|
121
|
130
|
await interaction.response.defer(ephemeral=True, thinking=True)
|
|
122
|
131
|
try:
|
|
123
|
132
|
backer_role = await self.__fetch_backer_role(interaction.guild)
|
|
|
@@ -158,7 +167,7 @@ class KickstarterCog(BaseCog):
|
|
158
|
167
|
'user and removes the backer role.'
|
|
159
|
168
|
)
|
|
160
|
169
|
@guild_only()
|
|
161
|
|
- @guilds(PERMITTED_GUILD_IDS)
|
|
|
170
|
+ @guilds(*_PERMITTED_GUILD_IDS)
|
|
162
|
171
|
async def unlink(self, interaction: Interaction):
|
|
163
|
172
|
await interaction.response.defer(ephemeral=True, thinking=True)
|
|
164
|
173
|
guild = interaction.guild
|
|
|
@@ -175,16 +184,17 @@ class KickstarterCog(BaseCog):
|
|
175
|
184
|
await member.remove_roles(backer_role)
|
|
176
|
185
|
self.__delete_member_link(guild.id, member.id)
|
|
177
|
186
|
text = f"{CONFIG['success_emoji']} Unlinked as Kickstarter backer. Use " \
|
|
178
|
|
- "`/link` if you change your mind."
|
|
|
187
|
+ "`/link` again if you change your mind."
|
|
179
|
188
|
await interaction.followup.send(text, ephemeral=True)
|
|
180
|
189
|
|
|
|
190
|
+
|
|
181
|
191
|
# -- Admin commands -----
|
|
182
|
192
|
|
|
183
|
193
|
kickstarter = Group(
|
|
184
|
194
|
name='kickstarter',
|
|
185
|
195
|
description='Manages roles for users identified as Kickstarter backers.',
|
|
186
|
196
|
guild_only=True,
|
|
187
|
|
- guild_ids=PERMITTED_GUILD_IDS,
|
|
|
197
|
+ guild_ids=_PERMITTED_GUILD_IDS,
|
|
188
|
198
|
default_permissions=MOD_PERMISSIONS
|
|
189
|
199
|
)
|
|
190
|
200
|
|
|
|
@@ -199,17 +209,22 @@ class KickstarterCog(BaseCog):
|
|
199
|
209
|
@kickstarter.command(
|
|
200
|
210
|
description='Checks the database for users to assign the backer role to.'
|
|
201
|
211
|
)
|
|
202
|
|
- async def sync_emails(self, interaction: Interaction):
|
|
|
212
|
+ async def sync(self, interaction: Interaction):
|
|
|
213
|
+ if _EMAIL_MODE:
|
|
|
214
|
+ await self.__sync_emails_command(interaction)
|
|
|
215
|
+ elif _USERNAME_MODE:
|
|
|
216
|
+ await self.__sync_usernames_command(interaction)
|
|
|
217
|
+ else:
|
|
|
218
|
+ await self.__misconfigured(interaction)
|
|
|
219
|
+
|
|
|
220
|
+ async def __sync_emails_command(self, interaction: Interaction):
|
|
203
|
221
|
await interaction.response.defer(ephemeral=True, thinking=True)
|
|
204
|
222
|
sync_result: _SyncEmailsResult = await self.__sync_by_email(interaction.guild)
|
|
205
|
223
|
text = f"{CONFIG['success_emoji']} Sync complete.\n" + \
|
|
206
|
224
|
sync_result.summary_markdown()
|
|
207
|
225
|
await interaction.followup.send(text, ephemeral=True)
|
|
208
|
|
-
|
|
209
|
|
- @kickstarter.command(
|
|
210
|
|
- description='Checks the database for users to assign the backer role to.'
|
|
211
|
|
- )
|
|
212
|
|
- async def sync_usernames(self, interaction: Interaction):
|
|
|
226
|
+
|
|
|
227
|
+ async def __sync_usernames_command(self, interaction: Interaction):
|
|
213
|
228
|
await interaction.response.defer(ephemeral=True, thinking=True)
|
|
214
|
229
|
sync_result: _SyncUsernamesResult = await self.__sync_by_username(interaction.guild)
|
|
215
|
230
|
text = f"{CONFIG['success_emoji']} Sync complete.\n" + \
|
|
|
@@ -217,14 +232,6 @@ class KickstarterCog(BaseCog):
|
|
217
|
232
|
await interaction.followup.send(text, ephemeral=True)
|
|
218
|
233
|
|
|
219
|
234
|
@kickstarter.command(
|
|
220
|
|
- description='DEV TEST - testing timeout'
|
|
221
|
|
- )
|
|
222
|
|
- async def timeout_test(self, interaction: Interaction):
|
|
223
|
|
- await interaction.response.defer(ephemeral=True, thinking=True)
|
|
224
|
|
- await sleep(300)
|
|
225
|
|
- await interaction.followup.send("5m timer done", ephemeral=True)
|
|
226
|
|
-
|
|
227
|
|
- @kickstarter.command(
|
|
228
|
235
|
description='Shows info about Kickstarter linked Discord members.'
|
|
229
|
236
|
)
|
|
230
|
237
|
async def info(self, interaction: Interaction):
|
|
|
@@ -269,52 +276,67 @@ class KickstarterCog(BaseCog):
|
|
269
|
276
|
return
|
|
270
|
277
|
|
|
271
|
278
|
if backer_role in member.roles:
|
|
272
|
|
- text = f"{CONFIG['info_emoji']} Member {member.name} has `{backer_role.name}` role."
|
|
|
279
|
+ text = f"{CONFIG['info_emoji']} Member {member.name} has " \
|
|
|
280
|
+ f"`{backer_role.name}` role."
|
|
273
|
281
|
await interaction.followup.send(text, ephemeral=True)
|
|
274
|
282
|
return
|
|
275
|
283
|
|
|
|
284
|
+ if _USERNAME_MODE:
|
|
|
285
|
+ await self.__find_member_username_mode(interaction, member, backer_role)
|
|
|
286
|
+ elif _EMAIL_MODE:
|
|
|
287
|
+ await self.__find_member_email_mode(interaction, member, backer_role)
|
|
|
288
|
+ else:
|
|
|
289
|
+ await self.__misconfigured(interaction)
|
|
|
290
|
+
|
|
|
291
|
+ async def __find_member_username_mode(self, interaction: Interaction, member: Member, backer_role: Role):
|
|
|
292
|
+ guild = interaction.guild
|
|
276
|
293
|
username_record = self.__fetch_kickstarter_username(username=member.name)
|
|
277
|
294
|
if username_record is not None:
|
|
278
|
295
|
await member.add_roles(backer_role)
|
|
279
|
296
|
username_record.lookup_status = _LookupStatus.member_found
|
|
280
|
297
|
self.__update_kickstarter_username(username_record)
|
|
281
|
|
- text = f"{CONFIG['success_emoji']} Member's username found in Kickstarter export. Assigned `{backer_role.name}` role."
|
|
|
298
|
+ text = f"{CONFIG['success_emoji']} Member's username found in " \
|
|
|
299
|
+ f"Kickstarter export. The `{backer_role.name}` role has now " \
|
|
|
300
|
+ "been assigned to them."
|
|
282
|
301
|
await interaction.followup.send(text, ephemeral=True)
|
|
283
|
302
|
return
|
|
|
303
|
+ stats = self.__fetch_stats(guild.id)
|
|
|
304
|
+ text = f"{CONFIG['info_emoji']} Member's username is not in the " \
|
|
|
305
|
+ f"Kickstarter export. (Last import <t:{stats.username_last_imported_at}:f>)"
|
|
|
306
|
+ await interaction.followup.send(text, ephemeral=True)
|
|
284
|
307
|
|
|
285
|
|
- # FIXME: Sorta splitting the difference between email and username model here
|
|
286
|
|
-
|
|
|
308
|
+ async def __find_member_email_mode(self, interaction: Interaction, member: Member, backer_role: Role):
|
|
|
309
|
+ guild = interaction.guild
|
|
287
|
310
|
member_link = self.__fetch_member_link_by_member_id(guild.id, member.id)
|
|
288
|
311
|
if member_link is None:
|
|
289
|
|
- text = f"No record found for member {member.mention}. Possible reasons:\n" \
|
|
|
312
|
+ text = f"{CONFIG['info_emoji']} No record found for member " \
|
|
|
313
|
+ f"{member.mention}.\n" \
|
|
|
314
|
+ "\n" \
|
|
|
315
|
+ "Possible reasons:\n" \
|
|
290
|
316
|
"- They haven't used the `/link` command yet to provide their " \
|
|
291
|
317
|
"Kickstarter email address\n" \
|
|
292
|
318
|
"- They linked using a different Discord account. (You can " \
|
|
293
|
319
|
"search by email with `/find_email`)\n" \
|
|
294
|
|
- "- They used the `/unlink` after being linked"
|
|
|
320
|
+ "- They used the `/unlink` command after being linked"
|
|
295
|
321
|
await interaction.followup.send(text, ephemeral=True)
|
|
296
|
322
|
return
|
|
297
|
323
|
|
|
298
|
|
- text = f"Member {member.mention} successfully used the `/link` command"
|
|
299
|
|
- if backer_role in member.roles:
|
|
300
|
|
- text += f", and they already have the {backer_role.name} Discord role. " \
|
|
301
|
|
- "They should already have access to backer-only areas. If they " \
|
|
302
|
|
- "still can't see them, some possible reasons:\n" \
|
|
303
|
|
- "- Its channel group is collapsed in the channel list.\n" \
|
|
304
|
|
- "- Some channels are hidden. Direct them to the Browse Channels " \
|
|
305
|
|
- "area in the channel list to see if it's listed and checked visible.\n" \
|
|
306
|
|
- "- Their client may need to be refreshed. Have them restart Discord " \
|
|
307
|
|
- "and see if the channel shows up."
|
|
|
324
|
+ text = f"{CONFIG['info_emoji']} Member record found for {member.mention}.\n" \
|
|
|
325
|
+ "\n" \
|
|
|
326
|
+ "- User provided an email address with the `/link` command.\n"
|
|
|
327
|
+ is_backer = self.__is_kickstarter_email_hash(interaction.guild.id, member_link.email_hash)
|
|
|
328
|
+ if is_backer:
|
|
|
329
|
+ await member.add_roles(backer_role)
|
|
|
330
|
+ text += "- Provided email is in the Kickstarter backer list but " \
|
|
|
331
|
+ f"we hadn't given them the {backer_role.name} role yet.\n" \
|
|
|
332
|
+ f"- Member granted {backer_role.name} role just now."
|
|
308
|
333
|
else:
|
|
309
|
|
- is_backer = self.__is_kickstarter_email_hash(interaction.guild.id, member_link.email_hash)
|
|
310
|
|
- if is_backer:
|
|
311
|
|
- await member.add_roles(backer_role)
|
|
312
|
|
- text += f", and their email address is in the Kickstarter backer list, " \
|
|
313
|
|
- f"but they didn't have the {backer_role.name} role yet! **This was " \
|
|
314
|
|
- "just now corrected.** Ask them to check again."
|
|
315
|
|
- else:
|
|
316
|
|
- text += ", but we don't have the address they provided in the " \
|
|
317
|
|
- "Kickstarter backer list yet. Have we refreshed it recently?"
|
|
|
334
|
+ stats = self.__fetch_stats(guild.id)
|
|
|
335
|
+ text += "- Provided email is _not_ in the Kickstarter backer list.\n" \
|
|
|
336
|
+ " - Did they provide the same email as Kickstarter and " \
|
|
|
337
|
+ "spell it correctly?\n" \
|
|
|
338
|
+ " - Have we imported addresses recently? Last import on " \
|
|
|
339
|
+ f"<t:{stats.email_last_imported_at}:f>."
|
|
318
|
340
|
await interaction.followup.send(text, ephemeral=True)
|
|
319
|
341
|
|
|
320
|
342
|
@kickstarter.command(
|
|
|
@@ -380,15 +402,20 @@ class KickstarterCog(BaseCog):
|
|
380
|
402
|
await interaction.followup.send(text, ephemeral=True)
|
|
381
|
403
|
|
|
382
|
404
|
@kickstarter.command(
|
|
383
|
|
- description='Uploads an export of Kickstarter backer email addresses.'
|
|
|
405
|
+ description='Uploads an export of Kickstarter backers.'
|
|
384
|
406
|
)
|
|
385
|
|
- async def upload_emails(self, interaction: Interaction):
|
|
|
407
|
+ async def upload(self, interaction: Interaction):
|
|
|
408
|
+ if _EMAIL_MODE:
|
|
|
409
|
+ await self.__upload_emails_command(interaction)
|
|
|
410
|
+ elif _USERNAME_MODE:
|
|
|
411
|
+ await self.__upload_usernames_command(interaction)
|
|
|
412
|
+ else:
|
|
|
413
|
+ await self.__misconfigured(interaction)
|
|
|
414
|
+
|
|
|
415
|
+ async def __upload_emails_command(self, interaction: Interaction):
|
|
386
|
416
|
await interaction.response.send_modal(_UploadEmailsModal())
|
|
387
|
417
|
|
|
388
|
|
- @kickstarter.command(
|
|
389
|
|
- description='Uploads an export of backer Discord usernames.'
|
|
390
|
|
- )
|
|
391
|
|
- async def upload_usernames(self, interaction: Interaction):
|
|
|
418
|
+ async def __upload_usernames_command(self, interaction: Interaction):
|
|
392
|
419
|
await interaction.response.send_modal(_UploadUsernamesModal())
|
|
393
|
420
|
|
|
394
|
421
|
async def interaction_check(self, interaction: Interaction) -> bool:
|
|
|
@@ -396,6 +423,9 @@ class KickstarterCog(BaseCog):
|
|
396
|
423
|
self.__trace(interaction.guild, f"@{interaction.user.name} used /{interaction.command.qualified_name}")
|
|
397
|
424
|
return True
|
|
398
|
425
|
|
|
|
426
|
+
|
|
|
427
|
+ # -- UI callbacks -----
|
|
|
428
|
+
|
|
399
|
429
|
async def on_email_upload_submit(self, interaction: Interaction, attachment: Attachment):
|
|
400
|
430
|
"""Callback for email upload modal."""
|
|
401
|
431
|
await interaction.response.defer(ephemeral=True, thinking=True)
|
|
|
@@ -477,6 +507,9 @@ class KickstarterCog(BaseCog):
|
|
477
|
507
|
|
|
478
|
508
|
await interaction.followup.send(text, ephemeral=True)
|
|
479
|
509
|
|
|
|
510
|
+
|
|
|
511
|
+ # -- Operations -----
|
|
|
512
|
+
|
|
480
|
513
|
async def __link_member(self, guild: Guild, member: Member, email: str) -> '_LinkResult':
|
|
481
|
514
|
if not is_email_address(email.strip()):
|
|
482
|
515
|
return _LinkResult(_LinkResultStatus.malformed_address)
|