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, )