Skip to content

Commit ed7627f

Browse files
Merge pull request #18 from WrichikBasu/dev
Improved bot from multiple directions
2 parents d79d51a + 40a5da7 commit ed7627f

File tree

1 file changed

+128
-55
lines changed

1 file changed

+128
-55
lines changed

main.py

+128-55
Original file line numberDiff line numberDiff line change
@@ -118,19 +118,44 @@ async def on_ready(self) -> None:
118118
"""Override the on_ready method"""
119119
print(f'Bot is ready as {self.user.name}#{self.user.discriminator}')
120120

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
131153

132154
self.set_roles()
133155

156+
if busy_work_necessary:
157+
await self.do_busy_work()
158+
134159
def set_roles(self):
135160
"""
136161
Sets the `self.failed_role` and `self.reliable_counter_role` variables.
@@ -247,12 +272,16 @@ async def on_message(self, message: discord.Message) -> None:
247272
if not all(c in POSSIBLE_CHARACTERS for c in content) or not any(char.isdigit() for char in content):
248273
return
249274

275+
zero_division: bool = False
276+
250277
try:
251278
number: int = round(eval(content))
252279
except SyntaxError:
280+
await message.add_reaction('⚠️')
281+
await message.channel.send(f'Syntax error in mathematical expression!\nThe chain has **not** been broken.')
253282
return
254283
except ZeroDivisionError:
255-
return
284+
zero_division = True
256285

257286
self._busy += 1
258287

@@ -273,40 +302,39 @@ async def on_message(self, message: discord.Message) -> None:
273302
else:
274303
highest_valid_count = stats[0]
275304

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):
280309

281310
if self.failed_role:
282311
self._config.failed_member_id = message.author.id # Designate current user as failed member
283312
# Adding/removing failed role is done when not busy
284313

285-
await self.handle_wrong_count(message)
314+
await self.handle_wrong_member(message)
286315

287316
c.execute('UPDATE members SET score = score - 1, wrong = wrong + 1 WHERE member_id = ?',
288317
(message.author.id,))
289-
290318
conn.commit()
291319
conn.close()
292320

293321
await self.schedule_busy_work()
294-
295322
return
296323

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:
301328

302329
if self.failed_role:
303330
self._config.failed_member_id = message.author.id # Designate current user as failed member
304331
# Adding/removing failed role is done when not busy
305332

306-
await self.handle_wrong_member(message)
333+
await self.handle_wrong_count(message)
307334

308335
c.execute('UPDATE members SET score = score - 1, wrong = wrong + 1 WHERE member_id = ?',
309336
(message.author.id,))
337+
310338
conn.commit()
311339
conn.close()
312340

@@ -418,7 +446,7 @@ async def setup_hook(self) -> None:
418446

419447

420448
@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)
422450
async def sync(interaction: discord.Interaction):
423451
"""Sync all the slash commands to the bot"""
424452
if not interaction.user.guild_permissions.ban_members:
@@ -431,37 +459,45 @@ async def sync(interaction: discord.Interaction):
431459

432460
@bot.tree.command(name='set_channel', description='Sets the channel to count in')
433461
@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)
435463
async def set_channel(interaction: discord.Interaction, channel: discord.TextChannel):
436464
"""Command to set the channel to count in"""
437465
if not interaction.user.guild_permissions.ban_members:
438466
await interaction.response.send_message('You do not have permission to do this!')
439467
return
468+
await interaction.response.defer()
440469
config = Config.read()
441470
config.channel_id = channel.id
442471
config.dump_data()
443472
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}')
445474

446475

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):
449479
"""Command to list all the slash commands"""
450480
emb = discord.Embed(title='Slash Commands', color=discord.Color.blue(),
451481
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
455483
**stats_user** - Shows the stats of a specific user
456484
**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)
465501

466502

467503
@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 =
503539
@bot.tree.command(name="stats_server", description="View server counting stats")
504540
async def stats_server(interaction: discord.Interaction):
505541
"""Command to show the stats of the server"""
542+
await interaction.response.defer()
543+
506544
# Use the bot's config variable, do not re-read file as it may not have been updated yet
507545
config: Config = bot._config
508546

509547
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!")
511549
return
512550

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}
515552
High Score: {config.high_score}
516553
{f"Last counted by: <@{config.current_member_id}>" if config.current_member_id else ""}''',
517554
color=discord.Color.blurple()
518555
)
519556
server_stats_embed.set_author(name=interaction.guild, icon_url=interaction.guild.icon)
520557

521-
await interaction.response.send_message(embed=server_stats_embed)
558+
await interaction.followup.send(embed=server_stats_embed)
522559

523560

524561
@bot.tree.command(name='leaderboard', description='Shows the first 10 users with the highest score')
525562
async def leaderboard(interaction: discord.Interaction):
526563
"""Command to show the top 10 users with the highest score in Indently"""
527564
await interaction.response.defer()
565+
528566
emb = discord.Embed(title='Top 10 users in Indently',
529567
color=discord.Color.blue(), description='')
530568

@@ -547,12 +585,13 @@ async def leaderboard(interaction: discord.Interaction):
547585
@app_commands.default_permissions(ban_members=True)
548586
async def set_failed_role(interaction: discord.Interaction, role: discord.Role):
549587
"""Command to set the role to be used when a user fails to count"""
588+
await interaction.response.defer()
550589
config = Config.read()
551590
config.failed_role_id = role.id
552591
config.dump_data()
553592
bot.read_config() # Explicitly ask the bot to re-read the config
554593
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}.')
556595

557596

558597
@bot.tree.command(name='set_reliable_role',
@@ -561,59 +600,64 @@ async def set_failed_role(interaction: discord.Interaction, role: discord.Role):
561600
@app_commands.default_permissions(ban_members=True)
562601
async def set_reliable_role(interaction: discord.Interaction, role: discord.Role):
563602
"""Command to set the role to be used when a user gets 100 of score"""
603+
await interaction.response.defer()
564604
config = Config.read()
565605
config.reliable_counter_role_id = role.id
566606
config.dump_data()
567607
bot.read_config() # Explicitly ask the bot to re-read the config
568608
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}.')
570610

571611

572612
@bot.tree.command(name='remove_failed_role', description='Removes the failed role feature')
573613
@app_commands.default_permissions(ban_members=True)
574614
async def remove_failed_role(interaction: discord.Interaction):
615+
await interaction.response.defer()
575616
config = Config.read()
576617
config.failed_role_id = None
577618
config.failed_member_id = None
578619
config.correct_inputs_by_failed_member = 0
579620
config.dump_data()
580621
bot.read_config() # Explicitly ask the bot to re-read the config
581622
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.')
583624

584625

585626
@bot.tree.command(name='remove_reliable_role', description='Removes the reliable role feature')
586627
@app_commands.default_permissions(ban_members=True)
587628
async def remove_reliable_role(interaction: discord.Interaction):
629+
await interaction.response.defer()
588630
config = Config.read()
589631
config.reliable_counter_role_id = None
590632
config.dump_data()
591633
bot.read_config() # Explicitly ask the bot to re-read the config
592634
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.')
594636

595637

596638
@bot.tree.command(name='disconnect', description='Makes the bot go offline')
597639
@app_commands.default_permissions(ban_members=True)
598640
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)
603644
await bot.close()
604645

605646

606647
@bot.tree.command(name='force_dump', description='Forcibly dumps configuration data')
607648
@app_commands.default_permissions(ban_members=True)
608649
async def force_dump(interaction: discord.Interaction):
650+
await interaction.response.defer()
609651
bot._busy = 0
610652
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)
612655

613656

614657
@bot.tree.command(name='prune', description='(DANGER) Deletes data of users who are no longer in the server')
615658
@app_commands.default_permissions(ban_members=True)
616659
async def prune(interaction: discord.Interaction):
660+
await interaction.response.defer()
617661

618662
conn: sqlite3.Connection = sqlite3.connect('database.sqlite3')
619663
cursor: sqlite3.Cursor = conn.cursor()
@@ -634,15 +678,44 @@ async def prune(interaction: discord.Interaction):
634678

635679
if count > 0:
636680
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).')
638682
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.')
640684

641685
else:
642-
await interaction.response.send_message('No users found in the database.')
686+
await interaction.followup.send('No users found in the database.')
643687

644688
conn.close()
645689

646690

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+
647720
if __name__ == '__main__':
648721
bot.run(TOKEN)

0 commit comments

Comments
 (0)