Example #1
0
class _Schema:

    config = immp.Schema({"token": str,
                          immp.Optional("bot", True): bool,
                          immp.Optional("webhooks", dict): {str: str},
                          immp.Optional("playing"): immp.Nullable(str)})

    webhook = immp.Schema(immp.Any({"code": int, "message": str}, {"id": str}))
Example #2
0
 def _api(nested={}):
     return immp.Schema(
         immp.Any(
             {
                 "ok": True,
                 immp.Optional("response_metadata", dict): {
                     immp.Optional("next_cursor", ""): str
                 },
                 **nested
             }, {
                 "ok": False,
                 "error": str
             }))
Example #3
0
class AsyncShellHook(immp.ResourceHook):
    """
    Hook to launch an asynchonous console alongside a :class:`.Host` instance.

    Attributes:
        buffer (collections.deque):
            Queue of recent messages, the length defined by the ``buffer`` config entry.
        last ((.SentMessage, .Message) tuple):
            Most recent message received from a connected plug.
    """

    schema = immp.Schema({
        "bind": immp.Any(str, int),
        immp.Optional("buffer"): immp.Nullable(int)
    })

    def __init__(self, name, config, host):
        super().__init__(name, config, host)
        if not aioconsole:
            raise immp.PlugError("'aioconsole' module not installed")
        self.buffer = None
        self._server = None

    @property
    def last(self):
        return self.buffer[-1] if self.buffer else None

    async def start(self):
        await super().start()
        if self.config["buffer"] is not None:
            self.buffer = deque(maxlen=self.config["buffer"] or None)
        if isinstance(self.config["bind"], str):
            log.debug("Launching console on socket %s", self.config["bind"])
            bind = {"path": self.config["bind"]}
        else:
            log.debug("Launching console on port %d", self.config["bind"])
            bind = {"port": self.config["bind"]}
        self._server = await aioconsole.start_interactive_server(
            factory=self._factory, **bind)

    async def stop(self):
        await super().stop()
        self.buffer = None
        if self._server:
            log.debug("Stopping console server")
            self._server.close()
            self._server = None

    @staticmethod
    def _pprint(console, obj):
        console.print(pformat(obj))

    def _factory(self, streams=None):
        context = {"host": self.host, "shell": self, "immp": immp}
        console = aioconsole.AsynchronousConsole(locals=context,
                                                 streams=streams)
        context["pprint"] = partial(self._pprint, console)
        return console

    async def on_receive(self, sent, source, primary):
        await super().on_receive(sent, source, primary)
        if self.buffer is not None:
            self.buffer.append((sent, source))
Example #4
0
class _Schema:

    image_sizes = ("original", "512", "192", "72", "48", "32", "24")

    _images = {
        immp.Optional("image_{}".format(size)): immp.Nullable(str)
        for size in image_sizes
    }

    config = immp.Schema({
        "token": str,
        immp.Optional("fallback-name", "IMMP"): str,
        immp.Optional("fallback-image"): immp.Nullable(str),
        immp.Optional("thread-broadcast", False): bool
    })

    team = immp.Schema({
        "id": str,
        "name": str,
        "domain": str,
        "prefs": {
            immp.Optional("display_real_names", False): bool,
            str: immp.Any()
        }
    })

    user = immp.Schema({
        "id": str,
        "name": str,
        "profile": {
            immp.Optional("real_name"): immp.Nullable(str),
            immp.Optional("bot_id"): immp.Nullable(str),
            **_images
        }
    })

    bot = immp.Schema({
        "id": str,
        "app_id": str,
        "name": str,
        "icons": _images
    })

    channel = immp.Schema({"id": str, "name": str})

    direct = immp.Schema({"id": str, "user": str})

    _shares = {str: [{"ts": str}]}

    file = immp.Schema(
        immp.Any(
            {
                "id": str,
                "name": immp.Nullable(str),
                "pretty_type": str,
                "url_private": str,
                immp.Optional("mode"): immp.Nullable(str),
                immp.Optional("shares", dict): {
                    immp.Optional("public", dict): _shares,
                    immp.Optional("private", dict): _shares
                }
            }, {
                "id": str,
                "mode": "tombstone"
            }))

    attachment = immp.Schema({
        immp.Optional("fallback"): immp.Nullable(str),
        immp.Optional("title"): immp.Nullable(str),
        immp.Optional("image_url"): immp.Nullable(str),
        immp.Optional("is_msg_unfurl", False): bool
    })

    msg_unfurl = immp.Schema({"channel_id": str, "ts": str}, attachment)

    _base_msg = immp.Schema({
        "ts": str,
        "type": "message",
        immp.Optional("hidden", False): bool,
        immp.Optional("channel"): immp.Nullable(str),
        immp.Optional("edited", dict): {
            immp.Optional("user"): immp.Nullable(str)
        },
        immp.Optional("thread_ts"): immp.Nullable(str),
        immp.Optional("replies", list): [{
            "ts": str
        }],
        immp.Optional("files", list): [file],
        immp.Optional("attachments", list): [attachment],
        immp.Optional("is_ephemeral", False): bool
    })

    _plain_msg = immp.Schema(
        {
            immp.Optional("user"): immp.Nullable(str),
            immp.Optional("bot_id"): immp.Nullable(str),
            immp.Optional("username"): immp.Nullable(str),
            immp.Optional("icons", dict): dict,
            "text": str
        }, _base_msg)

    message = immp.Schema(
        immp.Any(
            immp.Schema({"subtype": "file_comment"}, _base_msg),
            immp.Schema({"subtype": "message_changed"}, _base_msg),
            immp.Schema({
                "subtype": "message_deleted",
                "deleted_ts": str
            }, _base_msg),
            immp.Schema(
                {
                    "subtype": immp.Any("channel_name", "group_name"),
                    "name": str
                }, _plain_msg),
            immp.Schema({immp.Optional("subtype"): immp.Nullable(str)},
                        _plain_msg)))

    # Circular references to embedded messages.
    message.raw.choices[1].raw.update({
        "message": message,
        "previous_message": message
    })

    event = immp.Schema(
        immp.Any(
            message, {
                "type": "team_pref_change",
                "name": "str",
                "value": immp.Any()
            }, {
                "type": immp.Any("team_join", "user_change"),
                "user": user
            }, {
                "type":
                immp.Any("channel_created", "channel_joined", "channel_rename",
                         "group_created", "group_joined", "group_rename"),
                "channel": {
                    "id": str,
                    "name": str
                }
            }, {
                "type": "im_created",
                "channel": {
                    "id": str
                }
            }, {
                "type": immp.Any("member_joined_channel",
                                 "member_left_channel"),
                "user": str,
                "channel": str
            }, {
                "type": "message",
                immp.Optional("subtype"): immp.Nullable(str)
            }, {"type": str}))

    def _api(nested={}):
        return immp.Schema(
            immp.Any(
                {
                    "ok": True,
                    immp.Optional("response_metadata", dict): {
                        immp.Optional("next_cursor", ""): str
                    },
                    **nested
                }, {
                    "ok": False,
                    "error": str
                }))

    rtm = _api({
        "url": str,
        "self": {
            "id": str
        },
        "team": {
            "id": str,
            "name": str,
            "domain": str
        },
        "users": [user],
        "channels": [channel],
        "groups": [channel],
        "ims": [direct],
        "bots": [{
            "id": str,
            "deleted": bool
        }]
    })

    im_open = _api({"channel": direct})

    members = _api({"members": [str]})

    post = _api({"ts": str})

    upload = _api({"file": file})

    history = _api({"messages": [message]})

    api = _api()
Example #5
0
class WebHook(immp.ResourceHook):
    """
    Hook that provides a generic webserver, which other hooks can bind routes to.

    Attributes:
        app (aiohttp.web.Application):
            Web application instance, used to add new routes.
    """

    schema = immp.Schema(
        immp.Any({
            immp.Optional("host"): immp.Nullable(str),
            "port": int
        }, {"path": str}))

    def __init__(self, name, config, host):
        super().__init__(name, config, host)
        self.app = web.Application()
        if aiohttp_jinja2:
            # Empty mapping by default, other hooks can add to this via add_loader().
            self._loader = PrefixLoader({})
            self._jinja = aiohttp_jinja2.setup(self.app, loader=self._loader)
            self._jinja.filters["json"] = json.dumps
            self._jinja.globals["immp"] = immp
            self._jinja.globals["host"] = self.host
        self._runner = web.AppRunner(self.app)
        self._site = None
        self._contexts = {}

    def context(self, prefix, module, path=None, env=None):
        """
        Retrieve a context for the current module.

        Args:
            prefix (str):
                URL prefix acting as the base path.
            module (str):
                Dotted module name of the Python module using this context.  Callers should use
                :data:`__name__` from the root of their module.
            path (str):
                Base path of the module, needed for static routes.  Callers should use
                ``os.path.dirname(__file__)`` from the root of their module.
            env (dict):
                Additional variables to make available in the Jinja context.  See
                :attr:`.WebContext.env` for details.

        Returns:
            .WebContext:
                Linked context instance for that module.
        """
        self._contexts[module] = WebContext(self, prefix, module, path, env)
        return self._contexts[module]

    def add_loader(self, module):
        """
        Register a Jinja2 package loader for the given module.

        Args:
            module (str):
                Module name to register.
        """
        if not aiohttp_jinja2:
            raise immp.HookError("Loaders require Jinja2 and aiohttp_jinja2")
        self._loader.mapping[module] = PackageLoader(module)

    def add_route(self, *args, **kwargs):
        """
        Equivalent to :meth:`aiohttp.web.UrlDispatcher.add_route`.
        """
        return self.app.router.add_route(*args, **kwargs)

    def add_static(self, *args, **kwargs):
        """
        Equivalent to :meth:`aiohttp.web.UrlDispatcher.add_static`.
        """
        return self.app.router.add_static(*args, **kwargs)

    async def start(self):
        await super().start()
        await self._runner.setup()
        if "path" in self.config:
            log.debug("Starting server on socket %s", self.config["path"])
            self._site = web.UnixSite(self._runner, self.config["path"])
        else:
            log.debug("Starting server on host %s:%d", self.config["host"],
                      self.config["port"])
            self._site = web.TCPSite(self._runner, self.config["host"],
                                     self.config["port"])
        await self._site.start()

    async def stop(self):
        await super().stop()
        if self._site:
            log.debug("Stopping server")
            await self._runner.cleanup()
            self._site = None