-
Notifications
You must be signed in to change notification settings - Fork 48
Description
I have noticed names missing in bot.channels
after a connection error like this:
CRITICAL irc3.|foobot connection lost (136908348632496): TimeoutError('SSL shutdown timed out')
However, bot.channels
isn't empty. The bot itself recovers when the Internet connection comes back, and it keeps working. It looks as if irc3.plugins.userlist
stops updating bot.channels
. Maybe it is only missing the names that joined during the downtime, but it doesn't seem that way.
Unfortunately, I am can't give you the steps to reproduce the problem because it is intermittent. Simply cutting the Internet connection doesn't reliably cause it.
Note I don't have irc3.plugins.userlist
in includes
in my config file, only a plugin that requires it. Could that prevent userlist
from recovering?
As for solutions, irc3.plugins.userlist
could issue a NAMES
command to update its data when it is probably out of date. I have prototyped a NAMES
-based irc3 plugin. It must still have bugs, and it only updates manually when you call update
. I release this code under irc3's license.
names
plugin source
import asyncio
import time
from dataclasses import dataclass, replace
from typing import ClassVar
import irc3 # type: ignore
MAX_OUTDATED = 10
@dataclass(frozen=True)
class ChannelUsers:
flag: asyncio.Event
names: frozenset[str]
updated_at: float
@irc3.plugin
class NamesPlugin:
requires: ClassVar[list[str]] = [
"irc3.plugins.core",
]
def __init__(self, bot):
self.bot = bot
self.bot.names_plugin = self
self._curr_channel = ""
self._flag = None
self.channels: dict[str, ChannelUsers] = {}
async def update(self, channel: str):
if self._flag is not None:
await self._flag.wait()
curr_channel_users = self.channels.get(channel)
if (
curr_channel_users is not None
and time.time() - curr_channel_users.updated_at <= MAX_OUTDATED
):
return
new_channel_users = ChannelUsers(asyncio.Event(), frozenset(), 0)
self.channels[channel] = new_channel_users
self._curr_channel = channel
self._flag = new_channel_users.flag
self.bot.send_line(f"NAMES {channel}")
await self._flag.wait()
@irc3.event(irc3.rfc.RPL_NAMREPLY)
async def on_namreply(self, _mask=None, data=None, **_kw):
if self._flag is None:
return
if not isinstance(data, str):
self.bot.log.error("data is not 'str'")
return
channel = self._curr_channel
channel_users = self.channels[channel]
self.channels[channel] = replace(
channel_users,
names=channel_users.names | frozenset(data.split()),
)
@irc3.event(irc3.rfc.RPL_ENDOFNAMES)
async def on_endofnames(self, _mask=None, _data=None, **_kw):
if self._flag is None:
return
channel = self._curr_channel
channel_users = self.channels[channel]
channel_users.flag.set()
self.channels[channel] = replace(channel_users, updated_at=time.time())
self._curr_channel = ""
self._flag = None