Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better reaction-based quick actions #91

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 47 additions & 12 deletions herring/puzzles/discordbot.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@
# making this less than 25 leaves a little elbow room for cleanup_channels if necessary
PUZZLES_PER_CATEGORY = 20

# special emojis used for on_raw_reaction_add
SIGNUP_EMOJI = "\N{RAISED HAND}"
LEAVE_EMOJI = "\N{LEAF FLUTTERING IN WIND}"
TRIUMPH_EMOJI = "\N{PARTY POPPER}"

# this is the most users we'll try to mention during an hb!who; more than this and you just get the number
MAX_USER_LIST = 10
Expand Down Expand Up @@ -223,12 +226,38 @@ async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent):
# ignore myself
if payload.user_id == self.bot.user.id:
return
if payload.channel_id == self.announce_channel.id and payload.emoji.name == SIGNUP_EMOJI:
channel = self.bot.get_channel(payload.channel_id) or await self.bot.fetch_channel(payload.channel_id)
message: discord.Message = await channel.fetch_message(payload.message_id)
if payload.emoji.name == SIGNUP_EMOJI:
# add someone to the puzzle
message: discord.Message = await self.announce_channel.fetch_message(payload.message_id)
await message.remove_reaction(SIGNUP_EMOJI, payload.member)
target_channel = message.channel_mentions[0]
await self.add_user_to_puzzle(payload.member, target_channel.name)
# we're ok with this working anywhere for any reason if anyone ever mentions a puzzle channel
if len(message.raw_channel_mentions) > 0:
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does raw_channel_mentions in fact work for what we need here, for both regular and DM messages? (I saw in ryan's PR that regular channel_mentions did not.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it does. All channel_mentions does is map message.guild.get_channel over raw_channel_mentions, but if message is a DM, that doesn't work because it doesn't have a guild. So I'm doing it myself.

target_channel_id = message.raw_channel_mentions[0]
target_channel = self.guild.get_channel(target_channel_id)
if not target_channel:
return
try:
puzzle = await _get_puzzle_by_slug(target_channel.name)
except Puzzle.DoesNotExist:
return

await self.add_user_to_puzzle(payload.member, puzzle.name)
if channel.type != discord.ChannelType.private:
await message.remove_reaction(SIGNUP_EMOJI, payload.member)
elif payload.emoji.name == LEAVE_EMOJI:
# this should only work on the appropriate message in the puzzle channel or in puzzle-announcements
if message.author.id == self.bot.user.id:
if channel.type != discord.ChannelType.private:
await message.remove_reaction(LEAVE_EMOJI, payload.member)
if payload.channel_id == self.announce_channel.id:
target_channel = message.channel_mentions[0]
elif TRIUMPH_EMOJI in message.content:
# hopefully this is the "puzzle solved!" message
target_channel = channel
else:
target_channel = None
if target_channel:
await self.remove_user_from_puzzle(payload.member, target_channel.name)

@commands.Cog.listener()
async def on_message(self, message: discord.Message):
Expand Down Expand Up @@ -863,6 +892,7 @@ def get_channel_pair(self, puzzle_name):
return text_channel, voice_channel

async def remove_user_from_puzzle(self, member: discord.Member, puzzle_name: str):
if member.bot: return
text_channel, voice_channel = self.get_channel_pair(puzzle_name)

await text_channel.set_permissions(member, overwrite=None)
Expand Down Expand Up @@ -1069,8 +1099,8 @@ def __init__(self, *args, **kwargs):
# we don't want this bot to respond to messages; just don't ask for the intent
intents.message_content = False
super(HerringAnnouncerBot, self).__init__(*args, intents=intents, **kwargs)
self.guild = None
self.announce_channel = None
self.guild : Optional[discord.Guild] = None
self.announce_channel : Optional[discord.TextChannel] = None
self._really_ready = asyncio.Event()

async def on_ready(self):
Expand Down Expand Up @@ -1143,7 +1173,7 @@ async def make_category():
round, category = await ensure_category_ready()

text_channel, voice_channel = await _make_puzzle_channels_inner(category, puzzle)
announcement = await self.announce_channel.send(f"New puzzle {puzzle.name} opened! {SIGNUP_EMOJI} this message to join, then click here to jump to the channel: {text_channel.mention}.")
announcement = await self.announce_channel.send(f"New puzzle {puzzle.name} opened in round {round.name}! {SIGNUP_EMOJI} this message to join, then click here to jump to the channel: {text_channel.mention}.")
await announcement.add_reaction(SIGNUP_EMOJI)

async def post_message(self, channel_name, message, **kwargs):
Expand All @@ -1154,16 +1184,20 @@ async def post_message(self, channel_name, message, **kwargs):
return
await channel.send(message, **kwargs)

async def post_local_and_global(self, puzzle_name, local_message, global_message:str):
async def post_local_and_global(self, puzzle_name, local_content, global_content:str, local_reaction=None, global_reaction=None):
await self._really_ready.wait()
channel: discord.TextChannel = get(self.guild.text_channels, name=puzzle_name)
if channel is None:
logging.error(f"Couldn't get Discord channel {puzzle_name} in post_local_and_global!")
return

await channel.send(local_message)
global_message = global_message.replace(f"#{puzzle_name}", channel.mention)
await self.announce_channel.send(global_message)
local_message = await channel.send(local_content)
global_content = global_content.replace(f"#{puzzle_name}", channel.mention)
global_message = await self.announce_channel.send(global_content)
if local_reaction:
await local_message.add_reaction(local_reaction)
if global_reaction:
await global_message.add_reaction(global_reaction)

async def add_user_to_puzzle(self, user_profile: UserProfile, puzzle_name):
await self._really_ready.wait()
Expand Down Expand Up @@ -1289,6 +1323,7 @@ def _get_puzzle_by_slug(slug):
return Puzzle.objects.get(slug=slug, hunt_id=settings.HERRING_HUNT_ID)

async def _add_user_to_channels(member, text_channel:discord.TextChannel, voice_channel):
if member.bot: return False
current_perms = text_channel.overwrites
if member not in current_perms:
await text_channel.set_permissions(member, read_messages=True)
Expand Down
27 changes: 9 additions & 18 deletions herring/puzzles/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import json
import kombu.exceptions
from lazy_object_proxy import Proxy as lazy_object
from puzzles.discordbot import run_listener_bot, DISCORD_ANNOUNCER, do_in_discord
from puzzles.discordbot import run_listener_bot, DISCORD_ANNOUNCER, do_in_discord, LEAVE_EMOJI, TRIUMPH_EMOJI
from puzzles.models import Puzzle, Round, UserProfile
from puzzles.spreadsheets import check_spreadsheet_service, iterate_changes, make_sheet
from redis import Redis
Expand Down Expand Up @@ -72,10 +72,10 @@ def apply_async(*args, **kwargs):
return t


def post_local_and_global(local_channel, local_message, global_message):
logging.warning("tasks: post_local_and_global(%s, %s, %s)", local_channel, local_message, global_message)
def post_local_and_global(local_channel, local_message, global_message, local_reaction=None, global_reaction=None):
logging.warning("tasks: post_local_and_global(%s, %s, %s, %s)", local_channel, local_message, global_message, local_reaction, global_reaction)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You missed a %s there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blast, I originally only included one reaction and added the 2nd one later when I realized that I was sometimes going to want it in global but not local. Will fix.

if settings.HERRING_ACTIVATE_DISCORD:
do_in_discord(DISCORD_ANNOUNCER.post_local_and_global(local_channel, local_message, global_message))
do_in_discord(DISCORD_ANNOUNCER.post_local_and_global(local_channel, local_message, global_message, local_reaction, global_reaction))

@optional_task
@shared_task(rate_limit=0.5)
Expand All @@ -84,13 +84,9 @@ def post_answer(slug, answer):

puzzle = Puzzle.objects.get(slug=slug)
answer = answer.upper()
local_message = "\N{PARTY POPPER} Confirmed answer: {}".format(answer)
global_message = '\N{PARTY POPPER} Puzzle "{name}" (#{slug}) was solved! The answer is: {answer}'.format(
answer=answer,
slug=slug,
name=puzzle.name
)
post_local_and_global(slug, local_message, global_message)
local_message = f"{TRIUMPH_EMOJI} Confirmed answer: {answer}\nReact with {LEAVE_EMOJI} to leave the puzzle!"
global_message = f'{TRIUMPH_EMOJI} Puzzle "{puzzle.name}" (#{slug}) from round {puzzle.parent.name} was solved! The answer is: {answer}\nReact with {LEAVE_EMOJI} to leave the puzzle!'
post_local_and_global(slug, local_message, global_message, LEAVE_EMOJI, LEAVE_EMOJI)


@optional_task
Expand All @@ -102,13 +98,8 @@ def post_update(slug, updated_field, value):
puzzle = Puzzle.objects.get(slug=slug)
except Puzzle.DoesNotExist:
return
local_message = '{} set to: {}'.format(updated_field, value)
global_message = '"{name}" (#{slug}) now has these {field}: {value}'.format(
field=updated_field,
value=value,
slug=slug,
name=puzzle.name
)
local_message = f'{updated_field} set to: {value}'
global_message = f'"{puzzle.name}" (#{slug}) from round {puzzle.parent.name} now has these {updated_field}: {value}'
post_local_and_global(slug, local_message, global_message)


Expand Down