Files
dc-nogi/bot/reactor.py
Minz ef90ee6a0b Add spinner/countdown-bar to stats panel; backfill on widened days_back
Stats panel: a persistent spinner marks whichever target is currently
being scanned/reacted to, and jittered waits show a live-ticking
countdown bar instead of a static "waiting Xs" line. Both the status
line and countdown row are always rendered (blank when idle) so their
appearing/disappearing no longer shifts the table's height.

Backlog: channel_scan_state now tracks oldest_covered_at alongside
newest_id_seen. If days_back is widened after a channel already has a
resume checkpoint, the forward-only diff would never look back far
enough to notice — now the newly-exposed gap gets backfilled first.
2026-08-01 19:24:20 +02:00

82 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.start_countdown(delay, f"reacting in {chan_label}")
await asyncio.sleep(delay)
if display is not None:
display.clear_countdown()
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