@@ -118,19 +118,44 @@ async def on_ready(self) -> None:
118
118
"""Override the on_ready method"""
119
119
print (f'Bot is ready as { self .user .name } #{ self .user .discriminator } ' )
120
120
121
- if self ._config .channel_id is not None :
122
- channel = bot .get_channel (self ._config .channel_id )
123
-
124
- if self ._config .current_member_id is not None :
125
- member = await channel .guild .fetch_member (self ._config .current_member_id )
126
- await channel .send (
127
- f'I\' m now online! Last counted by { member .mention } . The **next** number is '
128
- f'**{ self ._config .current_count + 1 } **.' )
129
- else :
130
- await channel .send (f'I\' m now online!' )
121
+ busy_work_necessary : bool = False
122
+
123
+ if self ._config .channel_id :
124
+
125
+ channel : Optional [discord .TextChannel ] = bot .get_channel (self ._config .channel_id )
126
+ if channel : # It is possible that the channel was removed, so check if channel exists
127
+
128
+ emb : discord .Embed = discord .Embed (description = ':green_circle: **I\' m now online!**' ,
129
+ colour = discord .Color .brand_green ())
130
+
131
+ if self ._config .high_score > 0 :
132
+ emb .description += (f'\n \n :fire: Let\' s beat the high score of { self ._config .high_score } ! '
133
+ f':muscle:\n ' )
134
+
135
+ emb .add_field (name = 'NEXT number' , value = f'{ self ._config .current_count + 1 } ' , inline = True )
136
+
137
+ if self ._config .current_member_id :
138
+
139
+ member : Optional [discord .Member ] = channel .guild .get_member (self ._config .current_member_id )
140
+ if member : # It is possible that the member has left the server, so check if member exists
141
+ emb .add_field (name = 'Last input by' , value = f'{ member .mention } ' , inline = True )
142
+
143
+ else : # Member has left the server.
144
+ self ._config .current_member_id = None
145
+ emb .add_field (name = 'Last input by' , value = f'An ex-member' , inline = True )
146
+ busy_work_necessary = True
147
+
148
+ await channel .send (embed = emb )
149
+
150
+ else : # Counting channel doesn't exist.
151
+ self ._config .channel_id = None
152
+ busy_work_necessary = True
131
153
132
154
self .set_roles ()
133
155
156
+ if busy_work_necessary :
157
+ await self .do_busy_work ()
158
+
134
159
def set_roles (self ):
135
160
"""
136
161
Sets the `self.failed_role` and `self.reliable_counter_role` variables.
@@ -247,12 +272,16 @@ async def on_message(self, message: discord.Message) -> None:
247
272
if not all (c in POSSIBLE_CHARACTERS for c in content ) or not any (char .isdigit () for char in content ):
248
273
return
249
274
275
+ zero_division : bool = False
276
+
250
277
try :
251
278
number : int = round (eval (content ))
252
279
except SyntaxError :
280
+ await message .add_reaction ('⚠️' )
281
+ await message .channel .send (f'Syntax error in mathematical expression!\n The chain has **not** been broken.' )
253
282
return
254
283
except ZeroDivisionError :
255
- return
284
+ zero_division = True
256
285
257
286
self ._busy += 1
258
287
@@ -273,40 +302,39 @@ async def on_message(self, message: discord.Message) -> None:
273
302
else :
274
303
highest_valid_count = stats [0 ]
275
304
276
- # --------------
277
- # Wrong number
278
- # --------------
279
- if int ( number ) != int (self ._config .current_count ) + 1 :
305
+ # -------------
306
+ # Wrong member
307
+ # -------------
308
+ if zero_division or (self ._config .current_count and self . _config . current_member_id == message . author . id ) :
280
309
281
310
if self .failed_role :
282
311
self ._config .failed_member_id = message .author .id # Designate current user as failed member
283
312
# Adding/removing failed role is done when not busy
284
313
285
- await self .handle_wrong_count (message )
314
+ await self .handle_wrong_member (message )
286
315
287
316
c .execute ('UPDATE members SET score = score - 1, wrong = wrong + 1 WHERE member_id = ?' ,
288
317
(message .author .id ,))
289
-
290
318
conn .commit ()
291
319
conn .close ()
292
320
293
321
await self .schedule_busy_work ()
294
-
295
322
return
296
323
297
- # -------------
298
- # Wrong member
299
- # -------------
300
- if self . _config . current_count and self ._config .current_member_id == message . author . id :
324
+ # --------------
325
+ # Wrong number
326
+ # --------------
327
+ if int ( number ) != int ( self ._config .current_count ) + 1 :
301
328
302
329
if self .failed_role :
303
330
self ._config .failed_member_id = message .author .id # Designate current user as failed member
304
331
# Adding/removing failed role is done when not busy
305
332
306
- await self .handle_wrong_member (message )
333
+ await self .handle_wrong_count (message )
307
334
308
335
c .execute ('UPDATE members SET score = score - 1, wrong = wrong + 1 WHERE member_id = ?' ,
309
336
(message .author .id ,))
337
+
310
338
conn .commit ()
311
339
conn .close ()
312
340
@@ -418,7 +446,7 @@ async def setup_hook(self) -> None:
418
446
419
447
420
448
@bot .tree .command (name = 'sync' , description = 'Syncs the slash commands to the bot' )
421
- @app_commands .checks . has_permissions (administrator = True , ban_members = True )
449
+ @app_commands .default_permissions (administrator = True , ban_members = True )
422
450
async def sync (interaction : discord .Interaction ):
423
451
"""Sync all the slash commands to the bot"""
424
452
if not interaction .user .guild_permissions .ban_members :
@@ -431,37 +459,45 @@ async def sync(interaction: discord.Interaction):
431
459
432
460
@bot .tree .command (name = 'set_channel' , description = 'Sets the channel to count in' )
433
461
@app_commands .describe (channel = 'The channel to count in' )
434
- @app_commands .checks . has_permissions (ban_members = True )
462
+ @app_commands .default_permissions (ban_members = True )
435
463
async def set_channel (interaction : discord .Interaction , channel : discord .TextChannel ):
436
464
"""Command to set the channel to count in"""
437
465
if not interaction .user .guild_permissions .ban_members :
438
466
await interaction .response .send_message ('You do not have permission to do this!' )
439
467
return
468
+ await interaction .response .defer ()
440
469
config = Config .read ()
441
470
config .channel_id = channel .id
442
471
config .dump_data ()
443
472
bot .read_config () # Explicitly ask the bot to re-read the config
444
- await interaction .response . send_message (f'Counting channel was set to { channel .mention } ' )
473
+ await interaction .followup . send (f'Counting channel was set to { channel .mention } ' )
445
474
446
475
447
- @bot .tree .command (name = 'listcmds' , description = 'Lists commands' )
448
- async def list_commands (interaction : discord .Interaction ):
476
+ @bot .tree .command (name = 'list_commands' , description = 'Lists commands' )
477
+ @app_commands .describe (ephemeral = 'Whether the output should be ephemeral' )
478
+ async def list_commands (interaction : discord .Interaction , ephemeral : bool = True ):
449
479
"""Command to list all the slash commands"""
450
480
emb = discord .Embed (title = 'Slash Commands' , color = discord .Color .blue (),
451
481
description = '''
452
- **sync** - Syncs the slash commands to the bot (Admins only)
453
- **set_channel** - Sets the channel to count in (Admins only)
454
- **listcmds** - Lists all the slash commands
482
+ **list_commands** - Lists all the slash commands
455
483
**stats_user** - Shows the stats of a specific user
456
484
**stats_server** - Shows the stats of the server
457
- **leaderboard** - Shows the leaderboard of the server
458
- **set_failed_role** - Sets the role to give when a user fails (Admins only)
459
- **set_reliable_role** - Sets the role to give when a user passes the score of 100 (Admins only)
460
- **remove_failed_role** - Removes the role to give when a user fails (Admins only)
461
- **remove_reliable_role** - Removes the role to give when a user passes the score of 100 (Admins only)
462
- **force_dump** - Forcibly dump bot config data. Use only when no one is actively playing. (Admins only)
463
- **prune** - Remove data for users who are no longer in the server. (Admins only)''' )
464
- await interaction .response .send_message (embed = emb )
485
+ **leaderboard** - Shows the leaderboard of the server''' )
486
+
487
+ if interaction .user .guild_permissions .ban_members :
488
+ emb .description += '''\n
489
+ __Restricted commands__ (Admin-only)
490
+ **sync** - Syncs the slash commands to the bot
491
+ **set_channel** - Sets the channel to count in
492
+ **set_failed_role** - Sets the role to give when a user fails
493
+ **set_reliable_role** - Sets the reliable role
494
+ **remove_failed_role** - Unsets the role to give when a user fails
495
+ **remove_reliable_role** - Unsets the reliable role
496
+ **force_dump** - Forcibly dump bot config data. Use only when no one is actively playing.
497
+ **prune** - Remove data for users who are no longer in the server.
498
+ '''
499
+
500
+ await interaction .response .send_message (embed = emb , ephemeral = ephemeral )
465
501
466
502
467
503
@bot .tree .command (name = 'stats_user' , description = 'Shows the user stats' )
@@ -503,28 +539,30 @@ async def stats_user(interaction: discord.Interaction, member: discord.Member =
503
539
@bot .tree .command (name = "stats_server" , description = "View server counting stats" )
504
540
async def stats_server (interaction : discord .Interaction ):
505
541
"""Command to show the stats of the server"""
542
+ await interaction .response .defer ()
543
+
506
544
# Use the bot's config variable, do not re-read file as it may not have been updated yet
507
545
config : Config = bot ._config
508
546
509
547
if config .channel_id is None : # channel not set yet
510
- await interaction .response . send_message ("Counting channel not set yet!" )
548
+ await interaction .followup . send ("Counting channel not set yet!" )
511
549
return
512
550
513
- server_stats_embed = discord .Embed (
514
- description = f'''**Current Count**: { config .current_count }
551
+ server_stats_embed = discord .Embed (description = f'''**Current Count**: { config .current_count }
515
552
High Score: { config .high_score }
516
553
{ f"Last counted by: <@{ config .current_member_id } >" if config .current_member_id else "" } ''' ,
517
554
color = discord .Color .blurple ()
518
555
)
519
556
server_stats_embed .set_author (name = interaction .guild , icon_url = interaction .guild .icon )
520
557
521
- await interaction .response . send_message (embed = server_stats_embed )
558
+ await interaction .followup . send (embed = server_stats_embed )
522
559
523
560
524
561
@bot .tree .command (name = 'leaderboard' , description = 'Shows the first 10 users with the highest score' )
525
562
async def leaderboard (interaction : discord .Interaction ):
526
563
"""Command to show the top 10 users with the highest score in Indently"""
527
564
await interaction .response .defer ()
565
+
528
566
emb = discord .Embed (title = 'Top 10 users in Indently' ,
529
567
color = discord .Color .blue (), description = '' )
530
568
@@ -547,12 +585,13 @@ async def leaderboard(interaction: discord.Interaction):
547
585
@app_commands .default_permissions (ban_members = True )
548
586
async def set_failed_role (interaction : discord .Interaction , role : discord .Role ):
549
587
"""Command to set the role to be used when a user fails to count"""
588
+ await interaction .response .defer ()
550
589
config = Config .read ()
551
590
config .failed_role_id = role .id
552
591
config .dump_data ()
553
592
bot .read_config () # Explicitly ask the bot to re-read the config
554
593
bot .set_roles () # Ask the bot to re-load the roles
555
- await interaction .response . send_message (f'Failed role was set to { role .mention } ' )
594
+ await interaction .followup . send (f'Failed role was set to { role .mention } . ' )
556
595
557
596
558
597
@bot .tree .command (name = 'set_reliable_role' ,
@@ -561,59 +600,64 @@ async def set_failed_role(interaction: discord.Interaction, role: discord.Role):
561
600
@app_commands .default_permissions (ban_members = True )
562
601
async def set_reliable_role (interaction : discord .Interaction , role : discord .Role ):
563
602
"""Command to set the role to be used when a user gets 100 of score"""
603
+ await interaction .response .defer ()
564
604
config = Config .read ()
565
605
config .reliable_counter_role_id = role .id
566
606
config .dump_data ()
567
607
bot .read_config () # Explicitly ask the bot to re-read the config
568
608
bot .set_roles () # Ask the bot to re-load the roles
569
- await interaction .response . send_message (f'Reliable role was set to { role .mention } ' )
609
+ await interaction .followup . send (f'Reliable role was set to { role .mention } . ' )
570
610
571
611
572
612
@bot .tree .command (name = 'remove_failed_role' , description = 'Removes the failed role feature' )
573
613
@app_commands .default_permissions (ban_members = True )
574
614
async def remove_failed_role (interaction : discord .Interaction ):
615
+ await interaction .response .defer ()
575
616
config = Config .read ()
576
617
config .failed_role_id = None
577
618
config .failed_member_id = None
578
619
config .correct_inputs_by_failed_member = 0
579
620
config .dump_data ()
580
621
bot .read_config () # Explicitly ask the bot to re-read the config
581
622
bot .set_roles () # Ask the bot to re-load the roles
582
- await interaction .response . send_message ('Failed role removed' )
623
+ await interaction .followup . send ('Failed role removed. ' )
583
624
584
625
585
626
@bot .tree .command (name = 'remove_reliable_role' , description = 'Removes the reliable role feature' )
586
627
@app_commands .default_permissions (ban_members = True )
587
628
async def remove_reliable_role (interaction : discord .Interaction ):
629
+ await interaction .response .defer ()
588
630
config = Config .read ()
589
631
config .reliable_counter_role_id = None
590
632
config .dump_data ()
591
633
bot .read_config () # Explicitly ask the bot to re-read the config
592
634
bot .set_roles () # Ask the bot to re-load the roles
593
- await interaction .response . send_message ('Reliable role removed' )
635
+ await interaction .followup . send ('Reliable role removed. ' )
594
636
595
637
596
638
@bot .tree .command (name = 'disconnect' , description = 'Makes the bot go offline' )
597
639
@app_commands .default_permissions (ban_members = True )
598
640
async def disconnect (interaction : discord .Interaction ):
599
- config = Config .read ()
600
- if config .channel_id is not None :
601
- channel = bot .get_channel (config .channel_id )
602
- await channel .send ('Bot is now offline.' )
641
+ emb : discord .Embed = discord .Embed (description = ':octagonal_sign: The bot is going offline :octagonal_sign:' ,
642
+ colour = discord .Colour .brand_red ())
643
+ await interaction .response .send_message (embed = emb )
603
644
await bot .close ()
604
645
605
646
606
647
@bot .tree .command (name = 'force_dump' , description = 'Forcibly dumps configuration data' )
607
648
@app_commands .default_permissions (ban_members = True )
608
649
async def force_dump (interaction : discord .Interaction ):
650
+ await interaction .response .defer ()
609
651
bot ._busy = 0
610
652
await bot .do_busy_work ()
611
- await interaction .response .send_message ('Configuration data successfully dumped.' )
653
+ emb = discord .Embed (description = f'✅ Configuration data successfully dumped.' , colour = discord .Colour .og_blurple ())
654
+ await interaction .followup .send (embed = emb )
612
655
613
656
614
657
@bot .tree .command (name = 'prune' , description = '(DANGER) Deletes data of users who are no longer in the server' )
615
658
@app_commands .default_permissions (ban_members = True )
616
659
async def prune (interaction : discord .Interaction ):
660
+ await interaction .response .defer ()
617
661
618
662
conn : sqlite3 .Connection = sqlite3 .connect ('database.sqlite3' )
619
663
cursor : sqlite3 .Cursor = conn .cursor ()
@@ -634,15 +678,44 @@ async def prune(interaction: discord.Interaction):
634
678
635
679
if count > 0 :
636
680
conn .commit ()
637
- await interaction .response . send_message (f'Successfully removed data for { count } user(s).' )
681
+ await interaction .followup . send (f'Successfully removed data for { count } user(s).' )
638
682
else :
639
- await interaction .response . send_message ('No users met the criteria to be removed.' )
683
+ await interaction .followup . send ('No users met the criteria to be removed.' )
640
684
641
685
else :
642
- await interaction .response . send_message ('No users found in the database.' )
686
+ await interaction .followup . send ('No users found in the database.' )
643
687
644
688
conn .close ()
645
689
646
690
691
+ @bot .tree .command (name = 'calc' , description = 'Evaluate a mathematical expression' )
692
+ @app_commands .describe (expression = 'The mathematical expression to be evaluated' )
693
+ async def calc (interaction : discord .Interaction , expression : str ) -> None :
694
+ await interaction .response .defer ()
695
+
696
+ emb : discord .Embed = discord .Embed (description = '' )
697
+
698
+ if not all (c in POSSIBLE_CHARACTERS for c in expression ) or not any (char .isdigit () for char in expression ):
699
+ emb .description = f'**Expression:** `{ expression } `\n \n ❌ Invalid mathematical expression!'
700
+ emb .colour = discord .Colour .brand_red ()
701
+ await interaction .followup .send (embed = emb )
702
+ return
703
+
704
+ try :
705
+ number : int = round (eval (expression ))
706
+ emb .description = f'**Expression:** `{ expression } `\n \n **Result:** `{ number } `'
707
+ emb .colour = discord .Colour .brand_green ()
708
+ await interaction .followup .send (embed = emb )
709
+ except SyntaxError :
710
+ emb .description = f'**Expression:** `{ expression } `\n \n ❌ Invalid mathematical expression!'
711
+ emb .colour = discord .Colour .brand_red ()
712
+ await interaction .followup .send (embed = emb )
713
+ return
714
+ except ZeroDivisionError :
715
+ emb .description = f'**Expression:** `{ expression } `\n \n ❌ Division by zero!'
716
+ emb .colour = discord .Colour .brand_red ()
717
+ await interaction .followup .send (embed = emb )
718
+ return
719
+
647
720
if __name__ == '__main__' :
648
721
bot .run (TOKEN )
0 commit comments