import asyncio import datetime import logging from typing import Awaitable, Callable import discord from bot.discovery import Target from bot.storage import Storage log = logging.getLogger(__name__) ReactFn = Callable[[discord.Message, Target], Awaitable[bool]] async def scan_channel_backlog( storage: Storage, channel: discord.abc.Messageable, target: Target, friend_id: int, cutoff: datetime.datetime, react_fn: ReactFn, ) -> None: """Scan a channel's history once, resumably. First run: walk forward from `cutoff` (now - days_back) to the present. Subsequent runs: only diff forward from the newest message already seen, since the days_back window only ever moves forward in time. """ channel_id = getattr(channel, "id", None) if channel_id is None: return state = await asyncio.to_thread(storage.get_scan_state, channel_id) newest_id_seen = int(state["newest_id_seen"]) if state and state.get("newest_id_seen") else None after = discord.Object(id=newest_id_seen) if newest_id_seen else cutoff checkpoint_every = 25 seen_since_checkpoint = 0 try: async for message in channel.history(after=after, oldest_first=True, limit=None): if newest_id_seen is None or message.id > newest_id_seen: newest_id_seen = message.id await react_fn(message, target) seen_since_checkpoint += 1 if seen_since_checkpoint >= checkpoint_every: seen_since_checkpoint = 0 await asyncio.to_thread( storage.set_scan_state, channel_id, target.target_id, newest_id_seen, False ) await asyncio.to_thread( storage.set_scan_state, channel_id, target.target_id, newest_id_seen, True ) except discord.Forbidden: log.warning("No access to channel %s (%s), skipping", channel_id, getattr(channel, "name", "")) except discord.HTTPException: log.exception("HTTP error scanning channel %s", channel_id)