Utilities ========= IMMP comes with a number of common idioms and features provided by built-in hooks, which can be used by your own classes to avoid reimplementing common bot tasks. Data schema validation ---------------------- The included plugs make use of an internal schema validator to ensure all API responses contain the expected fields. These effectively act as assertions on the existence of all required fields, before starting to parse the object. In the most basic case, prepare a :class:`.Schema` representing the data structure, and call it on each API response of that type before using it. For plug and hook configuration, attach it to the :attr:`schema <.Configurable.schema>` attribute to have incoming config validated at creation. Storing data ------------ Some hooks may need to store their own information. The hook config is read-only -- any changes are not persisted because the config must be provided at startup. For data encountered during the app's lifetime, a database may be required. The :class:`.DatabaseHook` resource provides generic database access using the Peewee ORM. Define your models as subclasses of :class:`.BaseModel`, then at startup, obtain the database connection :attr:`.DatabaseHook.db` and call :meth:`Database.create_tables` with a list of all your models. From there, you can use Peewee's model methods to query and manage records. Adding commands --------------- The :class:`.CommandHook` provides an easy way to register commands that interact with a custom hook. Each command method should be decorated using :func:`.command`, and should accept a :class:`.Message` argument followed by any arguments expected from the user. Keyword arguments are not supported, though optional (with default values) and variable (varargs) parameters may be used. .. note:: This replaces the class-level attribute with a :class:`.Command` instance. This instance is callable, allowing you to use the underlying method like any other, though introspection may give surprising results if you rely on it being a traditional method. Dynamic commands ~~~~~~~~~~~~~~~~ You may need to make commands available based on config or state. For simple cases, you can make a command conditional by setting the :attr:`test` field to a method returning ``True`` if the command is currently valid. For more complex hooks, you may instead need to *generate* new commands based on config. This is where dynamic commands come into play. An unnamed command is effectively a template -- it won't itself be included in command sets, but you can provide qualified instances of it inside :meth:`.DynamicCommands.commands` using :meth:`.BaseCommand.complete`. Identities and access --------------------- If your hook interacts with a third-party service, with users from connected plugs mapping to external users within the service, consider implementing :class:`.IdentityProvider` to make this information available to other hooks like :ref:`admins/hooks:Sync`. You can also use permissions from your service to enforce and restrict user access to channels by integrating :class:`.AccessPredicate` into your hook, to be used with :ref:`admins/hooks:Access`. Incoming HTTP requests ---------------------- Some networks may only support listening for messages by subscribing to a webhook. You may also need to provide some web-based UI for certain features. You can use the :class:`.WebHook` resource hook to spin up a local :mod:`aiohttp.web` server, and bind URL paths to it from your plug or hook. Initial setup is abstracted by the :class:`.WebContext` context -- an instance of this can be obtained using :meth:`.WebHook.context`. With this, you can configure new routes into your code like a native :mod:`aiohttp` application. .. note:: Routes are named in :mod:`aiohttp` using the module path as their prefix. The helper method :meth:`.WebContext.url_for` provides path resolution without providing the prefix each time. Jinja2 templates ~~~~~~~~~~~~~~~~ If a template name is given to :meth:`.WebContext.route`, the request handler method will be wrapped by :mod:`aiohttp_jinja2`, meaning it should return a :class:`dict` to act as the template context. See :attr:`.WebContext.env` for the variables provided by default.