{"id":18103970,"url":"https://github.com/svoop/rodbot","last_synced_at":"2025-04-06T05:48:17.473Z","repository":{"id":66851609,"uuid":"601601904","full_name":"svoop/rodbot","owner":"svoop","description":"Minimalistic framework to build bots on top of a Roda backend.","archived":false,"fork":false,"pushed_at":"2024-03-26T19:55:04.000Z","size":1682,"stargazers_count":1,"open_issues_count":4,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-04-26T21:22:50.291Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/svoop.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"svoop","custom":"https://donorbox.org/bitcetera"}},"created_at":"2023-02-14T12:23:05.000Z","updated_at":"2024-04-26T21:22:50.292Z","dependencies_parsed_at":"2023-12-29T00:25:00.009Z","dependency_job_id":"da636c0f-a821-4ecb-adad-33fbddda5102","html_url":"https://github.com/svoop/rodbot","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svoop%2Frodbot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svoop%2Frodbot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svoop%2Frodbot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/svoop%2Frodbot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/svoop","download_url":"https://codeload.github.com/svoop/rodbot/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247441006,"owners_count":20939235,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-31T22:13:39.449Z","updated_at":"2025-04-06T05:48:17.456Z","avatar_url":"https://github.com/svoop.png","language":"Ruby","funding_links":["https://github.com/sponsors/svoop","https://donorbox.org/bitcetera"],"categories":[],"sub_categories":[],"readme":"[![Version](https://img.shields.io/gem/v/rodbot.svg?style=flat)](https://rubygems.org/gems/rodbot)\n[![Tests](https://img.shields.io/github/actions/workflow/status/svoop/rodbot/test.yml?style=flat\u0026label=tests)](https://github.com/svoop/rodbot/actions?workflow=Test)\n[![Code Climate](https://img.shields.io/codeclimate/maintainability/svoop/rodbot.svg?style=flat)](https://codeclimate.com/github/svoop/rodbot/)\n[![GitHub Sponsors](https://img.shields.io/github/sponsors/svoop.svg)](https://github.com/sponsors/svoop)\n\n\u003cimg src=\"https://github.com/svoop/rodbot/raw/main/doc/rodbot.avif\" alt=\"Rodbot\" height=\"125\" align=\"left\"\u003e\n\n# Rodbot\n\nMinimalistic yet polyglot framework to build chat bots on top of a Roda backend for chatops and fun.\n\n\u003cbr clear=\"all\"\u003e\n\n* [Homepage](https://github.com/svoop/rodbot)\n* [API](https://rubydoc.info/gems/rodbot)\n* Author: [Sven Schwyn - Bitcetera](https://bitcetera.com)\n\nThank you for supporting free and open-source software by sponsoring on [GitHub](https://github.com/sponsors/svoop) or on [Donorbox](https://donorbox.com/bitcetera). Any gesture is appreciated, from a single Euro for a ☕️ cup of coffee to 🍹 early retirement.\n\n## Table of contents\n\n[Install](#install) \u003cbr\u003e\n[Anatomy](#anatomy) \u003cbr\u003e\n\u0026emsp;\u0026emsp;\u0026emsp;[App service](#app-service) \u003cbr\u003e\n\u0026emsp;\u0026emsp;\u0026emsp;[Relay services](#relay-services) \u003cbr\u003e\n\u0026emsp;\u0026emsp;\u0026emsp;[Schedule service](#schedule-service) \u003cbr\u003e\n[CLI](#CLI) \u003cbr\u003e\n[Request](#request)\u003cbr\u003e\n[Say](#say)\u003cbr\u003e\n[Routes and commands](#routes-and-commands) \u003cbr\u003e\n[Database](#database) \u003cbr\u003e\n[Environments](#environments) \u003cbr\u003e\n[Credentials](#credentials) \u003cbr\u003e\n[Plugins](#plugins) \u003cbr\u003e\n[Environment variables](#environment-variables) \u003cbr\u003e\n[Development](#development) \u003cbr\u003e\n\n## Install\n\n### Security\n\nThis gem is [cryptographically signed](https://guides.rubygems.org/security/#using-gems) in order to assure it hasn't been tampered with. Unless already done, please add the author's public key as a trusted certificate now:\n\n```\ngem cert --add \u003c(curl -Ls https://raw.github.com/svoop/rodbot/main/certs/svoop.pem)\n```\n\n### Generate new bot\n\nSimilar to other frameworks, generate the files for your new bot as follows:\n\n```\ngem install rodbot --trust-policy MediumSecurity\nrodbot new my_bot\ncd my_bot\n```\n\nFor the bot to be useful at all, you should choose one of the supported [relay service plugins](#plugins). Say, you'd like to interact via Matrix:\n\n```\nbundle config set --local with matrix\nbundle install\n```\n\nYou can use more than one plugin of course. Please note that you have to list them separated with a space:\n\n```\nbundle config set --local with matrix slack\nbundle install\n```\n\nPlease refer to the [Matrix plugin README](https://rubydoc.info/github/svoop/rodbot/file/lib/rodbot/plugins/matrix/README.matrix.md) for more on how to configure and authorise this relay service.\n\nTime to add Git to the mix. Both `gems.locked` and `.bundle` are included in order to use the same gems and versions both for local development and deployment to production:\n\n```\ngit init\ngit add .\ngit commit -m \"Bootstrap Rodbot\"\n```\n\nYou're all set, let's have a look at what Rodbot can do for you:\n\n```\nbundle exec rodbot --help\n```\n\n## Anatomy\n\nThe bot consists of three kinds of services interacting with one another:\n\n```\nRODBOT                                                            EXTERNAL\n╭╴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╶╮\n╷ ╭──────────────────╮  \u003c─────\u003e  ╭──────────────────╮       ╷\n╷ │ APP              │  \u003c───╮    │ RELAY - Matrix   ├╮  \u003c──────\u003e  [1] Matrix\n╷ ╰──────────────────╯  \u003c─╮ │    ╰┬─────────────────╯├╮  \u003c──┼──\u003e  [1] simulator\n╷                         │ │     ╰┬─────────────────╯│   \u003c────\u003e  [1] ...\n╷                         │ │      ╰──────────────────╯     ╵\n╷                         │ │                               ╵\n╷                         │ │     ╭──────────────────╮      ╵\n╷                         │ ╰──\u003e  │ SCHEDULE         │  \u003c───┼───  [2] clock\n╷                         │       ╰──────────────────╯      ╷\n╷                         │                                 ╷\n╷                         ╰─────────────────────────────────────  [3] webhook caller\n╰╴ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╶╯\n```\n\n### App service\n\nThe **app service** is a [Roda app](https://roda.jeremyevans.net) where the real action happens. It acts on and responds to HTTP requests from:\n\n* commands forwarded by **relay services**\n* timed events triggered by the **schedule service**\n* third party webhook calls e.g. from GitLab, GitHub etc\n\nSee [Rodbot::Config::DEFAULTS](https://github.com/svoop/rodbot/blob/main/lib/rodbot/config.rb) for available config settings and their defaults.\n\n#### Roda\n\nThe Roda app is located in the `app` directory. It contains:\n\n* `app.rb` – Roda app class where new routes are added using `run` statements\n* `routes\\` – Directory which contains one route file for every `run` statement\n* `views\\` – Directory which contains layouts and views called with `view` in route files\n\nFor an example, take a look at `app/routes/help.rb` generated as part of every new Rodbot app.\n\nThe `app.rb` loads the Rodbot plugin with `plugin :rodbot`. This Roda plugin is a necessary dependency for many Rodbot plugins and does two things.\n\nIt loads the following Roda plugins:\n\n* [multi_run](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/MultiRun.html)\n* [environments](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Environments.html)\n* [heartbeat](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Heartbeat.html)\n* [public](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Public.html)\n* [run_append_slash](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/RunAppendSlash.html)\n* [halt](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Halt.html)\n* [unescape_path](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/UnescapePath.html)\n* [render](http://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/Render.html)\n\nIt also loads the following Roda extension provided by Rodbot:\n\n* Shortcut `r.arguments` for `r.params['arguments']`\n\n#### Host\n\nThe **app service** binds to `localhost` by default and therefore isolates it from the internet. In case you want to make it publicly reachable, you have to set the `RODBOT_APP_HOST` environment variable to a public IP. Or to bind to all IPs of all interfaces:\n\n```\nexport RODBOT_APP_HOST=0.0.0.0\n```\n\n#### Ports\n\nThe **app service** binds to the base port 7200 by default. However, each **relay service** needs a predictable port to bind to as well, which is why the next few following ports must not be in use already. If you have to, you can change the base port in `config/rodbot.rb`:\n\n```ruby\nport 12345\n```\n\n#### Commands\n\nAll top level GET requests such as `GET /foobar` are commands and therefore accessible by relays, for instance using `!foobar` on Matrix.\n\nResponses have to be either of the following content types:\n\n* `text/plain; charset=utf-8`\n* `text/markdown; charset=utf-8`\n\nPlease note that the Markdown might get stripped on communication networks which feature only limited or no support for Markdown.\n\nThe response may contain special tags which have to be replaced appropriately by the corresponding **relay service**:\n\nTag | Replaced with\n----|--------------\n`[[SENDER]]` | Mention the sender of the command.\n`[[EVERYBODY]]` | Mention everybody.\n\n#### Other routes\n\nAll higher level requests such as `GET /foo/bar` are not accessible by relays. Use them to implement other aspects of your bot such as webhooks or schedule tasks.\n\n### Relay services\n\nThe **relay service** act as glue between the **app service** and external communication networks such as Matrix.\n\nEach relay service does three things:\n\n* **Proactive:** It creates and listens to a local TCP socket which accepts and forwards messages. See below for more on this.\n* **Reactive:** It reads messages, detects commands usually beginning with a `!`, forwards them to the **app service** and writes the HTTP response back as a message to the communication network.\n* **Test:** It detects the `!ping` command and replies with \"pong\" *without* hitting the **app service**.\n\nYou can simulate such a communication network locally:\n\n```\nrodbot simulator\n```\n\nEnter the command `!pay EUR 123` and you see the request `GET /pay?argument=EUR+123` hitting the **app service**.\n\n#### TCP socket\n\nThe TCP socket is primarily used by other Rodbot services to forward messages to the corresponding external communication network. However, you can use these sockets for non-Rodbot processes as well e.g. to issue notifications when events happen on the host running Rodbot.\n\nSimply connect to a socket and submit the message as plain text or Markdown in UTF-8. Multiple lines are allowed, to finish and post the message, append the EOT character (`\\x04` alias Ctrl-D).\n\nSuch simple messages are always posted to the primary room (aka: channel, group etc) of the communication network. For more complex scenarios, please take a look at [message objects](https://www.rubydoc.info/gems/rodbot/Rodbot/Message) which may contain meta information as well.\n\n### Schedule service\n\nThe **schedule service** is a [Clockwork process](https://github.com/Rykian/clockwork) which triggers Ruby code asynchronously as configured in `config/schedule.rb`.\n\nIt's a good idea to have the **app service** do the heavy lifting while the schedule simply fires the corresponding HTTP request.\n\nA word or two on time zones since they are particularly important for schedules:\n\nAutomatic discovery of the local time zone and DST status is rather unreliable. Therefore, Rodbot expects you to set the time zone in `config/rodbot.rb` using `time_zone`. See `ls /usr/share/zoneinfo` for valid values. To correctly handle DST, you should use geographical zones like `Europe/Paris` rather than technical zones like `CET`. If `time_zone` is not defined, the environment variable `TZ` is read instead. And if `TZ` isn't set neither, Rodbot falls back to `Etc/UTC`.\n\nAlso, make sure the [time zone data is available](https://tzinfo.github.io) where you deploy your bot to. The [official Alpine-based Ruby images](https://hub.docker.com/_/ruby) for instance doesn't come with it preinstalled, so you either have to `RUN apk add --no-cache tzdata` in the Dockerfile or add the [tzinfo-data gem](https://rubygems.org/gems/tzinfo-data) to the bundle for `TZ` to have any effect at all.\n\n## CLI\n\nThe `rodbot` CLI is the main tool to manage your bot. For a full list of functions:\n\n```\nrodbot --help\n```\n\n### Starting and stopping services\n\nWhile working on the app service, you certainly want to try routes:\n\n```\nrodbot start app\n```\n\nThis starts the server in the current terminal. You can set breakpoints with `binding.irb`, however, if you prefer a real debugger:\n\n```\nrodbot start app --debugger\n```\n\nThis requires the [debug gem](https://rubygems.org/gems/debug) and adds the ability to set breakpoints with `debugger`.\n\nHere's how to start single services in the background:\n\n```\nrodbot start app --daemonize\n```\n\nYou can also start all services at once in which case the services must run in the background and therefore the `--daemonize` is implied and may be omitted:\n\n\n```\nrodbot start\n```\n\nFinally, to stop all running Rodbot services:\n\n```\nrodbot stop\n```\n\n### Deployment\n\nWhile controlling Rodbot as mentioned in the previous section is okay for local development, deploying the bot to production comes in a gazillion scenarios. Rodbot helps you with scaffolds for some of them. To get the list of all deploy scaffolds:\n\n```\nrodbot deploy --help\n```\n\n:warning: It's near impossible to include such deployment scenarios in the test suite. If you find an error or have an improvement, please [submit an issue](https://github.com/svoop/rodbot/issues)!\n\nLet's take a quick look at the two most common scenarios:\n\n#### Docker\n\nTo run all of Rodbot in one single Docker service:\n\n```\nrodbot deploy docker\n```\n\nIn case you prefer to split each service into its own container:\n\n```\nrodbot deploy docker --split\n```\n\nThe Docker deployment is a `compose.yml` file, so you might want to write it to disk:\n\n```\nrodbot deploy docker \u003ecompose.yml\n```\n\n#### Procfile\n\nThe `Procfile` was introduced by Heroku and is nowadays supported many cloud providers as well as tools for local development.\n\nWhile a monolith approach is certainly possible, it makes more sense to split each service into its own process:\n\n```\nrodbot deploy procfile --split\n```\n\nAs per convention, the `Procfile` should be placed in the root of the project:\n\n```\nrodbot deploy procfile --split \u003eProcfile\n```\n\nIt's easy to test drive using a process manager such as [Foreman](https://rubygems.org/gems/foreman):\n\n```\ngem install foreman\nforeman start\n```\n\nFor more control and debug features, you might want to try [Overmind](https://github.com/DarthSim/overmind) instead e.g. installed via [Homebrew](https://brew.sh):\n\n```\nbrew install overmind\novermind start\n```\n\n## Request\n\nTo query the **app service**, you can either use the bundled [HTTPX](https://rubygems.org/gems/httpx) gem or the following convenience wrapper:\n\n```ruby\nresponse = Rodbot.request('/time', params: { zone: 'UTC' })\n```\n\nThis uses the default `method: :get` and the default `timeout: 10` seconds, it returns an instance of [HTTPX::Response](https://www.rubydoc.info/gems/httpx/HTTPX/Response):\n\n```ruby\nresponse.code   # =\u003e 200\nresponse.body   # =\u003e '2023-09-06 22:51:50.231703 UTC'\n```\n\n## Say\n\nYou can send proactive messages to communication networks with `Rodbot.say`.\n\nSince you're not limited to just one relay plugin, you have to configure which of them shall post messages submitted with `Rodbot.say` by adding `say true` in `config/rodbot.rb`. Here's an example for the Matrix relay plugin:\n\n```ruby\nplugin :matrix do\n  say true\n  (...)\nend\n```\n\nWith this in place, you can now submit messages from just about anywhere, most notably **app service** routes and **schedule service** jobs.\n\n```ruby\nsay(\"Hello, World!\")\n```\n\nYou can further narrow where to post the message if you specify the relay plugin explicitly:\n\n```ruby\nsay(\"Hello, Slack!\", on: :slack)\n```\n\n## Routes and commands\n\nAdding new tricks to your bot boils down to adding routes to the app service which is powered by Roda, a simple yet very powerful framework for web applications: Easy to learn (like Sinatra) but really fast and efficient. Take a minute and [get familiar with the basics of Roda](http://roda.jeremyevans.net/).\n\nRodbot relies on [MultiRun](https://roda.jeremyevans.net/rdoc/classes/Roda/RodaPlugins/MultiRun.html) to spread routes over more than one routing file. This is necessary for Rodbot plugins but is entirely optional for your own routes.\n\n⚠️ At this point, keep in mind that any routes at the root level like `/pay` or `/calculate` can be accessed via chat commands such as `!pay` and `!calculate`. Routes which are nested further down, say, `/myapi/users` are off limits and should be used to trigger schedule events and such. Make sure you don't accidentally add routes to the root level you don't want people to access via chat commands, not even by accident.\n\nTo add a simple \"Hello, World!\" command, all you have to do is add a route `/hello`. A good place to do so is `app/routes/hello.rb`:\n\n```ruby\nmodule Routes\n  class Hello \u003c App\n\n    route do |r|\n\n      # GET /hello\n      r.root do\n        response['Content-Type'] = 'text/plain; charset=utf-8'\n        'Hello, World!'\n      end\n\n    end\n\n  end\nend\n```\n\nTo try, start the app service with `rodbot start app` and fire up the simulator with `rodbot simulator`:\n\n```\nrodbot\u003e !hello\nHello, World!\n```\n\nTry to keep these route files thin and extract the heavy lifting into service classes. Put those into the `lib` directory where they will be autoloaded by Zeitwerk.\n\n## Database\n\nYour bot might be happy dealing with every command as an isolated event. However, some implementations require data to be persisted between requests. A good example is the [OTP plugin](https://rubydoc.info/github/svoop/rodbot/file/lib/rodbot/plugins/otp/README.otp.md) which needs a database to assure each one-time password is accepted once only.\n\nRodbot implements a very simple key/value database which is completely optional and supports a few different backends.\n\n### Redis\n\nFor the Redis backend to work, you have to install the corresponding Bundler group:\n\n```\nbundle config set --local with redis\nbundle install\n```\n\nThen set the connection URL in `config/rodbot.rb`:\n\n```ruby\ndb 'redis://localhost:6379/10'\n```\n\n### Hash\n\nThe Hash backend is not thread-safe and therefore shouldn't be used in production. To use it, simply add the following to `config/rodbot.rb`:\n\n```ruby\ndb 'hash'\n```\n\n### Write and read data\n\nWith this in place, you can access the database with `Rodbot.db`:\n\n```ruby\nRodbot.db.flush                                        # =\u003e Rodbot::Db\n\nRodbot.db.set('foo') { 'bar' }                         # =\u003e 'bar'\nRodbot.db.get('foo')                                   # =\u003e 'bar'\nRodbot.db.scan('*')                                    # =\u003e ['foo']\nRodbot.db.delete('foo')                                # =\u003e 'bar'\nRodbot.db.get('foo')                                   # =\u003e nil\n\nRodbot.db.set('lifetime', expires_in: 1) { 'short' }   # =\u003e 'short'\nRodbot.db.get('lifetime')                              # =\u003e 'short'\nsleep 1\nRodbot.db.get('lifetime')                              # =\u003e nil\n```\n\nFor a few more tricks, see the [Rodbot::Db docs](https://www.rubydoc.info/gems/rodbot/Rodbot/Db.html).\n\n## Environments\n\nSimilar to other frameworks, Rodbot features different environments which affect the way certain processes work. Use the environment variable `RODBOT_ENV` to set control this:\n\nValue | Meaning\n------|--------\ndevelopment | This is the default environment used for local develoment.\nproduction | Use this environment when you deploy Rodbot.\ntest | This environment is set for the automated tests of Rodbot.\n\nThe current environment can be programmatically queried:\n\n```ruby\nENV['RODBOT_ENV'] = \"production\"\nRodbot.env.current        # =\u003e \"production\"\nRodbot.env.production?    # =\u003e true\nRodbot.env.development?   # =\u003e false\n```\n\nYou can use the more generic alternative `APP_ENV` as well, however, if `RODBOT_ENV` is defined, it takes precedence over `APP_ENV`.\n\n## Credentials\n\nIn order not to commit secrets to repositories or environment variables, Rodbot bundles the [dry-credentials](https://rubygems.org/gems/dry-credentials) gem and exposes it via the `rodbot credentials` CLI command. The secrets are then available in your code like `Rodbot.credentials.my_secret` and the encrypted files are written to `config/credentials`.\n\n## Plugins\n\nRodbot aims to keep its core small and add features via plugins, either built-in or provided by gems.\n\n### Built-in plugins\n\nName | Dependencies | Description\n-----|--------------|------------\n[:matrix](https://rubydoc.info/github/svoop/rodbot/file/lib/rodbot/plugins/matrix/README.matrix.md) | yes | relay service for the [Matrix communication network](https://matrix.org)\n[:slack](https://rubydoc.info/github/svoop/rodbot/file/lib/rodbot/plugins/slack/README.slack.md) | yes | relay service for the [Slack communication network](https://slack.com)\n[:otp](https://rubydoc.info/github/svoop/rodbot/file/lib/rodbot/plugins/otp/README.otp.md) | yes | guard commands with one-time passwords\n[:gitlab_webhook](https://rubydoc.info/github/svoop/rodbot/file/lib/rodbot/plugins/gitlab_webhook/README.gitlab_webhook.md) | no | event announcements from [GitLab](https://gitlab.com)\n[:github_webhook](https://rubydoc.info/github/svoop/rodbot/file/lib/rodbot/plugins/github_webhook/README.github_webhook.md) | no | event announcements from [GitHub](https://github.com)\n[:hal](https://rubydoc.info/github/svoop/rodbot/file/lib/rodbot/plugins/hal/README.hal.md) | no | feel like Dave (demo)\n[:word_of_the_day](https://rubydoc.info/github/svoop/rodbot/file/lib/rodbot/plugins/word_of_the_day/README.word_of_the_day.md) | no | word of the day announcements (demo)\n\nYou have to install the corresponding Bundler group in case the plugin depends on extra gems. Here's an example for the `:otp` plugin listed above:\n\n```\nbundle config set --local with otp\nbundle install\n```\n\n### How plugins work\n\nGiven the following `config/rodbot.rb`:\n\n```ruby\nplugin :my_plugin do\n  color 'red'\nend\n```\n\nPlugins provide one or more extensions each of which extends one of the services. In order only to spin things up when needed, the plugin may contain the following files:\n\n* `rodbot/plugins/my_plugin/app.rb` – add routes and/or extend Roda\n* `rodbot/plugins/my_plugin/relay.rb`  – add a relay\n* `rodbot/plugins/my_plugin/schedule.rb` – add schedules to Clockwork\n\nWhenever a service boots, the corresponding file is required.\n\nIn order to keep these plugin files slim, you should extract functionality into service classes. Just put them into `rodbot/plugins/my_plugin/lib/` and use `require_relative` where you need them.\n\n### Create plugins\n\nYou can create plugins in any of the following places:\n\n* inside your Rodbot instance:\u003cbr\u003e`/lib/rodbot/plugins/my_plugin`\n* in a vendored gem \"rodbot-my_plugin\":\u003cbr\u003e`/lib/rodbot/vendor/gems/rodbot-my_plugin/lib/rodbot/my_plugin`\n* in a published gem \"rodbot-my_plugin\":\u003cbr\u003e`/lib/rodbot/plugins/my_plugin`\n\nPlease adhere to common naming conventions and use the dashed prefix `rodbot-` (and Module `Rodbot`), however, underscores in case the remaining gem name consists of several words.\n\n#### App extension\n\nAn app extension `rodbot/plugins/my_plugin/app.rb` defines the module `App` and looks something like this:\n\n```ruby\nmodule Rodbot\n  class Plugins\n    module MyPlugin\n      module App\n\n        module Routes \u003c ::App\n          route do |r|\n            # GET /my_plugin\n            r.root do\n              # called by command !my_plugin\n            end\n\n            # GET /my_plugin/whatever\n            r.get('whatever') do\n              # not reachable by any command\n            end\n          end\n        end\n\n        module ResponseMethods\n          # (...)\n        end\n\n      end\n    end\n  end\nend\n```\n\nThe `Routes` module contains all the routes you would like to inject.\n\nThe `App` module can be used to [extend all aspects of Roda](https://github.com/jeremyevans/roda#plugins-).\n\nFor an example, take a look at the [:hal plugin](https://github.com/svoop/rodbot/tree/main/lib/rodbot/plugins/hal).\n\n#### Relay extension\n\nA relay extension `rodbot/plugins/my_plugin/relay.rb` defines the class `Relay` and looks something like this:\n\n```ruby\nmodule Rodbot\n  class Plugins\n    module MyPlugin\n      class Relay \u003c Rodbot::Relay\n\n        def loops\n          SomeAwesomeCommunicationNetwork.connect\n          [method(:read_loop), method(:write_loop)]\n        end\n\n        private\n\n        def read_loop\n          loop do\n            # Listen in on the communication network\n          end\n        end\n\n        def write_loop\n          loop do\n            # Post something to the communication network\n          end\n        end\n\n      end\n    end\n  end\nend\n```\n\nThe `loops` method must returns an array of callables (e.g. a Proc or Method) which will be called when this relay service is started. The loops must trap the `INT` signal.\n\nProactive messages require other parts of Rodbot to forward a message directly. To do so, the relay has to implement a TCP socket. This socket must bind to the IP and port you get from the `bind` method which returns an array like `[\"localhost\", 7201]`.\n\nFor an example, take a look at the [:matrix plugin](https://github.com/svoop/rodbot/tree/main/lib/rodbot/plugins/matrix).\n\n#### Schedule extension\n\nA schedule extension `rodbot/plugins/my_plugin/schedule.rb` defines the class `Schedule` and looks something like this:\n\n```ruby\nmodule Rodbot\n  class Plugins\n    module MyPlugin\n      class Schedule\n\n        def initialize\n          Clockwork.every(1.day, -\u003e { tea }, at: '16:00')\n        end\n\n        private\n\n        def tea\n          Rodbot.say \"Time for a cup of tea!\"\n        end\n\n      end\n    end\n  end\nend\n```\n\nThe initializer must set at least one schedule using `Clockwork.every` – see the [Clockwork docs](https://www.rubydoc.info/gems/clockwork).\n\nFor an example, take a look at the [:word_of_the_day plugin](https://github.com/svoop/rodbot/tree/main/lib/rodbot/plugins/word_of_the_day).\n\n#### Toolbox\n\nBefore you write a plugin, familiarise yourself with the following bundled helpers:\n\n* [Rodbot::Refinements](https://www.rubydoc.info/gems/rodbot/Rodbot/Refinements.html) – just a few handy extensions to Ruby core classes\n* [Rodbot::Memoize](https://www.rubydoc.info/gems/rodbot/Rodbot/Memoize.html) – environment-aware memoization for method return values\n\n## Environment variables\n\nEnvironment variables are used for the configuration bits which cannot or should not be part of `config/rodbot.rb` mainly because they have to be set on the infrastructure level.\n\nVariable | Description | Default\n---------|-------------|--------\n`RODBOT_ENV` | Environment | development\n`RODBOT_CREDENTIALS_DIR` | Override the directory containing encrypted credentials files | config/credentials/\n`RODBOT_APP_HOST` | Override where to locally bind the app service | localhost\n`RODBOT_APP_URL` | Override where to locally reach the app service | http://localhost\n`RODBOT_RELAY_HOST` | Override where to bind the relay services | localhost\n`RODBOT_RELAY_URL_XXX` | Override where to locally reach the given relay service `XXX` (e.g. `MATRIX`) | tcp://localhost\n\n## Development\n\nTo install the development dependencies and then run the test suite:\n\n```\nbundle install\nbundle exec rake    # run tests once\nbundle exec guard   # run tests whenever files are modified\n```\n\nSome tests require Redis and will be skipped by default. You can enable them by setting the following environment variable along the lines of:\n\n```\nexport RODBOT_SPEC_REDIS_URL=redis://localhost:6379/10\n```\n\nYou're welcome to join the [discussion forum](https://github.com/svoop/rodbot/discussions) to ask questions or drop feature ideas, [submit issues](https://github.com/svoop/rodbot/issues) you may encounter or contribute code by [forking this project and submitting pull requests](https://docs.github.com/en/get-started/quickstart/fork-a-repo).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvoop%2Frodbot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsvoop%2Frodbot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsvoop%2Frodbot/lists"}