# -*- coding: utf-8 -*- """ ====================================================== :mod:`irc3.plugins.asynchronous` Asynchronous Events ====================================================== Provides asynchronous handling for various IRC events including WHOIS, WHO queries, and channel management through non-blocking operations. Features: - **WHOIS Support**: Retrieve detailed user information from the server. - **WHO Queries**: Fetch channel users with their respective flags. - **CTCP Handling**: Manage custom Client-to-Client Protocol requests. - **Channel Topic Management**: Get or modify channel topics efficiently. - **Ban List Handling**: Query active bans on a channel. Usage ===== Subclass `~irc3.asynchronous.AsyncEvents` to create custom asynchronous event handlers. Example: class MyPlugin: def __init__(self, bot): self.bot = bot self.whois = Whois(bot) async def do_whois(self): whois = await self.whois(nick='gawel') if int(whois['idle']) / 60 > 10: self.bot.privmsg('gawel', 'Wake up dude') .. warning:: Always verify `result['timeout']` to ensure a response was received before the timeout. """ from irc3.asynchronous import AsyncEvents from irc3 import utils from irc3 import dec class Whois(AsyncEvents): """Asynchronously handle WHOIS responses to gather user details. Attributes: timeout (int): Default timeout for WHOIS response in seconds. send_line (str): IRC command template for WHOIS requests. """ timeout = 20 send_line = 'WHOIS {nick} {nick}' events = ( {'match': r"(?i)^:\S+ 301 \S+ {nick} :(?P.*)"}, { 'match': ( r"(?i)^:\S+ 311 \S+ {nick} (?P\S+) (?P\S+) " r". :(?P.*)" ) }, { 'match': r"(?i)^:\S+ 312 \S+ {nick} (?P\S+) :(?P.*)" }, {'match': r"(?i)^:\S+ 317 \S+ {nick} (?P[0-9]+).*"}, {'match': r"(?i)^:\S+ 319 \S+ {nick} :(?P.*)", 'multi': True}, { 'match': ( r"(?i)^:\S+ 330 \S+ {nick} (?P\S+) :(?P.*)" ) }, {'match': r"(?i)^:\S+ 671 \S+ {nick} :(?P.*)"}, { 'match': r"(?i)^:\S+ (?P(318|401)) \S+ (?P{nick}) :.*", 'final': True, }, ) def process_results(self, results=None, **value): """Aggregate and structure WHOIS results into a consolidated dictionary. Args: results (list): Collected event responses. **value: Accumulated data from event processing. Returns: dict: Structured user information with success status. """ channels = [] for res in results: channels.extend(res.pop('channels', '').split()) value.update(res) value['channels'] = channels value['success'] = value.get('retcode') == '318' return value class WhoChannel(AsyncEvents): """Handle WHO responses for channel user listings. Attributes: send_line (str): IRC command template for WHO requests. """ send_line = 'WHO {channel}' events = ( { 'match': ( r"(?i)^:\S+ 352 \S+ {channel} (?P\S+) (?P\S+) " r"(?P\S+) (?P\S+) (?P\S+) " r":(?P\S+) (?P.*)" ), 'multi': True, }, {'match': r"(?i)^:\S+ (?P(315|401)) \S+ {channel} :.*", 'final': True}, ) def process_results(self, results=None, **value): """Compile WHO channel results into a list of users. Args: results (list): Raw event response data. **value: Extracted key-value pairs from responses. Returns: dict: Processed result with user list and success status. """ users = [] for res in results: if 'retcode' in res: value.update(res) else: res['mask'] = utils.IrcString(f"{res['nick']}!{res['user']}@{res['host']}") users.append(res) value['users'] = users value['success'] = value.get('retcode') == '315' return value @dec.plugin class Async: """Expose asynchronous IRC command interfaces for plugin usage.""" def __init__(self, context): """Initialize with the bot context and register async commands.""" self.context = context self.context.async_cmds = self self.async_whois = Whois(context) self.async_who_channel = WhoChannel(context) def send_message(self, target: str, message: str): """Send a message to a target (channel or user). Args: target (str): Recipient channel or nickname. message (str): Message content to send. """ self.context.privmsg(target, message) @dec.extend def whois(self, nick: str, timeout: int = 20): """Initiate a WHOIS query for a nickname. Args: nick (str): Nickname to query. timeout (int): Response timeout in seconds. Returns: Awaitable[dict]: WHOIS result data. """ return self.async_whois(nick=nick.lower(), timeout=timeout) @dec.extend def who(self, target: str, timeout: int = 20): """Perform a WHO query on a channel or user. Args: target (str): Channel or nickname to query. timeout (int): Response timeout in seconds. Returns: Awaitable[dict] | None: WHO results for channels, else None. """ target = target.lower() if target.startswith('#'): return self.async_who_channel(channel=target, timeout=timeout) return None