Shows what's happening right now underneath the table (connecting, resolving the friend, discovering targets, fetching channel history, waiting out a jittered delay before reacting, sending a reaction, removing a reaction during --undo, etc). Status updates are cheap and DB-free, using the last fetched stats for re-rendering.
81 lines
2.2 KiB
Python
81 lines
2.2 KiB
Python
import asyncio
|
|
import logging
|
|
import random
|
|
from typing import Optional
|
|
|
|
import discord
|
|
|
|
from bot.discovery import Target
|
|
from bot.display import StatsDisplay
|
|
from bot.storage import Storage
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def human_jitter() -> float:
|
|
"""Gaussian delay centered a few seconds in, clamped to a natural-looking range."""
|
|
delay = random.gauss(4.0, 1.5)
|
|
return max(1.5, min(delay, 8.0))
|
|
|
|
|
|
async def react_to_message(
|
|
storage: Storage,
|
|
message: discord.Message,
|
|
target: Target,
|
|
friend_id: int,
|
|
emoji: str,
|
|
source: str,
|
|
display: Optional[StatsDisplay] = None,
|
|
count_found: bool = True,
|
|
) -> bool:
|
|
if message.author.id != friend_id:
|
|
return False
|
|
|
|
if count_found:
|
|
await asyncio.to_thread(
|
|
storage.record_message_found, message.id, message.channel.id, target.target_id, friend_id
|
|
)
|
|
if display is not None:
|
|
await display.refresh()
|
|
|
|
already_reacted = any(
|
|
reaction.me and str(reaction.emoji) == emoji for reaction in message.reactions
|
|
)
|
|
if not already_reacted:
|
|
already_reacted = await asyncio.to_thread(storage.has_reacted, message.id, emoji)
|
|
if already_reacted:
|
|
return False
|
|
|
|
chan_label = getattr(message.channel, "name", None) or target.name
|
|
delay = human_jitter()
|
|
if display is not None:
|
|
display.set_status(f"Waiting {delay:.1f}s before reacting in {chan_label}...")
|
|
await asyncio.sleep(delay)
|
|
|
|
if display is not None:
|
|
display.set_status(f"Sending reaction in {chan_label}...")
|
|
try:
|
|
await message.add_reaction(emoji)
|
|
except discord.HTTPException:
|
|
log.exception("Failed to react to message %s in channel %s", message.id, message.channel.id)
|
|
return False
|
|
|
|
inserted = await asyncio.to_thread(
|
|
storage.record_reaction,
|
|
message.id,
|
|
message.channel.id,
|
|
target.target_id,
|
|
friend_id,
|
|
emoji,
|
|
source,
|
|
message.created_at,
|
|
)
|
|
if inserted:
|
|
log.info(
|
|
"Reacted to message in %s [%s] (%s): %s",
|
|
target.name, target.target_type, source, message.jump_url,
|
|
)
|
|
if display is not None:
|
|
await display.refresh()
|
|
return inserted
|