feat: Add last_seen cache to dispatch.

This commit is contained in:
2026-01-16 02:35:11 +10:00
parent 93c17112e3
commit d2050ab2dc

View File

@@ -1,4 +1,5 @@
import asyncio
from collections import defaultdict
from string import punctuation
import datetime as dt
from datetime import datetime, timedelta
@@ -40,6 +41,7 @@ class FocusComponent(cmds.Component):
register_channel(self.channel.name, self.channel)
self._last_seen: dict[int, dict[int, datetime]] = defaultdict(dict)
self._last_deleted: dict[int, datetime] = {}
self.hyperfocus_lock = asyncio.Lock()
@@ -47,6 +49,44 @@ class FocusComponent(cmds.Component):
async def component_load(self):
await self.data.init()
async def dispatch_focuser_update(
self,
profileid: int,
communityid: int | None = None,
focuser: Hyperfocuser | None = None,
):
"""
Dispatch the given profile's hyperfocus status along the channel.
If the communityid is given, ensures that community receives the update.
"""
if focuser is None:
focuser = await self.get_hyperfocus(profileid)
elif focuser.profileid != profileid:
raise ValueError("Mis-matching profileid and focuser provided")
targets = set()
if communityid is not None:
targets.add(communityid)
now = utc_now()
if focuser is not None:
# If we are sending active, send to all last seens newer than a certain date, and delete any old ones
last_seen_cids = self._last_seen[profileid].items()
for cid, last_seen in last_seen_cids:
if (now - last_seen).total_seconds() < 4 * 3600:
targets.add(cid)
for cid in targets:
await self.channel.send_hyperfocus_patch(cid, focuser)
else:
# If we are deleting, send to *all* last seens, and then delete any old ones
# targets.update(self._last_seen[profileid].keys())
# for cid in targets:
await self.channel.send_hyperfocus_del(profileid)
# TODO: Cleanup old entries in last_seen
# TODO: Would prefer to use stream time and database member last seen instead
async def get_hyperfocus(self, profileid: int) -> Hyperfocuser | None:
"""
Get the Hyperfocuser if the user is hyperfocused.
@@ -96,6 +136,11 @@ class FocusComponent(cmds.Component):
async def handle_message(self, payload: twitchio.ChatMessage):
# Check if chatter is currently hyperfocused
profile = await self.bot.profiles.fetch_profile(payload.chatter, touch=True)
comm = await self.bot.profiles.fetch_community(payload.broadcaster, touch=True)
now = utc_now()
self._last_seen[profile.profileid][comm.communityid] = now
hyperfocused = await self.get_hyperfocus(profile.profileid)
# If they are, check the message content for deletion
@@ -103,7 +148,7 @@ class FocusComponent(cmds.Component):
# If we need to delete, run delete and send message
notify = ( #
not (last := self._last_deleted.get(profile.profileid))
or (utc_now() - last).total_seconds() > 30
or (now - last).total_seconds() > 30
)
try:
await self.focus_delete_message(payload)
@@ -115,7 +160,7 @@ class FocusComponent(cmds.Component):
deleted = False
if notify:
self._last_deleted[profile.profileid] = utc_now()
self._last_deleted[profile.profileid] = now
try:
if deleted:
await payload.broadcaster.send_message(
@@ -136,16 +181,12 @@ class FocusComponent(cmds.Component):
if hyperfocused:
# Send an update to the channel
# TODO: Nicer and more efficient caching of which channel has which users
# TODO: Possible race condition with the commands. Should use locks.
comm = await self.bot.profiles.fetch_community(
payload.broadcaster, touch=True
)
await self.channel.send_hyperfocus_patch(comm.communityid, hyperfocused)
# ------ Commands -----
@cmds.command(
name="hyperfocus", aliases=["hyperf", "hyper", "hypercrocus", "hyperofcus"]
name="hyperfocus",
aliases=["hfocus", "hyperf", "hyper", "hypercrocus", "hyperofcus"],
)
async def hyperfocus_cmd(self, ctx, *, duration: str | None = None):
now = utc_now()
@@ -188,7 +229,7 @@ class FocusComponent(cmds.Component):
started_in=comm.communityid,
)
await self.channel.send_hyperfocus_patch(comm.communityid, focuser)
await self.dispatch_focuser_update(pid, comm.communityid, focuser)
minutes = ceil(dur / 60)
await ctx.reply(
@@ -197,7 +238,7 @@ class FocusComponent(cmds.Component):
"Use !unfocus to come back if you need to, best of luck! ☘️🍀☘️ "
)
@cmds.command(name="unfocus")
@cmds.command(name="unfocus", aliases=["uncrocus"])
async def unfocus_cmd(self, ctx):
profile = await self.bot.profiles.fetch_profile(ctx.chatter, touch=True)
async with self.hyperfocus_lock:
@@ -239,10 +280,11 @@ class FocusComponent(cmds.Component):
)
@cmds.command(name="focuslist")
@cmds.is_moderator()
async def focuslist_cmd(self, ctx):
comm = await self.bot.profiles.fetch_community(ctx.broadcaster, touch=True)
link = (
f"https://croccyfocus.thewisewolf.dev/widget/?community={comm.communityid}"
)
await ctx.reply(f"Browser source link for your channel's hyperfocus: {link}")
await ctx.reply(
f"Browser source link for your channel's hyperfocus: {link} (For troubleshooting: your community id is {comm.communityid})"
)