No Description

Kirk Trombley 095f45c0a6 Seven 4 years ago
config 79e240c730 add rollcoing.mining.neutral to secrets template 4 years ago
lib 2d63f3b662 Fix bug in arg min annotations and make choice have delimiter tiers 4 years ago
src 095f45c0a6 Seven 4 years ago
.dockerignore e8726aaa7a Fixing the Dockerfile set up 5 years ago
.drone.yml d67a8b5bc9 Update drone to trigger pipeline 4 years ago
.gitignore bd8f22c289 Modifying all plugins to use the new structure 5 years ago
Dockerfile ae0dd030db Actually nevermind I did the healthcheck completely wrong 4 years ago
LICENSE 1e57eea03a License + README 6 years ago
README.md b21720a97c Improving docs, adding with_shutdown 4 years ago
TODO_commands f82d6f8ed1 Aliasing 6 years ago
mkplugin.sh 0c29867b0c Implementing the plex status check plugin 5 years ago
requirements.txt 9e9dae060b Properly freeze requirements 4 years ago
rollbot-docker.sh c0565cde86 Fixing the script for running the docker image locally 5 years ago
rollbot-local.sh bd8f22c289 Modifying all plugins to use the new structure 5 years ago

README.md

Rollbot

Build Status

Running the Project

Running Rollbot requires either Docker or Python 3.7 to be installed on your machine. Additionally, there is some one-time set up, detailed here

One Time Set-Up

Open GroupMe through your preferred application, and create a new chat containing just yourself. This will be your testing chat, where your local copy of rollbot will post messages. Future plans for the project include a simpler local testing environment, but integrating with GroupMe is an important testing step regardless, and so it is reasonable to do it now anyway.

Next, create a config/secrets.toml file. Do NOT commit this file! It is by default added to .gitignore for you, but you should always take care you are not accidentally sharing this file.

cp config/secrets.toml.template config/secrets.toml

Open the new secrets.toml file in your preferred editor, and then navigate to the GroupMe Bots page. Then, on the bots page, create a new bot. For this bot's group, select the new chat you made above. Name can be whatever you like, and you can leave the other fields blank. Click submit, and retrieve your Bot ID and Group ID from the bots page. Put these in your secrets.toml under the groupme_bots section, with your Group ID serving as the key and the Bot ID serving as the value (which must be in quotes).

For example, if your bot ID is 456, and your group ID is 789, your secrets.toml needs to contain the following

[groupme_bots]
789 = "456"

Note that other plugins may require additional secrets, and that image uploads (namely with the !seychelles plugin), require an imgur client ID as well. You can skip this if you will not be using the upload_image function in the util module, but if you will be uploading images, log-in to Imgur and register a new Application. Name the application Rollbot or something similar, tick the box for Anonymous usage without authorization, and fill in dummy values for any remaining fields. Click submit, and put the Client ID you are given on the next page (which can also be looked up from Account Settings > Applications) into your secrets.toml, as the value for imgur_client_id.

That's it for secrets!

Save the file and move on to deciding if you want to do your local execution and/or development with or without docker.

Build and Deploy w/ Docker

Run ./rollbot-docker.sh run or manually run

docker build -t rollbot3:latest .
docker run -p6070:6070 --name rollbot3-instance -d rollbot3

You can run ./rollbot-docker.sh clean to tear down the container.

Local Run w/o Docker

This requires Python 3.7 to be installed on your machine. The use of a Python virtual environment is recommended, and so you should run the following

python3 -mvenv .venv
. .venv/bin/activate
pip install -r requirements.txt

To leave the virtual environment, simply run deactivate, and when you want to run the project again, you need only run . .venv/bin/activate.

To launch rollbot run ./rollbot-local.sh or cd into src/ and run ROLLBOT_CFG_DIR=../config python3 app.py, stopping the execution with Ctrl-C.

Development/Contributing

Please keep new plugin branches to the scheme feature/name-of-plugin where possible. All work must be done on either a branch or a fork of the repository, and must go through the pull request process.

Developing w/ Docker

If you do not have Python 3.7.1 (or later) available, or want to develop using docker locally, you can make use of the rollbot-docker.sh script. You will need to run this as root (preferably with sudo), or as some user capable of using docker. If you are not running this script as root, and instead have a different local docker user, you will need to export FORCE_NOROOT=* before running the script.

You can run ./rollbot-docker.sh help to see a full list of options. The most useful are run and clean. To build the rollbot3:latest image and deploy it to a docker container called rollbot3-instance on your local machine, simply run

./rollbot-docker.sh run

This will also blow away any existing container called rollbot3-instance before building the new image and container. If you would just like to kill and remove existing containers with that name, without starting a new one, you can run

./rollbot-docker.sh clean

In general, your development loop will probably look something like, modify your plugin, run ./rollbot-docker.sh run, and repeat. When you are all done, you can use ./rollbot-docker.sh clean to remove the lingering container on your system.

POSTing to Local Server

Once you have a local server up on http://localhost:6070/rollbot, point your REST client of choice (my personal recommendation is Insomnia) at that address. Creat a new POST request, of JSON type (that is, your Content-Type should be application/json). Then, set the body of this POST to be

{
  "text": "!echo Hello",
  "group_id": "your-group-id",
  "attachments": [],
  "avatar_url": "https://i.groupme.com/123456789",
  "created_at": 1302623328,
  "id": "1234567890",
  "name": "Insomnia",
  "sender_id": "1",
  "sender_type": "user",
  "source_guid": "GUID",
  "system": false,
  "user_id": "1234567890"
}

The first two options, text and group_id will need to be modified. Set the value of group_id to the group ID you found and put into your secrets.toml above. Then, click run! Or however your REST client sends the request. If you have everything set up correctly, your phone should buzz and you should receive a message in your bot testing chat you set up before, from the chatbot you added to it, responding to a !echo Hello command. Now, change the value of text in the body of your request to be whatever message you would like to simulate sending, and you should be able to get the hang of how you can test your local instance of rollbot.

Writing a Plugin

The quickstart for creating a new plugin is to determine a name for the plugin, say, my_cool_plugin, and run

./mkplugin my_cool_plugin

This script will generate a file at src/plugins/my_cool_plugin.py containing (approximately)

from rollbot import as_plugin


@as_plugin
def my_cool_plugin():
    return "Hello, world!"

The script also adds an import plugins.my_cool_plugin to src/plugins/__init__.py, so if you are reverting what this script did, remember to remove this import from that file. Only plugins imported in that top level module file will be available for the application, so if creating a plugin manually, you will need to add an import to that file.

The above script is the most basic possible rollbot plugin, which will simply respond with Hello, world! when it receives a message starting with !my_cool_plugin. The actual wiring of the plugin is handled by the as_plugin decorator, which performs all of the following:

  • Inspects the name of your function and turns that into the command word. This can be overidden by passing a single, string argument to as_plugin, which is detailed below.
  • Inspects the arguments of your function, to determine what, if any, rollbot components need to be passed into your function, which are detailed below.
  • Wraps your plugin function to take any return values that are not instances of RollbotResponse and convert them to strings, before wrapping them in a RollbotResponse with default settings, which is detailed below.
  • Generates a RollbotPlugin extension class which overrides the on_command method to call your plugin function appropriately, and returns this class from the decorator, effectively assigning it to the my_cool_plugin variable instead of your plugin function, which is what allows your plugin to be found dynamically after just importing the plugin module

Other Decorators

The rollbot.plugins.decorators module exports decorators that can be used for specific additional functionality on functional commands. as_plugin and with_help are re-exported by rollbot for convenience, but other tools are left in this decorators submodule. All of these decorators should be applied after as_plugin.

  • with_startup - attach a function to a command to override the RollbotPlugin.on_start function
  • with_shutdown - the same as with_startup, but for the RollbotPlugin.on_shutdown function
  • require_min_args - enforce a minimum number of arguments in the message
  • require_args - enforce an exact number of arguments in the message

Plugin Dependency Injection

The rollbot.plugins.injection module exports many dependency injection tools for your plugins. If you are using the as_plugin decorator, you can annotate the arguments of your function to have these values injected at runtime.

  • Message - will receive the RollbotMessage triggering the command
  • Bot - the Rollbot instance executing the command
  • Logger - the logger the command should use
  • ArgList - the argument list, resulting from calling RollbotMessage.arg_list
    • Arg(n, conversion=c, fail_msg=m) - will receive the argument at index n, converted by c (which can be omitted if a string is desired), and will fail the command if the conversion fails with a ValueError and instead use the failure message m in the response (which itself will be formatted with the actual value of the argument)
  • Config(key=k) - will receive the configuration associated with k, or the root confiuration dictionary if k is not provided or is None
  • Database - will receive the SQLAlchemy database session scope, but it is preferred to use one of the other database-lookup types when possible
    • Singleton(cls) - will receive the proper singleton for this execution of the given cls, which itself must be annotated with either as_group_singleton or as_sender_singleton.
      • Singleton(cls).by(fn) - will execute fn (a function with its own dependencies) to perform the query
      • Singletone(cls).by_all(fn) - works same as Singleton.by but fn is instead expected to return an iterable of keys, and all singletons will be looked up
    • Query(cls) - will receive the contents of the table of the SQLAlchemy model class cls
      • Query(cls).filter(fn) - filters the Query by the filter returned by fn (a function with its own dependencies), which can be chained as Query.filter().filter()...
  • Lazy(a) - will receive an object that, when Lazy.get is called, will return the result of the injection a, delaying computation until this point, and only calculating the value on the first call to get

Additionally, as a legacy feature, plugin arguments can use specific names to request specific dependencies. New plugins should not use this system, but the argument names and name patterns are:

  • msg is the RollbotMessage triggering the command
  • db is the SQLAlchemy database session scope
  • log is the command's logger
  • bot is the Rollbot instance running the command
  • data.* is supplied the group singleton of the annotated data type
  • .*cfg is supplied the configuration associated with the annotated key (as a string)

RollbotResponse

TODO

Further docs to come! Good luck!

Contributing Your Plugin

Once you are confident your plugin is functional, push your code to remote with the following, again replacing my-awesome-branch with your chosen branch name.

git push -u origin feature/my-awesome-branch

Then, open a Pull Request, shoot me a message, and I'll take it from there!