progval / Limnoria

A robust, full-featured, and user/programmer-friendly Python IRC bot, with many existing plugins.
https://docs.limnoria.net/
Other
623 stars 173 forks source link

labeled-response hooks #1483

Open progval opened 3 years ago

progval commented 3 years ago

To fully benefit from labeled-response, we should provide an easy way for plugins to send a message and run code on a reply without manually managing state.

Current pseudo-code:

def __init__(self, irc):
    _in_flight = utils.structures.ExpiringDict

def do_the_thing(self, msg, *, state):
    ...

def mycommand(self, irc, msg, args):
    # ...
    label = ircutils.makeLabel()
    state = ...
    self._in_flight[label] = state
    msg = ircmsgs.IrcMsg(..., server_tags={"label": label})
    irc.queueMsg(msg)

def doCommand(self, irc, msg):
    label = msg.server_tags.get("label")
    if not label:
        return 
    state = self._register.get(label)
    if not state:
        return
   do_the_thing(msg, state=state)

That's a lot of boilerplate, so I'd like to provide a simple API like this to plugins:

def do_the_thing(self, msg, *, state):
    ...

def mycommand(self, irc, msg, args):
    # ...
    state = ...
    msg = ircmsgs.IrcMsg(...)
    self.addResponseCallback(msg, do_the_thing, state=state)  # This sets a label on the message
    irc.queueMsg(msg)

Or maybe even introduce async command functions:

async def mycommand(self, irc, msg, args):
    # ...
    msg = ircmsgs.IrcMsg(...)
    reply = await irc.awaitMsgReply(msg)
    # do_the_thing

The later is the most developer-friendly because it reuses the frame instead of explicitly passing state, but it is going to be a real strain on the event loop (unless we implement it in callbacks.Callback.__call__? or maybe a new plugin class in callbacks? or even make it an external library that defines a abstract callback class that plugins can inherit to use async?)

progval commented 3 years ago

Another random idea, but unrelated to labeled-response, but related to the latter: it could be useful to have async procedures like this to wait for an action; eg. when implementing turn-based games waiting for players to run a command.