Ejemplo n.º 1
0
def test_echo():
    manager = PluginManager()
    manager.scan_package('dywypi.plugins')
    manager.load('echo')
    assert 'echo' in manager.loaded_plugins

    loop = asyncio.get_event_loop()

    # TODO really this should work with a not-irc-specific raw message.  or
    # maybe it shouldn't take a raw message arg at all.
    # TODO this would be much easier if i could just pump messages into
    # somewhere and get them out of somewhere else.  even have an IRC proto on
    # both ends!  wow that sounds like a great idea too.
    client = DummyClient(loop)
    client.nick = 'dywypi'
    ev = Message(
        client,
        IRCMessage('PRIVMSG', 'dywypi', 'dywypi: echo foo', prefix='nobody!ident@host'),
    )

    manager.fire(ev)

    # Stop the loop as soon as everything has been processed
    # TODO is this the best way to do this, haha
    loop.call_soon(loop.stop)
    loop.run_forever()

    assert client.accumulated_messages == [('nobody', 'foo')]
Ejemplo n.º 2
0
    def __init__(self, plugin_name, *, client):
        self.plugin_name = plugin_name
        self.client = client

        self.manager = PluginManager()
        self.manager.scan_package('dywypi.plugins')
        self.manager.load(plugin_name)
        self.plugin = self.manager.loaded_plugins[plugin_name]
        self.data = self.manager.plugin_data[self.plugin]

        self.queued_events = deque()
Ejemplo n.º 3
0
class PluginPoker:
    def __init__(self, plugin_name, *, client):
        self.plugin_name = plugin_name
        self.client = client

        self.manager = PluginManager()
        self.manager.scan_package('dywypi.plugins')
        self.manager.load(plugin_name)
        self.plugin = self.manager.loaded_plugins[plugin_name]
        self.data = self.manager.plugin_data[self.plugin]

        self.queued_events = deque()

    def queue(self, event_type, *args):
        self.queued_events.append(event_type(*args, client=self.client))

    @asyncio.coroutine
    def fire_all(self):
        while self.queued_events:
            event = self.queued_events.popleft()
            yield from asyncio.wait(self.manager.fire(event))
Ejemplo n.º 4
0
def test_echo(loop):
    manager = PluginManager()
    manager.scan_package('dywypi.plugins')
    manager.load('echo')
    assert 'echo' in manager.loaded_plugins

    # TODO this would be much easier if i could just pump messages into
    # somewhere and get them out of somewhere else.  even have an IRC proto on
    # both ends!  wow that sounds like a great idea too.
    client = DummyClient(loop)
    client.nick = 'dywypi'
    ev = Message(
        Peer.from_prefix('nobody!ident@host'),
        Peer('dywypi', None, None),
        'dywypi: echo foo',
        client=client,
    )

    loop.run_until_complete(asyncio.gather(*manager.fire(ev), loop=loop))

    assert client.accumulated_messages == [('nobody', 'foo')]
Ejemplo n.º 5
0
 def __init__(self):
     self.networks = {}
     self.plugin_manager = PluginManager()
Ejemplo n.º 6
0
class Brain:
    """Central nervous system of the bot.  Handles initial configuration,
    plugin discovery, initial connections, and all that fun stuff.
    """

    def __init__(self):
        self.networks = {}
        self.plugin_manager = PluginManager()

    def configure_from_argv(self, argv=None):
        # Scan for known plugins first
        self.plugin_manager.scan_package('dywypi.plugins')
        try:
            self.plugin_manager.scan_package('dywypi_plugins')
        except ImportError:
            # No local plugins; no sweat
            pass

        parser = self.build_parser()
        ns = parser.parse_args(argv)

        for uristr in ns.adhoc_connections:
            self.add_adhoc_connection(uristr)

        if not ns.plugin:
            pass
        elif 'ALL' in ns.plugin:
            self.plugin_manager.loadall()
        else:
            # Always load the core plugin
            self.plugin_manager.load('core')
            for plugin_name in ns.plugin:
                # TODO this should probably take a fqn to a /plugin/ not to a
                # module?
                if '.' in plugin_name:
                    self.plugin_manager.loadmodule(plugin_name)
                else:
                    self.plugin_manager.load(plugin_name)

    def run(self, loop):
        asyncio.async(self._run(loop), loop=loop)
        try:
            loop.run_forever()
        except (KeyboardInterrupt, SystemExit):
            self.stop(loop)

    @asyncio.coroutine
    def _run(self, loop):
        # TODO less hard-coded here would be nice
        clients = []
        for network in self.networks.values():
            clients.append(network.client_class(loop, network))

        # TODO hmm this feels slightly janky; should this all be done earlier
        # perhaps
        self.current_clients = clients

        # TODO gracefully handle failed connections, and only bail entirely if
        # they all fail?
        yield from asyncio.gather(*[client.connect() for client in clients])

        # This is it, this is the event loop right here.
        # It's basically a select() loop across all our clients, except there's
        # nothing quite like select() at the moment, so we have to fake it by
        # keeping a fresh list of read_event calls per client.
        coros = {client: asyncio.Task(client.read_event()) for client in clients}
        while True:
            done, pending = yield from asyncio.wait(
                coros.values(),
                return_when=FIRST_COMPLETED)

            # Replace any coros that finished with fresh ones for the next run
            # of the loop
            for client, coro in coros.items():
                if coro in done:
                    coros[client] = asyncio.Task(client.read_event())

            # Evaluate all the tasks that completed (probably just one)
            for d in done:
                event = yield from d
                if event:
                    self.plugin_manager.fire(event)

    def stop(self, loop):
        """Disconnect all clients."""
        # Someone pressed Ctrl-C or called sys.exit.  Try to shut down
        # gracefully, but bail after 5s, or if we get KeyboardInterrupt a
        # second time.
        print("Waiting for connections to close...  (Ctrl-C to stop now)")
        # TODO do i need to stop my own event loop somehow?
        # TODO what happens if i try to disconnect while i'm still connecting?
        # TODO should this also try to stop any scheduled events, or just let
        # loop.close() take care of that?
        stop_task = asyncio.Task(self._stop(loop))
        timer = loop.call_later(5, stop_task.cancel)
        try:
            loop.run_until_complete(stop_task)
        except KeyboardInterrupt:
            pass
        timer.cancel()

    @asyncio.coroutine
    def _stop(self, loop):
        yield from asyncio.gather(*[client.disconnect() for client in self.current_clients])

    def add_network(self, network):
        # TODO check for dupes!
        self.networks[network.name] = network

    def build_parser(self):
        p = argparse.ArgumentParser()
        p.add_argument('adhoc_connections', nargs='+', action='store',
            help='URIs defining where to connect initially.')
        p.add_argument('-p', '--plugin', action='append',
            help='Load a plugin by name or module.  '
                'Specify ALL to auto-load all detected plugins.')

        return p

    def add_adhoc_connection(self, uristr):
        uri = urlparse(uristr)

        # TODO dying for some registration here.
        if uri.scheme in ('irc', 'ircs'):
            client_class = IRCClient
        elif uri.scheme in ('shell',):
            # TODO import down here in case no urwid
            from dywypi.dialect.shell import ShellClient
            client_class = ShellClient
        else:
            raise ValueError(
                "Don't know how to handle protocol {}: {}"
                .format(uri.scheme, uri)
            )

        # Try to guess a network name based on the host
        name = uristr
        if uri.hostname:
            # TODO handle IPs?  and have some other kind of ultimate fallback?
            parts = uri.hostname.split('.')
            # TODO this doesn't work for second-level like .co.jp
            if len(parts) > 1:
                name = parts[-2]
            else:
                name = parts[0]

        # TODO hmm should this stuff be delegated to a dialect?  some of it may
        # not make sense for some dialects
        network = Network(name)
        if uri.username:
            network.add_preferred_nick(uri.username)

        # TODO lol this tls hack is so bad.
        network.add_server(
            uri.hostname or 'localhost',
            uri.port,
            tls=uri.scheme.endswith('s'),
            password=uri.password,
        )

        if uri.path:
            channel_name = uri.path.lstrip('/')
            network.add_autojoin(channel_name)

        # TODO uhh yeah i don't know about any of this.
        network.client_class = client_class
        self.add_network(network)
Ejemplo n.º 7
0
def test_scan_package():
    manager = PluginManager()
    #assert not manager.known_plugins
    manager.scan_package('dywypi.plugins')
    assert 'echo' in manager.known_plugins
    manager.scan_package('dywypi.plugins')