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.
68 lines
2.6 KiB
Python
68 lines
2.6 KiB
Python
import asyncio
|
|
import logging
|
|
from typing import Optional
|
|
|
|
import discord
|
|
|
|
from bot.display import StatsDisplay
|
|
from bot.reactor import human_jitter
|
|
from bot.storage import Storage
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
async def undo_all_reactions(client: discord.Client, storage: Storage, display: Optional[StatsDisplay] = None) -> None:
|
|
"""Remove every reaction this script has previously logged, then clear those log entries."""
|
|
rows = await asyncio.to_thread(storage.get_all_reactions)
|
|
total = len(rows)
|
|
log.info("Undo: removing %d previously-added reaction(s)...", total)
|
|
|
|
removed = 0
|
|
for i, row in enumerate(rows, start=1):
|
|
channel_id = int(row["channel_id"])
|
|
message_id = int(row["message_id"])
|
|
emoji = row["emoji"]
|
|
should_clear_log = True
|
|
|
|
if display is not None:
|
|
display.set_active_target(row["target_id"])
|
|
display.set_status(f"Removing reaction from message {message_id} ({i}/{total})...")
|
|
|
|
try:
|
|
channel = client.get_channel(channel_id) or await client.fetch_channel(channel_id)
|
|
message = await channel.fetch_message(message_id)
|
|
await message.remove_reaction(emoji, client.user)
|
|
removed += 1
|
|
log.info("[%d/%d] Removed reaction from message %s", i, total, message_id)
|
|
except discord.NotFound:
|
|
log.info("[%d/%d] Message/reaction %s already gone, clearing log entry", i, total, message_id)
|
|
except discord.HTTPException:
|
|
log.exception("[%d/%d] Failed to remove reaction from message %s, will retry next run", i, total, message_id)
|
|
should_clear_log = False
|
|
|
|
if should_clear_log:
|
|
await asyncio.to_thread(storage.delete_reaction, row["id"])
|
|
if display is not None:
|
|
await display.refresh()
|
|
|
|
delay = human_jitter()
|
|
if display is not None:
|
|
display.start_countdown(delay, "next removal")
|
|
await asyncio.sleep(delay)
|
|
if display is not None:
|
|
display.clear_countdown()
|
|
|
|
if display is not None:
|
|
display.set_active_target(None)
|
|
display.set_status("Clearing channel resume points and found-message log...")
|
|
cleared = await asyncio.to_thread(storage.clear_scan_state)
|
|
await asyncio.to_thread(storage.reset_message_counts)
|
|
if display is not None:
|
|
display.set_status("Undo complete.")
|
|
await display.refresh()
|
|
log.info(
|
|
"Undo complete: removed %d/%d reaction(s), cleared %d channel resume point(s) — "
|
|
"the next normal run will do a full fresh backlog scan.",
|
|
removed, total, cleared,
|
|
)
|