Added possibility to confirm some actions

This commit is contained in:
Elnath 2021-06-12 19:44:41 +02:00
parent 15945d839c
commit 03cee9963b
1 changed files with 67 additions and 3 deletions

View File

@ -1,8 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import asyncio
import logging import logging
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Dict, Tuple, Coroutine
import discord import discord
import discord.utils import discord.utils
@ -21,11 +23,14 @@ class SecretBot(commands.Cog):
self.bot = commands.Bot( self.bot = commands.Bot(
"!", "!",
help_command = commands.MinimalHelpCommand(), help_command = commands.MinimalHelpCommand(),
intents = discord.Intents(guild_messages = True, guilds = True, members = True), intents = discord.Intents(guild_messages = True, guilds = True, members = True, reactions = True),
allowed_mentions = discord.AllowedMentions.none(), # By default we do not allow mentions, as we will white-list them on each required message allowed_mentions = discord.AllowedMentions.none(), # By default we do not allow mentions, as we will white-list them on each required message
) )
self.bot.add_cog(self) self.bot.add_cog(self)
self.games_file = games_file self.games_file = games_file
# Each guild can have a confirmation message to execute an action. We store:
# The confirmation message, the coroutine to execute if the user reacts with :thumbsup: to the confirmation message, a task that will delete the message after a certain time (so that we can stop it if the user confirms instead)
self.confirmation_messages: Dict[discord.guild, Tuple[discord.Message, Coroutine, asyncio.Task]] = {}
def run(self, token): def run(self, token):
self.bot.run(token) self.bot.run(token)
@ -114,6 +119,54 @@ class SecretBot(commands.Cog):
await ctx.reply(":warning: You should do this in the admin channel!") await ctx.reply(":warning: You should do this in the admin channel!")
raise utils.CheckFailDoNotNotify raise utils.CheckFailDoNotNotify
async def confirm_action(self, message_text, text_channel: discord.TextChannel, action: Coroutine, reply_to: discord.Message = None, max_delay = 60):
"""
Asks the user to confirm an action by reacting to a message.
If the user does not react in max_delay seconds, the confirmation message is deleted and the action is canceled.
For now, only one such confirmation per server is allowed. If another confirmation is asked while a previous one exists, the previous one is treated as if the delay passed.
"""
if text_channel.guild in self.confirmation_messages: # If there was already a pending confirmation
# We delete it
message, action, task = self.confirmation_messages[text_channel.guild]
asyncio.create_task(message.delete())
action.close()
task.cancel()
message = await text_channel.send(message_text + "\nReact to this message with :thumbsup: to confirm or :thumbsdown: to cancel", reference = reply_to)
async def add_reaction_prompts():
await message.add_reaction("👍")
await message.add_reaction("👎")
asyncio.create_task(add_reaction_prompts())
async def delete_confirmation_message_after_max_delay():
await asyncio.sleep(max_delay)
del self.confirmation_messages[text_channel.guild]
action.close()
await message.delete()
self.confirmation_messages[text_channel.guild] = (message, action, asyncio.create_task(delete_confirmation_message_after_max_delay()))
@commands.Cog.listener()
async def on_reaction_add(self, reaction: discord.Reaction, user: discord.Member):
if user.bot: # We ignore reactions added by bots
return
if reaction.message.guild in self.confirmation_messages:
message, action, task = self.confirmation_messages[reaction.message.guild]
if reaction.message == message:
# Note that we do not check which user added the reaction.
# The confirmation message is just supposed to be a safeguard against typing mistakes
if reaction.emoji == "👍":
del self.confirmation_messages[reaction.message.guild]
task.cancel()
asyncio.create_task(message.delete())
await action
elif reaction.emoji == "👎":
del self.confirmation_messages[reaction.message.guild]
task.cancel()
action.close()
await message.delete()
@commands.command(help = "See if I'm alive") @commands.command(help = "See if I'm alive")
async def ping(self, ctx: commands.Context): async def ping(self, ctx: commands.Context):
await ctx.reply(":ping_pong:", mention_author = True) await ctx.reply(":ping_pong:", mention_author = True)
@ -130,9 +183,13 @@ class SecretBot(commands.Cog):
await ctx.reply(":white_check_mark: Game started!") await ctx.reply(":white_check_mark: Game started!")
@commands.command("DeleteGame", help = "Delete a running game and all of its associated channels") @commands.command("DeleteGame", help = "Delete a running game and all of its associated channels")
async def delete_game_with_confirmation(self, ctx: commands.Context):
await self.get_running_game_or_error_message(ctx)
await self.check_is_administrator_or_gm(ctx)
await self.confirm_action("Do you really want to delete the current game?", ctx.channel, self.delete_game(ctx), ctx.message)
async def delete_game(self, ctx: commands.Context): async def delete_game(self, ctx: commands.Context):
game = await self.get_running_game_or_error_message(ctx) game = await self.get_running_game_or_error_message(ctx)
await self.check_is_administrator_or_gm(ctx)
gm_role = game.get_gm_role() gm_role = game.get_gm_role()
await game.delete() await game.delete()
await ctx.guild.get_member(self.bot.user.id).remove_roles(gm_role) await ctx.guild.get_member(self.bot.user.id).remove_roles(gm_role)
@ -174,9 +231,16 @@ class SecretBot(commands.Cog):
await self.cast_vote(ctx, False) await self.cast_vote(ctx, False)
@commands.command("StopTheCount") @commands.command("StopTheCount")
async def stop_vote(self, ctx: commands.Context): async def stop_vote_with_confirmation(self, ctx: commands.Context):
game = await self.get_running_game_or_error_message(ctx) game = await self.get_running_game_or_error_message(ctx)
await self.check_is_administrator_or_gm(ctx) await self.check_is_administrator_or_gm(ctx)
if not game.is_vote_running():
await ctx.reply(":x: No vote is running")
return
await self.confirm_action("Do you really want to stop the vote and reveal all votes?", ctx.channel, self.stop_vote(ctx), ctx.message)
async def stop_vote(self, ctx: commands.Context):
game = await self.get_running_game_or_error_message(ctx)
if not game.is_vote_running(): if not game.is_vote_running():
await ctx.reply(":x: No vote is running") await ctx.reply(":x: No vote is running")
return return