g1mp/plugins/asynchronious.py
2025-02-12 23:05:15 -08:00

195 lines
6.1 KiB
Python

# -*- 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 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<away>.*)"},
{
'match': (
r"(?i)^:\S+ 311 \S+ {nick} (?P<username>\S+) (?P<host>\S+) "
r". :(?P<realname>.*)"
)
},
{
'match': r"(?i)^:\S+ 312 \S+ {nick} (?P<server>\S+) :(?P<server_desc>.*)"
},
{'match': r"(?i)^:\S+ 317 \S+ {nick} (?P<idle>[0-9]+).*"},
{'match': r"(?i)^:\S+ 319 \S+ {nick} :(?P<channels>.*)", 'multi': True},
{
'match': (
r"(?i)^:\S+ 330 \S+ {nick} (?P<account>\S+) :(?P<account_desc>.*)"
)
},
{'match': r"(?i)^:\S+ 671 \S+ {nick} :(?P<connection>.*)"},
{
'match': r"(?i)^:\S+ (?P<retcode>(318|401)) \S+ (?P<nick>{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<user>\S+) (?P<host>\S+) "
r"(?P<server>\S+) (?P<nick>\S+) (?P<modes>\S+) "
r":(?P<hopcount>\S+) (?P<realname>.*)"
),
'multi': True,
},
{'match': r"(?i)^:\S+ (?P<retcode>(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
@dec.extend
async def names(self, target: str):
"""Retrieve the list of users in a channel."""
result = await self.who(target)
if result and 'users' in result:
return {'names': [user['nick'] for user in result['users']]}
return {'names': []}