-
Notifications
You must be signed in to change notification settings - Fork 2
/
bot.py
646 lines (567 loc) · 30.8 KB
/
bot.py
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
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
import asyncio
import datetime
import json
import logging
import random
import re
import time
import traceback
import typing
import aiohttp
import asyncpg
import discord
import git
import humanize
from discord.ext import commands, tasks
from discord.ext.commands import TextChannelConverter, BadArgument, MemberConverter
import db
class SBZGiveawayBot(commands.Bot):
def __init__(self, **options):
super().__init__(**options)
self.db = None
self.loaded_db = False
intents = discord.Intents.all()
logging.basicConfig(level=logging.WARNING)
bot = SBZGiveawayBot(command_prefix='g$', intents=intents)
bot.load_extension('jishaku')
tada_emoji = '\U0001f389'
repo = git.Repo('.')
bot.msg_sent = {}
with open('token.json', 'r') as f:
tokens = json.loads(f.read())
def convert_time(raw):
"""
Convert human time (eg. 10h10m) into seconds
:param raw: Human time
:return: Seconds
"""
res = re.split(r'(\d+)', raw)
res.pop(0)
total_time = 0
current_working = 0
time_suffixes = {'w': 604800, 'd': 86400, 'h': 3600, 'm': 60, 's': 1}
for i in res:
if i.isdigit():
current_working = i
else:
total_time += int(current_working) * time_suffixes[i]
current_working = 0
return total_time
@bot.event
async def on_connect():
if not bot.loaded_db:
logging.warning('LOADED DATABASE')
bot.db = await asyncpg.create_pool(**tokens['pgsql'])
await db.ensure_database_validity(bot.db)
bot.loaded_db = True
logging.warning('Connected')
@bot.event
async def on_ready():
logging.warning('Ready')
@bot.event
async def on_command_error(ctx, error):
traceback.print_exception(type(error), error, error.__traceback__)
traceback_data = ''.join(traceback.format_exception(type(error), error, error.__traceback__))
version = repo.head.object.hexsha[:8]
params = {'exc_string': str(error), 'exc_content': traceback_data,
'time': int(time.time()), 'msg_author_name': str(ctx.author), 'msg_author_id': str(ctx.author.id),
'msg_guild_name': ctx.guild.name,
'msg_guild_id': str(ctx.guild.id), 'msg_channel_name': ctx.channel.name,
'msg_channel_id': str(ctx.channel.id),
'msg_id': str(ctx.message.id), 'msg_cont': ctx.message.content,
'bot': 'SkyBlockZ Giveaways', 'version': version,
'key': tokens['error']}
async with aiohttp.ClientSession(loop=bot.loop) as cs:
resp = await cs.post('https://error.robothanzo.dev/add', params=params)
track_url = f'https://error.robothanzo.dev/view/{(await resp.json())["track_uuid"]}'
await ctx.send(f'Oh crap, something went VERY WRONG, the exception has been recorded at {track_url}')
@tasks.loop(hours=12)
async def clear_msg_sent():
bot.msg_sent = {}
@tasks.loop(seconds=1)
async def check_giveaways():
await bot.wait_until_ready()
if not bot.loaded_db:
# bot hasn't loaded DB yet
return
need_rolling = await db.get_need_rolling_giveaways(bot.db)
if len(need_rolling) == 0:
pass
else:
for ga_id in need_rolling:
try:
winners = await db.roll_winner(bot.db, ga_id)
except db.NoParticipants:
details = await db.get_info_of_giveaway(bot.db, ga_id)
embed = discord.Embed(title=details['prize_name'],
description='No one has joined the giveaway, thus the roll has been canceled',
colour=discord.Colour.dark_red())
embed.add_field(name='Hosted By', value=details['host'])
if details['image'] is not None:
embed.set_image(url=details['image'])
embed.timestamp = datetime.datetime.utcfromtimestamp(details['created_at'] + details['length'])
embed.set_footer(text=f'ID: {ga_id}| Ended At')
cc = bot.get_channel(details['channel_id'])
mc = await cc.fetch_message(details['message_id'])
await mc.edit(embed=embed)
await cc.send(
f'Giveaway of {details["prize_name"]} (ID:{str(ga_id)}) has been canceled, due to no participants in the giveaway')
continue
except db.NotEnoughParticipants:
details = await db.get_info_of_giveaway(bot.db, ga_id)
embed = discord.Embed(title=details['prize_name'],
description=f'Not enough people joined the giveaway (only {len(details["participants"])}), thus the roll has been canceled',
colour=discord.Colour.dark_red())
embed.add_field(name='Hosted By', value=f'<@!{details["host"]}>')
if details['image'] is not None:
embed.set_image(url=details['image'])
embed.timestamp = datetime.datetime.utcfromtimestamp(details['created_at'] + details['length'])
embed.set_footer(text=f'ID: {ga_id}| Ended At')
cc = bot.get_channel(details['channel_id'])
mc = await cc.fetch_message(details['message_id'])
await mc.edit(embed=embed)
await cc.send(
f'Giveaway of {details["prize_name"]} (ID:{str(ga_id)}) has been canceled, due to not enough people joined the giveaway (only {len(details["participants"])})')
continue
details = await db.get_info_of_giveaway(bot.db, ga_id)
winners_ping = [f'<@!{i}>' for i in details['winners']]
embed = discord.Embed(title=details['prize_name']
)
embed.add_field(name='Hosted By', value=f'<@!{details["host"]}>')
if details['requirements']:
req_ping = [f'<@&{i}>' for i in details['requirements']]
embed.add_field(name='Requirements (Match one of them)', value=', '.join(req_ping))
embed.add_field(name='Winner' + ('s' if len(winners) >= 2 else ''), value=', '.join(winners_ping))
if details['image'] is not None:
embed.set_image(url=details['image'])
embed.timestamp = datetime.datetime.utcfromtimestamp(details['created_at'] + details['length'])
embed.set_footer(text=f'ID: {ga_id}| Ended At')
cc = bot.get_channel(details['channel_id'])
mc = await cc.fetch_message(details['message_id'])
await mc.edit(embed=embed)
await cc.send(
f'Giveaway of {details["prize_name"]} (ID:{str(ga_id)}) has been rolled, winners: {" ".join(winners_ping)}\nCongratulations!')
@tasks.loop(seconds=1)
async def invalidate_and_check_ongoing_gates():
if not bot.loaded_db:
return
await db.clear_expired_gates(bot.db)
for iiiii in await db.get_ending_soon_gates(bot.db):
msg = await bot.get_channel(int(iiiii['channel_id'])).fetch_message(int(iiiii['id']))
for i in msg.reactions:
async for ii in i.users():
if ii.bot:
continue
if isinstance(ii, discord.User):
await i.remove(ii)
continue
ii_roles = [iii.id for iii in ii.roles]
if not any(iii in ii_roles for iii in iiiii['requirements']):
await i.remove(ii)
logging.info(f'Removed {str(ii.id)} from {str(iiiii["id"])}')
@check_giveaways.error
async def check_giveaways_error_handler(error):
logging.error('Task check_giveaways has failed')
traceback.print_exception(type(error), error, error.__traceback__)
class Canceled(Exception):
pass
@bot.command(name='new', usage='new', description='Launches an interactive session of creating a new giveaway')
@commands.has_any_role(593163327304237098, 764541727494504489, 637823625558229023, 598197239688724520)
async def new_giveaway(ctx):
def check(message):
if message.content == 'cancel':
raise Canceled
return message.author.id == ctx.author.id and message.channel.id == ctx.channel.id
await ctx.send(
'Welcome to Interactive Giveaway Creator, please answer some questions before you make a giveaway.\nType `cancel` anytime to terminate the interactive session\n\n1. Where would you like to create the giveaway at?')
try:
# ask for channel, time, winners, prize name, showcase image, donor, requirements
msg = await bot.wait_for('message', check=check, timeout=240)
channel = await TextChannelConverter().convert(ctx, msg.content)
await ctx.send(
f'Channel will be {channel.mention}\n\n2.How long should the giveaway last for? (suffixes: w-week d-day h-hour, m-minute, s-second)')
msg = await bot.wait_for('message', check=check, timeout=240)
length = convert_time(msg.content)
await ctx.send(
f'It will last for {humanize.naturaltime(length, future=True).rstrip(" from now")}\n\n3. How many winners should there be?')
msg = await bot.wait_for('message', check=check, timeout=240)
winner_count = int(msg.content)
await ctx.send(
f'There will be {str(winner_count)} winner{"s" if winner_count > 1 else ""}\n\n4. What should the prize be?')
msg = await bot.wait_for('message', check=check, timeout=240)
prize_name = msg.content
await ctx.send(
f'The prize will be {prize_name}\n\n5. Any showcase images for it? (Only one allowed) (Please post a link to the file or upload it as an attachment) (Type none or n if there isn\'t any)')
msg = await bot.wait_for('message', check=check, timeout=240)
if msg.content.lower() == 'none' or msg.content.lower() == 'n':
image = None
else:
if len(msg.attachments) <= 0:
image = msg.content
else:
image = msg.attachments[0].url
await ctx.send(
f'Image URL will be {image}\n\n6. Who donated for the giveaway? (You can enter their user ID / name#1234 or ping them)')
msg = await bot.wait_for('message', check=check, timeout=240)
host = await MemberConverter().convert(ctx, msg.content)
await ctx.send(
f'{host.mention} hosted the giveaway\n\n7. Set any requirements on this giveaway (Type in the role IDs, separate them in space, type none or n if there isn\'t any)')
msg = await bot.wait_for('message', check=check, timeout=240)
if msg.content.replace(' ', '').isdigit():
requirements = [int(i) for i in msg.content.split(' ')]
else:
requirements = None
if requirements is not None:
req_ping = [f'<@&{i}>' for i in requirements]
else:
req_ping = None
await ctx.send(
f'Please validate your selections:\n\nCHN {channel.mention}\nLGT `{length}`\nWNC `{winner_count}`\nPZN `{prize_name}`\nIMG `{image}`\nHST {host.mention}\nREQ {", ".join(req_ping) if req_ping is not None else "None"}\n\nType `yes` to start the giveaway',
allowed_mentions=discord.AllowedMentions.none())
msg = await bot.wait_for('message', check=check, timeout=240)
if 'y' not in msg.content:
raise InterruptedError
next_id = await db.get_next_id(bot.db)
creation_time = int(time.time())
embed = discord.Embed(title=prize_name,
colour=discord.Colour.from_rgb(random.randint(0, 255), random.randint(0, 255),
random.randint(0, 255)))
embed.add_field(name='Hosted By', value=host.mention)
if requirements is not None:
embed.add_field(name='Requirements (Match one of them)', value=', '.join(req_ping))
embed.add_field(name='Winners', value=str(winner_count))
if image is not None:
embed.set_image(url=image)
embed.timestamp = datetime.datetime.utcfromtimestamp(creation_time + length)
embed.set_footer(text=f'ID: {next_id}| Ends At')
sent = await channel.send(embed=embed)
sent_ctx = await bot.get_context(sent)
await db.create_giveaway(bot.db, next_id, sent_ctx, length, prize_name, host.id, winner_count, image,
requirements,
creation_time)
await sent.add_reaction(tada_emoji)
except asyncio.TimeoutError:
await ctx.send('Timed out, all inputs have been discarded.')
except Canceled:
await ctx.send('Canceled, all inputs have been discarded')
except BadArgument:
await ctx.send('Channel or host invalid, terminating interactive session, all inputs have been discarded')
except InterruptedError:
await ctx.send('Last validation did not pass, terminating interactive session, all inputs have been discarded')
async def send_message_if_needed(guild, gates, member):
if gates['id'] not in bot.msg_sent:
bot.msg_sent[gates['id']] = []
if member.id not in bot.msg_sent[gates['id']]:
bot.msg_sent[gates['id']].append(member.id)
embed = discord.Embed(title='\u274c**|**Giveaway Participation Attempt Failed',
colour=discord.Colour.red())
req_roles = [guild.get_role(iiii) for iiii in gates['requirements']]
embed.add_field(name='You are missing **one of the following** roles: ',
value='\n'.join([iiii.name for iiii in req_roles]), inline=False)
embed.add_field(name='You can check the following spreadsheet to learn how to get them: ',
value='https://docs.google.com/document/d/1r4rs_7KsopvFD99SQUKYteLjkXiJcI5jwlW5QNZFfgE',
inline=False)
await member.send(embed=embed)
@bot.event
async def on_raw_reaction_add(payload: discord.RawReactionActionEvent):
if payload.user_id == bot.user.id:
return
giveaway = await db.search_giveaway(bot.db, 'message_id', payload.message_id)
gates = await db.search_gate(bot.db, payload.channel_id, payload.message_id)
if giveaway is None and gates is None:
return
if gates is not None:
guild = bot.get_guild(payload.guild_id)
member = guild.get_member(payload.user_id)
roles = [iii.id for iii in member.roles]
if not any(iii in roles for iii in gates['requirements']):
message = await bot.get_channel(gates['channel_id']).fetch_message(gates['id'])
for i in message.reactions:
i: discord.Reaction
if str(payload.emoji) == str(i.emoji):
await i.remove(bot.get_user(payload.user_id))
if member is None:
pass
else:
await send_message_if_needed(guild, gates, member)
logging.info(f'Removed {str(payload.user_id)} from {str(payload.message_id)}')
if giveaway is None:
return
if giveaway['winners'] is not None:
return
member = bot.get_guild(payload.guild_id).get_member(payload.user_id)
res = await db.add_participant(bot.db, giveaway['id'], member)
msg = await bot.get_guild(payload.guild_id).get_channel(payload.channel_id).fetch_message(payload.message_id)
msg: discord.Message
if not res:
await msg.remove_reaction(tada_emoji, member)
await member.send(
f'Your attempt on participating in the giveaway at {msg.jump_url} has been denied, due to the insufficient requirements you meet')
else:
await member.send(f'You have successfully participated in the giveaway at {msg.jump_url}')
@bot.event
async def on_raw_reaction_remove(payload: discord.RawReactionActionEvent):
if payload.user_id == bot.user.id:
return
giveaway = await db.search_giveaway(bot.db, 'message_id', payload.message_id)
if giveaway is None:
return
if giveaway['winners'] is not None:
return
member = bot.get_guild(payload.guild_id).get_member(payload.user_id)
await db.remove_participant(bot.db, giveaway['id'], member)
await member.send(
f'You have successfully unparticipated the giveaway at https://discord.com/channels/{payload.guild_id}/{payload.channel_id}/{payload.message_id}')
return
@bot.command(name='reroll', usage='reroll <giveaway_id>', description='Rerolls the giveaway')
@commands.has_any_role(593163327304237098, 764541727494504489, 637823625558229023, 598197239688724520)
async def reroll(ctx: commands.Context, giveaway_id: int):
ga = await db.search_giveaway(bot.db, 'id', giveaway_id)
if ga is None:
await ctx.send(f'Reroll failed, giveaway ID {giveaway_id} does not exist')
chn = ctx.guild.get_channel(ga['channel_id'])
msg = await chn.fetch_message(ga['message_id'])
new_winners = await db.roll_winner(bot.db, giveaway_id)
winners_ping = [f'<@!{str(i)}>' for i in new_winners]
embed = discord.Embed(title=ga['prize_name'] + ' (Rerolled)'
)
embed.add_field(name='Hosted By', value=f'<@!{ga["host"]}>')
if ga['requirements']:
req_ping = [f'<@&{i}>' for i in ga['requirements']]
embed.add_field(name='Requirements (Match one of them)', value=', '.join(req_ping))
embed.add_field(name='Winner' + ('s' if len(new_winners) >= 2 else ''), value=', '.join(winners_ping))
if ga['image'] is not None:
embed.set_image(url=ga['image'])
embed.timestamp = datetime.datetime.utcfromtimestamp(ga['created_at'] + ga['length'])
embed.set_footer(text=f'ID: {giveaway_id}| Ended At')
await msg.edit(embed=embed)
await chn.send(
f'Giveaway of {ga["prize_name"]} (ID:{str(giveaway_id)}) has been rerolled, new winners: {" ".join(winners_ping)}\nCongratulations!')
@bot.command(name='forceaddparticipant', usage='forceaddparticipant <giveaway_id> <member>',
description='Force adds a pariticpant to the giveaway', aliases=['fadd'])
@commands.is_owner()
async def forceaddpariticpant(ctx, giveaway_id: int, member: discord.Member):
ga = await db.search_giveaway(bot.db, 'id', giveaway_id)
if ga is None:
await ctx.send(f'Force add failed, giveaway ID {giveaway_id} does not exist')
await db.add_participant(bot.db, giveaway_id, member)
@bot.command(name='quick',
usage='quick <channel> <length> <winner_count> <host> <prize_name>',
description='Creates a giveaway with one-line command, but without the ability to specify optional parameters, such as showcase image and requirements',
aliases=['q'])
@commands.has_any_role(593163327304237098, 764541727494504489, 637823625558229023, 598197239688724520)
async def quick(ctx: commands.Context, channel: discord.TextChannel, length: typing.Union[int, str], winner_count: int,
host: discord.Member, *, prize_name: str):
await ctx.send(
f'Giveaway will be created with the following parameters:\n\nCHN {channel.mention}\nLGT `{length}`\nWNC `{winner_count}`\nPZN `{prize_name}`',
allowed_mentions=discord.AllowedMentions.none())
next_id = await db.get_next_id(bot.db)
creation_time = int(time.time())
embed = discord.Embed(title=prize_name,
colour=discord.Colour.from_rgb(random.randint(0, 255), random.randint(0, 255),
random.randint(0, 255)))
embed.add_field(name='Hosted By', value=host.mention)
embed.add_field(name='Winners', value=str(winner_count))
embed.timestamp = datetime.datetime.utcfromtimestamp(creation_time + length)
embed.set_footer(text=f'ID: {next_id}| Ends At')
sent = await channel.send(embed=embed)
sent_ctx = await bot.get_context(sent)
await db.create_giveaway(bot.db, next_id, sent_ctx, length, prize_name, host.id, winner_count, None,
[],
creation_time)
await sent.add_reaction(tada_emoji)
@bot.group(name='gate', usage='gate <subcommand>', description='A series of reaction gates related commands',
invoke_without_command=True)
async def gate(ctx):
await ctx.send(
'You shall be using some other commands, instead of this one, think about what you could have done in this 3 seconds typing and wating for this message to appear...')
async def parse_requirements(requirements: str):
requirements = requirements.split(' ')
new_reqs = []
for req in requirements:
if isinstance(req, str):
if not req.isdigit():
res = await db.get_gate_template(bot.db, req)
if res is not None:
new_reqs.extend(res)
else:
new_reqs.append(int(req))
else:
new_reqs.append(req)
return new_reqs
@gate.command(name='add', usage='gate add <channel> <message_id> <interval> <requirements>',
description='Adds a requirement gate to the message, reacting any reactions on the message without matching the requirements will be denied and have it removed\nInterval formats as <amount><suffix> where available suffixes are w,d,h,m,s\nRequirements shall be splited with spaces')
@commands.has_any_role(593163327304237098, 764541727494504489, 637823625558229023, 598197239688724520)
async def add(ctx: commands.Context, channel: discord.TextChannel, message_id: int, interval: str, *,
requirements: str):
interval = int(convert_time(interval))
requirements = await parse_requirements(requirements)
try:
await db.add_gate(bot.db, channel.id, message_id, interval, requirements)
except asyncpg.UniqueViolationError:
await ctx.send('There has been already a gate on this message')
return
msg = await channel.fetch_message(message_id)
req_ping = [f'<@&{i}>' for i in requirements]
await ctx.send(
f'Gate added, with the following roles: {" ".join(req_ping)}, existing illegal reactions are being removed',
allowed_mentions=discord.AllowedMentions.none())
for i in msg.reactions:
async for ii in i.users():
if ii.bot:
continue
if isinstance(ii, discord.User):
await i.remove(ii)
continue
ii_roles = [iii.id for iii in ii.roles]
if not any(iii in ii_roles for iii in requirements):
await i.remove(ii)
logging.info(f'Removed {str(ii.id)} from {str(message_id)}')
@gate.command(name='modify', usage='gate modify <channel> <message_id> <new_requirements>',
description='Changes the requirement gate of message to new_requirements')
@commands.has_any_role(593163327304237098, 764541727494504489, 637823625558229023, 598197239688724520)
async def modify(ctx: commands.Context, channel: discord.TextChannel, message_id: int, new_requirements: str):
pr = await parse_requirements(new_requirements)
await db.modify_gate(bot.db, channel.id, message_id, pr)
req_ping = [f'<@&{i}>' for i in pr]
await ctx.send(f'Gate modified, with the new requirements: {" ".join(req_ping)}',
allowed_mentions=discord.AllowedMentions.none())
@gate.command(name='remove', usage='gate remove <channel> <message_id>', aliases=['delete'])
@commands.has_any_role(593163327304237098, 764541727494504489, 637823625558229023, 598197239688724520)
async def remove(ctx: commands.Context, channel: discord.TextChannel, message_id: int):
await db.remove_gate(bot.db, channel.id, message_id)
await ctx.send('Gate removed.')
@gate.command(name='qualifycheck', usage='gate qualifycheck <channel> <message_id>')
@commands.has_any_role(593163327304237098, 764541727494504489, 637823625558229023, 598197239688724520)
async def qualifycheck(ctx: commands.Context, channel: discord.TextChannel, message_id: int):
msg = await channel.fetch_message(message_id)
bombarded = 0
requirements = (await db.search_gate(bot.db, channel.id, message_id))['requirements']
for i in msg.reactions:
async for ii in i.users():
if ii.bot:
continue
if isinstance(ii, discord.User):
await i.remove(ii)
bombarded += 1
continue
ii_roles = [iii.id for iii in ii.roles]
if not any(iii in ii_roles for iii in requirements):
await i.remove(ii)
bombarded += 1
logging.info(f'Removed {str(ii.id)} from {str(message_id)}')
await ctx.send(f'{bombarded} users have lost their chance to the giveaway, feelin\' good')
@gate.command(name='bombard', usage='bombard',
description='Checks ALL the gates for the current server and remove those who don\'t qualify')
@commands.has_any_role(593163327304237098, 764541727494504489, 637823625558229023, 598197239688724520)
async def bombard(ctx: commands.Context):
gates = await db.list_gates(bot.db)
messages = []
for gate in gates:
bombarded = 0
msg = await bot.get_channel(gate['channel_id']).fetch_message(gate['id'])
for i in msg.reactions:
async for ii in i.users():
if ii.bot:
continue
if isinstance(ii, discord.User):
await i.remove(ii)
bombarded += 1
continue
ii_roles = [iii.id for iii in ii.roles]
if not any(iii in ii_roles for iii in gate['requirements']):
await i.remove(ii)
bombarded += 1
logging.info(f'Removed {str(ii.id)} from {str(msg.id)}')
messages.append(f'Removed {bombarded} people from {msg.jump_url}')
await ctx.send('\n'.join(messages))
@gate.command(name='purgeinvalid', usage='purgeinvalid', description='Pruges invalid gates', aliases=['pi'])
async def purge_invalid(ctx):
gates = await db.list_gates(bot.db)
removed = []
for gate in gates:
try:
await bot.get_channel(gate['channel_id']).fetch_message(gate['id'])
except discord.NotFound:
await db.remove_gate(bot.db, gate['channel_id'], gate['id'])
removed.append(
f'Removed {gate["id"]} at {bot.get_channel(gate["channel_id"]).mention} with the following roles: {" ".join(["<@&" + str(i) + ">" for i in gate["requirements"]])}')
await ctx.send("\n".join(removed), allowed_mentions=discord.AllowedMentions.none())
@gate.group(name='template', usage='template <subcommand>', description='Manage the gate templates',
invoke_without_command=True)
@commands.has_any_role(615756323589718046, 606228008134639636, 590693437922344960, 637823625558229023)
async def template(ctx):
await ctx.send('Should you be using my sub-commands now?')
@template.command(name='add', usage='add <template_id> <roles>')
@commands.has_any_role(615756323589718046, 606228008134639636, 590693437922344960, 637823625558229023)
async def add_template(ctx: commands.Context, template_id: str, *, roles: str):
roles = await parse_requirements(roles)
await db.add_gate_template(bot.db, template_id, roles)
res = await db.get_gate_template(bot.db, template_id)
await ctx.send(
f'Template {template_id} created with the following roles: {" ".join(["<@&" + str(i) + ">" for i in res])}',
allowed_mentions=discord.AllowedMentions.none())
@template.command(name='remove', usage='remove <template_id>')
@commands.has_any_role(615756323589718046, 606228008134639636, 590693437922344960, 637823625558229023)
async def remove(ctx: commands.Context, template_id: str):
await db.remove_gate_template(bot.db, template_id)
await ctx.send('Template removed.')
@template.command(name='alias', usage='alias <template_id> <aliases separated by space>')
@commands.has_any_role(615756323589718046, 606228008134639636, 590693437922344960, 637823625558229023)
async def alias(ctx: commands.Context, template_id: str, *, aliases: str):
await db.add_template_alias(bot.db, template_id, aliases.split(' '))
await ctx.send('Template aliased.')
@template.command(name='unalias', usage='unalias <template_id> <aliases separated by space>')
@commands.has_any_role(615756323589718046, 606228008134639636, 590693437922344960, 637823625558229023)
async def unalias(ctx: commands.Context, template_id: str, *, aliases: str):
for alias in aliases.split(' '):
await db.remove_template_alias(bot.db, template_id, alias)
await ctx.send(f'Alias{"es" if len(aliases.split(" ")) > 1 else ""} removed.')
@template.command(name='addrole', usage='addrole <template_id> <roles>')
@commands.has_any_role(615756323589718046, 606228008134639636, 590693437922344960, 637823625558229023)
async def addrole(ctx: commands.Context, template_id: str, *, roles: str):
await db.purge_template_invalid_roles(bot.db, ctx.guild, template_id)
roles = await parse_requirements(roles)
for role in roles:
await db.add_template_role(bot.db, template_id, role)
res = await db.get_gate_template(bot.db, template_id)
await ctx.send(
f'Template {template_id} added roles now with the following roles: {" ".join(["<@&" + str(i) + ">" for i in res])}',
allowed_mentions=discord.AllowedMentions.none())
@template.command(name='removerole', usage='removerole <template_id> <roles>', aliases=['unrole', 'rmrole'])
@commands.has_any_role(615756323589718046, 606228008134639636, 590693437922344960, 637823625558229023)
async def rmrole(ctx: commands.Context, template_id: str, *, roles: str):
await db.purge_template_invalid_roles(bot.db, ctx.guild, template_id)
roles = await parse_requirements(roles)
for role in roles:
await db.remove_template_role(bot.db, template_id, role)
res = await db.get_gate_template(bot.db, template_id)
await ctx.send(
f'Template {template_id} removed roles now with the following roles: {" ".join(["<@&" + str(i) + ">" for i in res])}',
allowed_mentions=discord.AllowedMentions.none())
@template.command(name='get', usage='get <template_id>')
async def getroles(ctx: commands.Context, template_id: str):
roles = await db.get_gate_template(bot.db, template_id)
roles = [f'<@&{x}>' for x in roles]
await ctx.send(f'Template {template_id} has the following roles: {" ".join(roles)}',
allowed_mentions=discord.AllowedMentions.none())
@template.command(name='list', usage='list')
@commands.has_any_role(615756323589718046, 606228008134639636, 590693437922344960, 637823625558229023)
async def template_list(ctx: commands.Context):
msg = ''
for template in await db.list_templates(bot.db):
roles = [f'<@&{r}>' for r in template['roles']]
msg += f'`{template["id"]}` -> {",".join(roles)}\n'
if len(msg) >= 1800:
await ctx.send(msg, allowed_mentions=discord.AllowedMentions.none())
msg = ''
if len(msg) > 0:
await ctx.send(msg, allowed_mentions=discord.AllowedMentions.none())
@bot.command(name='reboot')
@commands.is_owner()
async def reboot(ctx):
await ctx.send('Shutting down...')
await bot.close()
check_giveaways.start()
invalidate_and_check_ongoing_gates.start()
bot.run(tokens['token'])