Repository: attzonko/mmpy_bot (language: python)

Issue:
2.0.0 release
Let's use this issue to keep track of the to-do list for the upcoming 2.0.0 release. Now that the current version lives on its own branch, we should be able to merge #153 into master and then work from there.

To do:
- [x] Update all documentation
- [x] Let the `Settings` class try to infer values from environment variables if present
- [x] Provide a docker image with an entrypoint that can easily be overridden to add custom plugins etc.
- [ ] Add migration guide to docs
- [ ] Improve logging (it would be nice if the bot facilitates easily logging to file, and the logs should be more extensive with messages of various logging levels)

At release time:
- [x] Publish the docker image as `mmpy_bot:2.0.0`.
- [x] Publish new PyPi package `mmpy_bot:2.0.0`


Discussion / hints:
Agreed, I'll merge #153 with the version as 2.0.0.dev1 and once we finish this todo list we can make it the official 2.0.0 and release to pypi. 
In regards to the documentation, how do we feel update updating README.md to cut out everything south of the "Run the bot" section? I think the information about plugins, attachments and files can be covered in ./docs/.

Fix patch:
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 13690910..e6ef6093 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -2,20 +2,18 @@
 Credits
 =======
 
-Development Lead
-----------------
-
-* Alex Tzonkov <alex.tzonkov@gmail.com>
-
-Contributors
+Active Contributors
 ------------
-* Victor Hu <selain@nature.ee.ncku.edu.tw>
-* tgly307 <tgly307@163.com>
+* Alex Tzonkov <alex.tzonkov@gmail.com>
+* Jelmer Neeven <https://github.com/jneeven>
+* Renato Alves <https://github.com/unode>
+* Thomas Tuffin <ttuffin@redhat.com>
 
 Past Contributors
 ------------------
+* Victor Hu <selain@nature.ee.ncku.edu.tw>
+* tgly307 <tgly307@163.com>
 * GoTLiuM InSPiRiT <gotlium@gmail.com>
-
 * Grokzen <grokzen@gmail.com>
 * Philipp Schmitt <philipp@schmitt.co>
 * Tom Horlock <thomashorlock1993@gmail.com>
diff --git a/README.md b/README.md
index 9fd3aa87..4813fe6d 100644
--- a/README.md
+++ b/README.md
@@ -62,177 +62,37 @@ Note: **Enable Bot Account Creation** must be enabled under System Console
 1. Navigate to Integrations -> Bot Accounts -> Add Bot Account
 1. Fill in the configuration options and upon creation take note of the **Access Token**
 
-For use all API(V4.0.0), you need add bot user to system admin group to avoid 403 error.
+Note that some API functions, such as ephemeral message replies, will require the bot to be part of the **System Admin** group,
+however most API functions will work with a regular **Member** account role. Just be aware that if some API functions are not working, it
+may be due to a lack of appropriate permissions.
 
-### Configuration
-Edit settings.py and configure as needed.
 
-**settings.py:**
-
-```python
-MATTERMOST_URL: str = "https://chat.com"
-MATTERMOST_PORT: int = 443
-BOT_TOKEN: str = "token"
-BOT_TEAM: str = "team_name"
-SSL_VERIFY: bool = True
-WEBHOOK_HOST_ENABLED: bool = False
-WEBHOOK_HOST_URL: str = "http://127.0.0.1"
-WEBHOOK_HOST_PORT: int = 8579
-DEBUG: bool = False
-IGNORE_USERS: Sequence[str] = field(default_factory=list)
-# How often to check whether any scheduled jobs need to be run, default every second
-SCHEDULER_PERIOD: float = 1.0
-```
-
-
-### Run the bot
-
-Create your own startup file. For example `run.py`:
-
-```python
-from mmpy_bot.bot import Bot
-
-
-if __name__ == "__main__":
-    Bot().run()
-```
-
-Now you can talk to your bot in your Mattermost client!
-
-##### TODO: Update below instructions
-
-## Attachment Support
-
-```python
-from mmpy_bot.bot import respond_to
-
-
-@respond_to('webapi')
-def webapi_reply(message):
-    attachments = [{
-        'fallback': 'Fallback text',
-        'author_name': 'Author',
-        'author_link': 'http://www.github.com',
-        'text': 'Some text here ...',
-        'color': '#59afe1'
-    }]
-    message.reply_webapi(
-        'Attachments example', attachments,
-        username='Mattermost-Bot',
-        icon_url='https://goo.gl/OF4DBq',
-    )
-    # Optional: Send message to specified channel
-    # message.send_webapi('', attachments, channel_id=message.channel)
-```
-
-*Integrations must be allowed for non admins users.*
-
-
-## File Support
-
-```python
-from mmpy_bot.bot import respond_to
-
-
-@respond_to('files')
-def message_with_file(message):
-    # upload_file() can upload only one file at a time
-    # If you have several files to upload, you need call this function several times.
-    file = open('test.txt')
-    result = message.upload_file(file)
-    file.close()
-    if 'file_infos' not in result:
-        message.reply('upload file error')
-    file_id = result['file_infos'][0]['id']
-    # file_id need convert to array
-    message.reply('hello', [file_id])
-```
-
-
-## Plugins
-
-A chat bot is meaningless unless you can extend/customize it to fit your own use cases.
-
-To write a new plugin, simply create a function decorated by `mmpy_bot.bot.respond_to` or `mmpy_bot.bot.listen_to`:
-
-- A function decorated with `respond_to` is called when a message matching the pattern is sent to the bot (direct message or @botname in a channel/group chat)
-- A function decorated with `listen_to` is called when a message matching the pattern is sent on a channel/group chat (not directly sent to the bot)
-
-```python
-import re
-
-from mmpy_bot.bot import listen_to
-from mmpy_bot.bot import respond_to
-
-
-@respond_to('hi', re.IGNORECASE)
-def hi(message):
-    message.reply('I can understand hi or HI!')
-
-
-@respond_to('I love you')
-def love(message):
-    message.reply('I love you too!')
-
-
-@listen_to('Can someone help me?')
-def help_me(message):
-    # Message is replied to the sender (prefixed with @user)
-    message.reply('Yes, I can!')
-
-    # Message is sent on the channel
-    # message.send('I can help everybody!')
-```
-
-To extract params from the message, you can use regular expression:
-```python
-from mmpy_bot.bot import respond_to
-
-
-@respond_to('Give me (.*)')
-def give_me(message, something):
-    message.reply('Here is %s' % something)
-```
-
-If you would like to have a command like 'stats' and 'stats start_date end_date', you can create reg ex like so:
-
-```python
-from mmpy_bot.bot import respond_to
-import re
-
-
-@respond_to('stat$', re.IGNORECASE)
-@respond_to('stat (.*) (.*)', re.IGNORECASE)
-def stats(message, start_date=None, end_date=None):
-    pass
-```
-
-If you don't want to expose some bot commands to public, you can add `@allowed_users()` or `@allowed_channels()` like so:
-
-```python
-@respond_to('^admin$')
-@allow_only_direct_message() #only trigger by direct message, remove this line if you want call this in channel
-@allowed_users('Your username or email address here','user@email.com') # List of usernames or e-mails allowed
-@allowed_channels('allowed_channel_1','allowed_channel_2')  # List of allowed channels
-def users_access(message):
-    pass
-```
-Keep in mind the order matters! `@respond_to()` and `@listen_to()`must come before the "allowed" decorators.
+### Configure and run the bot
 
+Create an entrypoint file (or copy the one provided), that defines your Mattermost server and bot account settings and imports
+the desired modules.
 
-And add the plugins module to `PLUGINS` list of mmpy_bot settings, e.g. mmpy_bot_settings.py:
+Example `my_bot.py`:
 
 ```python
-PLUGINS = [
-    'mmpy_bot.plugins',
-    'devops.plugins',          # e.g. git submodule:  domain:devops-plugins.git
-    'programmers.plugins',     # e.g. python package: package_name.plugins
-    'frontend.plugins',        # e.g. project tree:   apps.bot.plugins
-]
+#!/usr/bin/env python
+
+from mmpy_bot import Bot, Settings
+from my_plugin import MyPlugin
+
+bot = Bot(
+    settings=Settings(
+        MATTERMOST_URL = "http://chat.example.com",
+        MATTERMOST_PORT = 443,
+        BOT_TOKEN = "a69155mvlsobcnqpfdceqihaa",
+        BOT_TEAM = "test",
+        SSL_VERIFY = True,
+    ),  # Either specify your settings here or as environment variables.
+    plugins=[MyPlugin()],  # Add your own plugins here.
+)
+bot.run()
 ```
-*For example you can separate git repositories with plugins on your team.*
 
+Set the executable bit on the entrypoint file (i.e. `chmod +x my_bot.py`) and start your bot from the command prompt. Now you can talk to your bot in your Mattermost client!
 
-If you are migrating from `Slack` to the `Mattermost`, and previously you are used `SlackBot`,
-you can use this battery without any problem. On most cases your plugins will be working properly
-if you are used standard API or with minimal modifications.
+In order to get the most out of your bot, you will need to write your own plugins. Refer to the [Plugins Documentation](https://mmpy-bot.readthedocs.io/en/latest/plugins.html) to get started.
diff --git a/docs/contributing.rst b/docs/contributing.rst
index 4e78f4b2..190b0af0 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -3,16 +3,129 @@
 Contributing
 ============
 
-If you wish to contribute, please add corresponding tests.
 
-Running tests::
+Setup your environment
+----------------------
 
-    make test
+We recommend using `venv <https://docs.python.org/3.8/library/venv.html>`_ to keep your development environment isolated from your base Python environment. It is part of Python by default.
 
-Checking coverage (requires ``coverage`` package)::
 
-    make coverage
+#. Clone the mmpy_bot repository, setup your virtual environment and install the requirements:
 
-Run tests for all python-django combinations ::
+    .. code-block:: bash
 
-    tox
+        $ git clone https://github.com/attzonko/mmpy_bot.git
+        $ cd mmpy_bot
+        $ python3 -m venv venv
+        $ source venv/bin/activate
+        $ pip install -e ".[dev]"
+
+#. Spin up the Mattermost container (Podman or Docker required):
+
+    .. code-block:: bash
+
+        $ podman-compose -f tests/integration_tests/docker-compose.yml up -d
+
+
+#. In order to run the bot, it is advised to use an entrypoint Python file which defines your Mattermost server and bot account settings,
+   as well as importing any custom plugins you may create. See the provided `entrypoint.py <https://github.com/attzonko/mmpy_bot/blob/master/entrypoint.py>`_  as a reference.
+   To run your bot inside a docker container (not necessary, you can also just run `python entrypoint.py`) you can use the provided `docker-compose file <https://github.com/attzonko/mmpy_bot/blob/master/docker-compose.yml>`_.
+
+
+Testing
+=======
+
+mmpy_bot develops all tests based on pytest. If you need to add your own tests and run tests, please install the dev requirements.
+
+    .. code-block:: bash
+
+        $ pip install -r dev-requirements.txt
+
+All the tests are put in `mmpy_bot\tests`.
+There are two test packages: :code:`unit_tests` and :code:`integration_tests`.
+
+Tests which can be performed by a single bot without requiring a server or interaction with other bots should be kept in the :code:`unit_tests` package.
+Tests that require interactions between bots on a mattermost server belong to the :code:`integration_tests` package.
+
+
+Adding unit tests
+-----------------
+
+There are multiple test modules inside unit_tests package, one for each module in the code.
+The naming convention of these modules is *modulename_test*.
+Inside each module, there will be several test functions with naming convention *test_methodname*, grouped into classes for each corresponding class in the code.
+If you need to add more unit tests, please consider following these conventions.
+
+
+Running the unit tests
+----------------------
+
+To run the unit tests (in parallel), simply execute:
+
+    .. code-block:: bash
+
+        $ pytest -n auto tests\unit_tests
+
+
+Adding integration tests
+------------------------
+
+The integration tests are run on the `jneeven:mattermost-bot-test` docker image, for which dockerfiles are provided in the `tests/intergration_tests` folder.
+The tests are defined as interactions between a bot (the responder) and a driver (the one sending test messages), which live inside the docker image.
+Their respective tokens are available in `tests/integration_tests/utils.py`, and the two bots are available as pytest fixtures so they can be easily re-used.
+Note that while the bot is also a fixture, it should not be used in any functions.
+It will simply be started whenever the integration tests are executed.
+
+An integration test might look like this (also have a look at the actual code in `tests/integration_tests/test_example_plugin.py`):
+
+    .. code-block:: python
+
+        from tests.integration_tests.utils import start_bot  # noqa, only imported so that the bot is started
+        from tests.integration_tests.utils import MAIN_BOT_ID, OFF_TOPIC_ID, RESPONSE_TIMEOUT, TEAM_ID
+        from tests.integration_tests.utils import driver as driver_fixture
+        from tests.integration_tests.utils import expect_reply
+
+        # Hacky workaround to import the fixture without linting errors
+        driver = driver_fixture
+
+        # Verifies that the bot is running and listening to this non-targeted message
+        def test_start(driver):
+            post = driver.create_post(OFF_TOPIC_ID, "starting integration tests!")
+            # Checks whether the bot has sent us the expected reply
+            assert expect_reply(driver, post)["message"] == "Bring it on!"
+
+In this test, the driver sends a message in the "off-topic" channel, and waits for the bot to reply 'Bring it on!'.
+If no reply occurs within a default response timeout (15 seconds by default, but this can be passed as an argument to `expect_reply`), an exception will be raised.
+The driver fixture is imported from the utils and can be re-used in every test function simply by adding it as a function argument.
+
+
+
+Running the integration_tests
+-----------------------------
+
+Running the integration_tests is easy: simply `cd` into `tests/integration_tests`, and run `docker-compose up -d` to start a local mattermost server.
+Then run `pytest -n auto .` to start the tests! For more info about the integration tests an the docker server, have a look at `tests/integration_tests/README.md`.
+
+Test coverage:
+--------------
+
+Install pytest-cov_:
+
+.. _pytest-cov: https://pypi.org/project/pytest-cov/
+
+    .. code-block:: bash
+
+        $ pip install pytest-cov
+
+Set necessary configuration as described above, and run:
+
+    .. code-block:: bash
+
+        $ py.test --cov=mmpy_bot tests\
+
+It automatically runs tests and measures code coverage of modules under mmpy_bot root dir.
+Using "--cov-report" parameter to write report into "cov_html" folder by html format.
+
+    .. code-block:: bash
+
+        py.test --cov-report html:logs\cov_html --cov=mmpy_bot tests\
diff --git a/docs/decorators.rst b/docs/decorators.rst
deleted file mode 100644
index 6f294088..00000000
--- a/docs/decorators.rst
+++ /dev/null
@@ -1,91 +0,0 @@
-.. _decorators:
-
-Built-in decorators
-===================
-
-
-Allowed users:
-
-.. code-block:: python
-
-    from mmpy_bot.utils import allowed_users
-    from mmpy_bot.bot import respond_to
-
-
-    @respond_to('^is-allowed$')
-    @allowed_users('admin', 'root', 'root@admin.com')
-    def access_allowed(message):
-        message.reply('Access is allowed')
-
-
-
-Direct messages:
-
-.. code-block:: python
-
-    from mmpy_bot.utils import allow_only_direct_message
-    from mmpy_bot.bot import respond_to
-
-
-    @respond_to('^direct-msg$')
-    @allow_only_direct_message()
-    def direct_msg(message):
-        message.reply('Message is direct')
-
-
-
-Actions just after plugin initilization:
-
-.. code-block:: python
-
-    from mmpy_bot.bot import at_start
-
-
-    @at_start
-    def hello(client):
-        client.connect_websocket()
-        client.ping()
-
-
-Real case
----------
-
-For example we have some users on our chat. We want allow some functionality
-to some users. We can create constants with allowed users on bot settings:
-
-.. code-block:: python
-
-    ADMIN_USERS = ['admin', 'root', 'root@admin.com']
-    SUPPORT_USERS = ['mike', 'nick', 'nick@nick-server.com']
-
-
-So now we can close access to some functions on plugins:
-
-.. code-block:: python
-
-    from mmpy_bot.utils import allow_only_direct_message
-    from mmpy_bot.utils import allowed_users
-    from mmpy_bot.bot import respond_to
-    from mmpy_bot import settings
-
-
-    @respond_to('^selfupdate')
-    @allow_only_direct_message()
-    @allowed_users(*settings.ADMIN_USERS)
-    def self_update(message):
-        # some code
-        message.reply('Update was done')
-
-
-    @respond_to('^smsto (.*)')
-    @allowed_users(*settings.SUPPORT_USERS)
-    def sms_to(message, name):
-        # some code
-        message.reply('Sms was sent to %s' % name)
-
-
-    @at_start
-    def hello(client):
-        team = client.api.get_team_by_name("TESTTEAM")
-        channel = client.api.get_channel_by_name("bot_test", team["id"])
-        client.channel_msg(channel["id"], "Hello, feels good to be alive!!")
diff --git a/docs/demo_installation.rst b/docs/demo_installation.rst
deleted file mode 100644
index af9db9ab..00000000
--- a/docs/demo_installation.rst
+++ /dev/null
@@ -1,35 +0,0 @@
-Demo installation
-=================
-
-Install OS specific packages::
-
-    # Ubuntu
-    sudo apt-get install python-virtualenv python-pip
-
-    # CentOS
-    sudo yum install python-virtualenv python-pip
-
-    # OS X
-    brew install python
-
-Create project directory and virtual environment::
-
-    mkdir ~/my-bot && cd ~/my-bot
-    virtualenv venv
-    . venv/bin/activate
-
-Install stable package from PyPi::
-
-    pip install mmpy_bot
-
-Create settings file `mmpy_bot_settings.py` with the following lines::
-
-    DEBUG = True
-    BOT_URL = 'http://mm.example.com:8065/api/v4'
-    BOT_LOGIN = '<BOT_EMAIL>'
-    BOT_PASSWORD = '<BOT_PASSWORD>'
-    BOT_TEAM = '<TEAM>'
-
-Run your bot by following command::
-
-    MATTERMOST_BOT_SETTINGS_MODULE=mmpy_bot_settings mmpy_bot
diff --git a/docs/deploy.rst b/docs/deploy.rst
deleted file mode 100644
index 0f486ad8..00000000
--- a/docs/deploy.rst
+++ /dev/null
@@ -1,87 +0,0 @@
-Deploy
-======
-
-Ubuntu 14.04
-------------
-
-Install supervisor and virtualenv::
-
-    $ sudo apt-get update
-    $ sudo apt-get install supervisor python-virtualenv git
-
-
-Add ``matterbot`` user::
-
-    $ useradd --shell /bin/bash -m -d /home/matterbot matterbot
-
-
-Login as ``matterbot``::
-
-    sudo -i -u matterbot
-
-
-Clone your project (before create git repository with ``settings.py``)::
-
-    $ git clone https://github.com/USER/REPO.git ~/mybot
-
-
-Create virtual environment::
-
-    $ cd ~/
-    $ virtualenv mm-env
-
-
-Install project requirements::
-
-    $ cd ~/mybot
-    $ . ~/mm-env/bin/activate
-    $ pip install mmpy_bot
-    $ pip install -r requirements.txt
-
-
-Logout::
-
-    $ exit
-
-
-Configure supervisor::
-
-    $ sudo vi /etc/supervisor/conf.d/matterbot.conf
-
-Add following config::
-
-    [program:matterbot]
-    command=/home/matterbot/mm-env/bin/mmpy_bot
-    user=matterbot
-    directory=/home/matterbot/mybot
-    # should be a real settings file on MATTERMOST_BOT_SETTINGS_MODULE
-    environment=MATTERMOST_BOT_SETTINGS_MODULE="settings"
-    redirect_stderr=true
-    stdout_logfile=/var/log/matterbot.log
-    numprocs=1
-    autostart=true
-    autorestart=true
-    startretries=25
-    logfile_maxbytes=50MB
-    logfile_backups=3
-    killasgroup=true
-    stopasgroup=true
-    priority=998
-
-
-Restart supervisor::
-
-    $ service supervisor restart
-
-
-Check status::
-
-    $ supervisorctl status
-
-
-Track bot logs::
-
-    $ tail -f /var/log/matterbot.log
-
-
-Enjoy:)
diff --git a/docs/dev_install.rst b/docs/dev_install.rst
deleted file mode 100644
index 7bb661c4..00000000
--- a/docs/dev_install.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-Installation for development
-============================
-
-.. code-block:: bash
-
-    $ sudo apt-get install virtualenvwrapper
-    $ mkvirtualenv mmpy_bot
-    $ git clone https://github.com/attzonko/mmpy_bot.git
-    $ cd mmpy_bot
-    $ python setup.py develop
-    $ pip install -r requirements.txt
-    $ pip install -r docs/requirements.txt
-    $ touch local_settings.py               # configure your local settings
-    $ mmpy_bot                              # run bot
-
-
-.. code-block:: python
-
-    >>> import mmpy_bot
-    >>> print(mmpy_bot.__version__)
diff --git a/docs/getting_started.rst b/docs/getting_started.rst
new file mode 100644
index 00000000..b132e17c
--- /dev/null
+++ b/docs/getting_started.rst
@@ -0,0 +1,93 @@
+.. _getting-started:
+
+Getting Started
+=================
+
+Compatibility
+-------------
+* Python: 2.6, 3.5+ (for version <= 1.3)
+* Python: 3.8+ (for version >= 2.0)
+* Mattermost: 4.0+
+
+Installation
+------------
+
+PyPi (pip)
+##########
+The recommended method to install `mmpy_bot` is via pip:
+
+.. code-block:: python
+
+    pip install -U mmpy_bot
+
+Git Repo
+########
+#. Clone the git repository:
+
+    .. code-block:: bash
+
+        $ git clone git@github.com:attzonko/mmpy_bot.git
+
+#. Install requirements:
+
+    .. code-block:: bash
+
+        $ pip install -r requirements.txt
+
+Running the bot
+---------------
+We recommend creating an `entrypoint` file for executing the bot, which will look something like this:
+
+    .. code-block:: python
+
+        #!/usr/bin/env python
+
+        from mmpy_bot import Bot, Settings, ExamplePlugin, WebhookExample
+        from my_plugin import MyPlugin
+
+        bot = Bot(
+            settings=Settings(
+                MATTERMOST_URL = "http://<mattermost_server_url>",
+                MATTERMOST_PORT = 443,
+                BOT_TOKEN = "<your_bot_token>",
+                BOT_TEAM = "<team_name>",
+                SSL_VERIFY = True,
+            ),
+            plugins=[ExamplePlugin(), WebhookExample()],
+        )
+        bot.run()
+
+You can then simply launch the bot with `python entrypoint.py`.
+For more information on configuring bot settings and plugins, please see `settings.py <https://github.com/attzonko/mmpy_bot/blob/master/mmpy_bot/settings.py>`_ and the :ref:`plugins <plugins>` page.
+
+Container
+#########
+A container image is available at `jneeven/mmpy_bot <https://hub.docker.com/r/jneeven/mmpy_bot>`_.
+Using your preferred container management software (Docker/Podman), you can pull the image and run it using the following steps:
+
+#. Pull the image from the Docker repository:
+
+    .. code-block:: bash
+
+        $ podman pull docker.io/jneeven/mmpy_bot
+
+#. Run the container with defined environment variables:
+
+    .. code-block:: bash
+
+        $ podman run -d --name=mmpy_bot --network=host -e MATTERMOST_URL=<mattermost_server_url> -e MATTERMOST_PORT=<mattermost_server_port> -e BOT_TOKEN=<bot_token> docker.io/jneeven/mmpy_bot
+
+You can also find an example `docker-compose.yml` file `here <https://github.com/attzonko/mmpy_bot/blob/master/docker-compose.yml>`_.
+
+Customizing your bot
+####################
+Getting your bot running is only the beginning. The real fun begins with writing plugins to get it functioning exactly how you want it! Head on over to the :ref:`plugins <plugins>` page to get started.
+
+Fetch mmpy_bot version
+####################
+To check your installed version of `mmpy_bot`, simply open a Python interpreter and run the following commands:
+
+    .. code-block:: python
+
+        import mmpy_bot
+        print(mmpy_bot.__version__)
diff --git a/docs/index.rst b/docs/index.rst
index 7eae9dc2..e4281d55 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -9,16 +9,9 @@ Contents
 .. toctree::
   :maxdepth: 1
 
-  installation
-  usage
-  settings
+  getting_started
   plugins
-  deploy
-  use_cases
-  decorators
-  demo_installation
-  dev_install
-  testing
+  migration
   contributing
   authors
 
diff --git a/docs/installation.rst b/docs/installation.rst
deleted file mode 100644
index ea44e2fd..00000000
--- a/docs/installation.rst
+++ /dev/null
@@ -1,19 +0,0 @@
-Installation
-============
-
-Compatibility
--------------
-* Python: 2.6, 3.5+
-* Mattermost: 4.0+
-
-
-Recommended way to install is via pip::
-
-  pip install -U mmpy_bot
-
-
-Check `mmpy_bot` version::
-
-    import mmpy_bot
-    print(mmpy_bot.__version__)
-
diff --git a/docs/plugins.rst b/docs/plugins.rst
index ae6356f4..ae7cc3a2 100644
--- a/docs/plugins.rst
+++ b/docs/plugins.rst
@@ -1,240 +1,245 @@
 Plugins
 =======
 
+A chat bot is meaningless unless you can extend/customize it to fit your own use cases, which can be achieved through custom plugins.
 
-Plugins
--------
+Writing your first plugin
+-------------------------
 
-A chat bot is meaningless unless you can extend/customize it to fit your own use cases.
+#. To demonstrate how easy it is to create a plugin for `mmpy_bot`, let's write a basic plugin and run it. Start with an empty Python file and import these three `mmpy_bot` modules:
 
-To write a new plugin, simply create a function decorated by ``mmpy_bot.bot.respond_to`` or ``mmpy_bot.bot.listen_to``:
+    .. code-block:: python
 
-- A function decorated with ``respond_to`` is called when a message matching the pattern is sent to the bot (direct message or @botname in a channel/group chat)
-- A function decorated with ``listen_to`` is called when a message matching the pattern is sent on a channel/group chat (not directly sent to the bot)
-- A function decorated with ``at_start`` is called as soon as the plugin is initialized (when the bot starts)
+        from mmpy_bot import Plugin, listen_to
+        from mmpy_bot import Message
 
-.. code-block:: python
+#. Now create a Class with the name of your plugin, subclassing `Plugin`:
 
-    import re
+    .. code-block:: python
 
-    from mmpy_bot.bot import listen_to
-    from mmpy_bot.bot import respond_to
+        class MyPlugin(Plugin):
 
+#. Now we can write the methods that control how the bot will respond to certain messages. Let's start with a basic one that will simply trigger a response from the bot:
 
-    @respond_to('hi', re.IGNORECASE)
-    def hi(message):
-        message.reply('I can understand hi or HI!')
+    .. code-block:: python
 
+        @listen_to("wake up")
+        async def wake_up(self, message: Message):
+            self.driver.reply_to(message, "I'm awake!")
 
-    @respond_to('I love you')
-    def love(message):
-        message.reply('I love you too!')
+    In the above code block, the `@listen_to` decorator tells the bot to listen on any channel for the string "wake up", and respond with "I'm awake!".
 
+#. Save your plugin file and open a fresh Python file which will be the entrypoint to start the bot and include your plugin:
 
-    @listen_to('Can someone help me?')
-    def help_me(message):
-        # Message is replied to the sender (prefixed with @user)
-        message.reply('Yes, I can!')
+    .. code-block:: python
 
-        # Message is sent on the channel
-        # message.send('I can help everybody!')
+        #!/usr/bin/env python
 
-    @at_start
-    def hello(client):
-        # Note that contrary to respond_to and listen_to @at_start
-        # receives a client object and not a message object
-        team = client.api.get_team_by_name("TESTTEAM")
-        channel = client.api.get_channel_by_name("bot_test", team["id"])
-        client.channel_msg(channel["id"], "Hello, feels good to be alive!!")
+        from mmpy_bot import Bot, Settings
+        from my_plugin import MyPlugin
 
-To extract params from the message, you can use regular expression:
+        bot = Bot(
+            settings=Settings(
+                MATTERMOST_URL = "http://127.0.0.1",
+                MATTERMOST_PORT = 8065,
+                BOT_TOKEN = "<your_bot_token>",
+                BOT_TEAM = "<team_name>",
+                SSL_VERIFY = False,
+            ),  # Either specify your settings here or as environment variables.
+            plugins=[MyPlugin()],  # Add your own plugins here.
+        )
+        bot.run()
 
-.. code-block:: python
+    The above code assumes your plugin is in the same directory as the entrypoint file. Also be sure to set the correct settings for your Mattermost server and bot account.
+
+#. Save your entrypoint file and run it from the command prompt:
 
-    from mmpy_bot.bot import respond_to
+    .. code-block:: bash
 
+        $ ./my_bot.py
 
-    @respond_to('Give me (.*)')
-    def give_me(message, something):
-        message.reply('Here is %s' % something)
+If everything went as planned you can now start your bot, send the message "wake up" and expect the appropriate reply.
 
+Further configuration
+---------------------
 
-If you would like to have a command like 'stats' and 'stats start_date end_date', you can create reg ex like so:
+The below code snippets provide an insight into the functionality that can be added to the bot. For more in-depth examples,
+please refer to [`./plugins/example.py`](https://github.com/attzonko/mmpy_bot/blob/master/mmpy_bot/plugins/example.py) and [`./plugins/webhook_example.py`](https://github.com/attzonko/mmpy_bot/blob/master/mmpy_bot/plugins/webhook_example.py).
+
+Implementing regular expression
+-------------------------------
 
 .. code-block:: python
 
-    from mmpy_bot.bot import respond_to
     import re
 
+    @listen_to('hi', re.IGNORECASE)
+    def hi(message):
+        message.reply('I can understand hi or HI!')
 
-    @respond_to('stat$', re.IGNORECASE)
-    @respond_to('stat (.*) (.*)', re.IGNORECASE)
-    def stats(message, start_date=None, end_date=None):
-        pass
-
+    @listen_to('Give me (.*)')
+    async def give_me(self, message, something):
+        self.driver.reply_to(message, 'Here is %s' % something)
 
 
-And add the plugins module to ``PLUGINS`` list of mmpy_bot settings, e.g. ``mmpy_bot_settings.py``:
+Only accept messages that mention the bot
+----------------------------------
 
-.. code-block:: python
+If you want the bot to only respond to messages containing a mention (e.g. "hey @bot_name !"), you can use the `needs_mention` flag.
+Note that this will also trigger if you send the bot a direct message without mentioning its name!
+    .. code-block:: python
 
-    PLUGINS = [
-        'mmpy_bot.plugins',
-        'devops.plugins',          # e.g. git submodule: domain:devops-plugins.git
-        'programmers.plugins',     # e.g. python package: package_name.plugins
-        'frontend.plugins',        # e.g. project tree: apps.bot.plugins
-    ]
+        @listen_to("hey", needs_mention=True)
+        async def hey(self, message: Message):
+            self.driver.reply_to(message, "Hi! You mentioned me?")
 
-*For example you can separate git repositories with plugins on your team.*
 
+Only accept direct messages
+----------------------------------
 
-Attachment Support
-------------------
+Using `direct_only=True`, the bot will only respond if you send it a direct message.
 
-.. code-block:: python
+    .. code-block:: python
 
-    from mmpy_bot.bot import respond_to
-
-
-    @respond_to('webapi')
-    def webapi_reply(message):
-        attachments = [{
-            'fallback': 'Fallback text',
-            'author_name': 'Author',
-            'author_link': 'http://www.github.com',
-            'text': 'Some text here ...',
-            'color': '#59afe1'
-        }]
-        message.reply_webapi(
-            'Attachments example', attachments,
-            username='Mattermost-Bot',
-            icon_url='https://goo.gl/OF4DBq',
-        )
-        # Optional: Send message to specified channel
-        # message.send_webapi('', attachments, channel_id=message.channel)
+        @listen_to("hey", direct_only=True)
+        async def hey(self, message: Message):
+            self.driver.reply_to(message, "Hi! This is a private conversation.")
 
 
-File Support
-------------------
+Restrict messages to specific users
+----------------------------------
 
-.. code-block:: python
+    .. code-block:: python
 
-    from mmpy_bot.bot import respond_to
+        @listen_to("^admin$", direct_only=True, allowed_users=["admin", "root"])
+        async def users_access(self, message: Message):
+            """Will only trigger if the username of the sender is 'admin' or 'root'."""
+            self.driver.reply_to(message, "Access allowed!")
 
+Click support
+-------------
+    `mmpy_bot` now supports [click](https://click.palletsprojects.com/en/7.x/) commands, so you can build a robust CLI-like experience if you need it.
+    The example below registers a `hello_click` command that takes a positional argument, a keyword argument and a toggleable flag, which are automatically converted to the correct type.
+    For example, it can be called with `hello_click my_argument --keyword-arg=3 -f` and will parse the arguments accordingly.
+    A nice benefit of `click` commands is that they also automatically generate nicely formatted help strings.
+    Try sending "help" to the `ExamplePlugin` to see what it looks like!
 
-    @respond_to('files')
-    def message_with_file(message):
-        # upload_file() can upload only one file at a time
-        # If you have several files to upload, you need call this function several times.
-        file = open('test.txt', 'w+')
-        result = message.upload_file(file)
-        file.close()
-        if 'file_infos' not in result:
-            message.reply('upload file error')
-        file_id = result['file_infos'][0]['id']
-        # file_id need convert to array
-        message.reply('hello', [file_id])
+    .. code-block:: python
 
+        @listen_to("hello_click", needs_mention=True)
+        @click.command(help="An example click command with various arguments.")
+        @click.argument("POSITIONAL_ARG", type=str)
+        @click.option("--keyword-arg", type=float, default=5.0, help="A keyword arg.")
+        @click.option("-f", "--flag", is_flag=True, help="Can be toggled.")
+        def hello_click(
+            self, message: Message, positional_arg: str, keyword_arg: float, flag: bool
+        ):
+            response = (
+                "Received the following arguments:\n"
+                f"- positional_arg: {positional_arg}\n"
+                f"- keyword_arg: {keyword_arg}\n"
+                f"- flag: {flag}\n"
+            )
+            self.driver.reply_to(message, response)
 
-Webhook Support
+File upload
 ------------------
 
-You can specify ``webhook_id`` at ``reply_webapi`` and ``send_webapi`` method call.
-The ``webhook_id`` can be generated and acquired from Mattermost Contol Panel.
-It is also possible to send messages to different teams/channels in the same message handler, as long as you got needed webhook_ids.
-
 .. code-block:: python
 
-    from mmpy_bot.bot import respond_to
-
-    @respond_to('reply')
-    def reply(message):
-        attachments = [{
-            'fallback': 'Fallback text',
-            'author_name': 'Author',
-            'author_link': 'http://www.github.com',
-            'text': 'Some text here ...',
-            'color': '#59afe1'
-        }]
-        message.reply_webapi(
-            'Response to team I got message from.',
-            attachments,
-            webhook_id='p7tuwghy37r63jp4nf3tsopque',
-        )
-        # Optional: Send message to specified channel
-        message.send_webapi(
-            'Response to another team.',
-            attachments,
-            webhook_id='aib7mnahsfy5zrt6tf3ycbghic',
-        )
+    @listen_to("^hello_file$", re.IGNORECASE, needs_mention=True)
+    async def hello_file(self, message: Message):
+        """Responds by uploading a text file."""
+        file = Path("/tmp/hello.txt")
+        file.write_text("Hello from this file!")
+        self.driver.reply_to(message, "Here you go", file_paths=[file])
+
 
-You can also set ``WEBHOOK_ID`` in settings.py or local_settings.py.
-The ``WEBHOOK_ID`` will serve as default webhook id to send message via webhook API if ``webhook_id`` is not given at ``reply_webapi`` or ``send_webapi`` method call.
+Plugin startup and shutdown
+---------------------------
+The `Plugin` class comes with an `on_start` and `on_stop` function, which will be called when the bot starts up or shuts down.
+They can be used as follows:
 
 .. code-block:: python
 
-    import os
+    def on_start(self):
+        """Notifies some channel that the bot is now running."""
+        self.driver.create_post(channel_id="some_channel_id", message="The bot just started running!")
 
-    PLUGINS = [
-        'my_plugins',
-    ]
+    def on_stop(self):
+        """Notifies some channel that the bot is shutting down."""
+        self.driver.create_post(channel_id="some_channel_id", message="I'll be right back!")
 
-    BOT_URL = os.environ.get("BOT_URL", 'http://your_server_dn/api/v4')
-    BOT_LOGIN = os.environ.get("DRIVERBOT_LOGIN", 'bot@nature.ee.ncku.edu.tw')
-    BOT_NAME = os.environ.get("DRIVERBOT_NAME", 'bot')
-    BOT_PASSWORD = os.environ.get("DRIVERBOT_PASSWORD", 'passwd')
 
-    # this team name should be the same as in driver_settings
-    BOT_TEAM = os.environ.get("BOT_TEAM", 'test_team')
+Webhook listener
+---------------------
+If you want to interact with your bot not only through chat messages but also through web requests (for example to implement an `interactive dialog <https://docs.mattermost.com/developer/interactive-dialogs.html>`_), you can use enable the built-in `WebHookServer`.
+In your `Settings`, make sure to set `WEBHOOK_HOST_ENABLED=True` and provide a value for `WEBHOOK_HOST_URL` and `WEBHOOK_HOST_PORT` (see `settings.py <https://github.com/attzonko/mmpy_bot/blob/master/mmpy_bot/settings.py>`_ for more info).
+Then, on your custom `Plugin` you can create a function like this:
+
+.. code-block:: python
+    from mmpy_bot import listen_webhook
 
-    # default public channel name
-    BOT_CHANNEL = os.environ.get("BOT_CHANNEL", 'off-topic')
+    @listen_webhook("ping")
+    async def ping_listener(self, event: WebHookEvent):
+        """Listens to post requests to '<server_url>/hooks/ping' and posts a message in
+        the channel specified in the request body."""
 
-    # a private channel in BOT_TEAM
-    BOT_PRIVATE_CHANNEL = os.environ.get("BOT_PRIVATE_CHANNEL", 'test')
+        self.driver.create_post(
+            event.body["channel_id"], f"Webhook {event.webhook_id} triggered!"
+        )
 
-    SSL_VERIFY = True
+And if you want to send a web response back to the incoming HTTP POST request, you can use `Driver.respond_to_web`:
 
-    # example webhook_id of test_team/off-topic
-    WEBHOOK_ID = 'p7tuwghy37r63jp4nf3tsopque'
+.. code-block:: python
+    @listen_webhook("ping")
+    async def ping_listener(self, event: WebHookEvent):
+        # Respond to the web request rather than posting a message.
+        self.driver.respond_to_web(
+            event,
+            {
+                # You can add any kind of JSON-serializable data here
+                "message": "hello!",
+            },
+        )
 
-If neither ``webhook_id`` (as parameter) nor ``WEBHOOK_ID`` (in settings.py) is given, the message will not be send, and a warning will be added to logging.
+For more information about the `WebHookServer` and its possibilities, take a look at the `WebHookExample  plugin <https://github.com/attzonko/mmpy_bot/blob/master/mmpy_bot/plugins/webhook_example.py>`_.
 
 
-Job Scheduling
+Job scheduling
 --------------
 
-mmpy_bot integrates `schedule 
+mmpy_bot integrates `schedule
 <https://github.com/dbader/schedule/>`_ to provide in-process job scheduling.
 
-With `schedule 
+With `schedule
 <https://github.com/dbader/schedule/>`_, we can put periodic jobs into waiting queue like this:
 
 .. code-block:: python
 
-    import re
-    from datetime import datetime
-    from mmpy_bot.bot import respond_to
-    from mmpy_bot.scheduler import schedule
-
-
-    @respond_to('reply \"(.*)\" every (.*) seconds', re.IGNORECASE)
-    def reply_every_seconds(message, content, seconds):
-        schedule.every(int(seconds)).seconds.do(message.reply, content)
+    @listen_to("^schedule every ([0-9]+)$", re.IGNORECASE, needs_mention=True)
+    def schedule_every(self, message: Message, seconds: int):
+        """Schedules a reply every x seconds. Use the `cancel jobs` command to stop.
 
+        Arguments:
+        - seconds (int): number of seconds between each reply.
+        """
+        schedule.every(int(seconds)).seconds.do(
+            self.driver.reply_to, message, f"Scheduled message every {seconds} seconds!"
+        )
 
-    @respond_to('cancel jobs', re.IGNORECASE)
+    @listen_to('cancel jobs', re.IGNORECASE)
     def cancel_jobs(message):
         schedule.clear()
-        message.reply('all jobs canceled.')
+        self.driver.reply_to('All jobs cancelled.')
 
-The `schedule 
-<https://github.com/dbader/schedule/>`_ itself provide human-readable APIs to schedule jobs. Check out `schedule.readthedocs.io <https://schedule.readthedocs.io/>`_ for more usage examples.
+The `schedule
+<https://github.com/dbader/schedule/>`_ package provides human-readable APIs to schedule jobs. Check out `schedule.readthedocs.io <https://schedule.readthedocs.io/>`_ for more usage examples.
 
-`schedule 
+`schedule
 <https://github.com/dbader/schedule/>`_ is designed for periodic jobs.
-In order to support one-time-only jobs, mmpy_bot has a monkey-patching on integrated 
-`schedule 
+In order to support one-time-only jobs, mmpy_bot has a monkey-patching on integrated
+`schedule
 <https://github.com/dbader/schedule/>`_ package.
 
 We can schedule a one-time-only job by `schedule.once` method.
@@ -242,19 +247,3 @@ You should notice that this method takes a datetime object, which is different f
 
 The following code example uses `schedule.once` to schedule a job.
 This job will be trigger at `t_time`.
-
-.. code-block:: python
-
-    import re
-    from datetime import datetime
-    from mmpy_bot.bot import respond_to
-    from mmpy_bot.scheduler import schedule
-
-
-    @respond_to('reply \"(.*)\" at (.*)', re.IGNORECASE)
-    def reply_specific_time(message, content, trigger_time):
-        t_time = datetime.strptime(trigger_time, '%b-%d-%Y_%H:%M:%S')
-        schedule.once(t_time).do(message.reply, content)
-
-All jobs added will be triggered periodically. 
-The trigger period (default 5 seconds) can be configured by `JOB_TRIGGER_PERIOD` in settings.py or local_settings.py.
diff --git a/docs/settings.rst b/docs/settings.rst
deleted file mode 100644
index 4d2f8e3c..00000000
--- a/docs/settings.rst
+++ /dev/null
@@ -1,72 +0,0 @@
-.. _settings:
-
-Settings
-========
-
-``mmpy_bot`` has some configuration:
-
-.. code-block:: python
-
-    DEBUG = False
-
-    PLUGINS = [
-        'mmpy_bot.plugins',
-    ]
-
-    # Docs + regexp or docs string only
-    PLUGINS_ONLY_DOC_STRING = False
-
-    # Basic configuration
-    BOT_URL = 'http://mm.example.com/api/v4'
-    BOT_LOGIN = 'bot@example.com'
-    BOT_PASSWORD = None
-    BOT_TEAM = 'devops'
-
-    # default public channel name
-    BOT_CHANNEL = 'off-topic'
-
-    # a private channel in BOT_TEAM
-    BOT_PRIVATE_CHANNEL = 'test'
-
-    # login with SSL verification
-    SSL_VERIFY = True
-
-    # the dafult webhook_id of message to reply via webhook
-    # this is optional
-    #WEBHOOK_ID = 'p7tuwghy37r63jp4nf3tsopque'
-
-    # Ignore broadcast message
-    IGNORE_NOTIFIES = ['@channel', '@all']
-    # Ignore message from specified senders (case-insensitive), ex: IGNORE_USERS = ['john', 'Mary']
-    IGNORE_USERS = []
-
-    # Threads num
-    WORKERS_NUM = 10
-
-    '''
-    # Custom default reply module
-
-    Example:
-    filename:
-        my_default_reply.py
-    code:
-        def default_reply(dispatcher, raw_msg):
-            dispatcher._client.channel_msg(
-                raw_msg['channel_id'], dispatcher.get_message(raw_msg)
-            )
-    settings:
-        DEFAULT_REPLY_MODULE = 'my_default_reply'
-    '''
-    DEFAULT_REPLY_MODULE = None
-
-    # or simple string for default answer
-    DEFAULT_REPLY = None
-
-    '''
-    If you use Mattermost Web API to send messages (with send_webapi()
-    or reply_webapi()), you can customize the bot logo by providing Icon or Emoji.
-    If you use Mattermost API to send messages (with send() or reply()),
-    the used icon comes from bot settings and Icon or Emoji has no effect.
-    '''
-    BOT_ICON = 'http://lorempixel.com/64/64/abstract/7/'
-    BOT_EMOJI = ':godmode:'
diff --git a/docs/usage.rst b/docs/usage.rst
deleted file mode 100644
index bb777d0d..00000000
--- a/docs/usage.rst
+++ /dev/null
@@ -1,87 +0,0 @@
-Usage
-=====
-
-.. _basic:
-
-Basic
------
-
-Register a new user on Mattermost. Copy the email/password/team and URL into your ``mmpy_bot_settings.py`` file::
-
-    BOT_URL = 'http://<mm.example.com>/api/v4'  # with 'http://' and with '/api/v4' path
-    BOT_LOGIN = '<bot-email-address>'
-    BOT_PASSWORD = '<bot-password>'
-    BOT_TEAM = '<your-team>'
-
-
-
-Run the bot::
-
-    $ MATTERMOST_BOT_SETTINGS_MODULE=mmpy_bot_settings mmpy_bot
-
-
-Structuring Your Basic Bot with Plugins
----------------------------------------
-
-If you simply want to add some plugins (``listen_to``, ``respond_to`` handlers), 
-you can organize the bot code files under ``~/opt/your_bot/`` like this:
-
-::
-
-    ~/opt/your_bot/
-        my_plugins/
-            __init__.py
-            intro.py
-            <more py modules>
-        mmpy_bot_settings.py
-
-where ``intro.py`` is the handler module.
-
-
-Integration with Django
------------------------
-
-Set ``bot_settings`` in your project, after which you can create a ``django`` command::
-
-    import logging
-    import sys
-
-    from django.core.management.base import BaseCommand
-    from django.conf import settings
-
-    from mmpy_bot import bot, settings
-
-
-    class Command(BaseCommand):
-
-        def handle(self, **options):
-
-            logging.basicConfig(**{
-                'format': '[%(asctime)s] %(message)s',
-                'datefmt': '%m/%d/%Y %H:%M:%S',
-                'level': logging.DEBUG if settings.DEBUG else logging.INFO,
-                'stream': sys.stdout,
-            })
-
-            try:
-                b = bot.Bot()
-                b.run()
-            except KeyboardInterrupt:
-                pass
-
-
-Modify ``manage.py``::
-
-    #!/usr/bin/env python
-
-    import os
-    import sys
-
-
-    if __name__ == "__main__":
-        os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
-        os.environ.setdefault("MATTERMOST_BOT_SETTINGS_MODULE", "project.mmpy_bot_settings")
-
-        from django.core.management import execute_from_command_line
-
-        execute_from_command_line(sys.argv)
diff --git a/docs/use_cases.rst b/docs/use_cases.rst
deleted file mode 100644
index 5d75c5cb..00000000
--- a/docs/use_cases.rst
+++ /dev/null
@@ -1,90 +0,0 @@
-Use cases
-=========
-
-Standard cases for web organizations.
-
-
-Support | QA
-------------
-
-``callto John|Alex|All Your text ...`` - Emergency call
-
-``smsto John|Alex|All Your text ...`` - Emergency sms
-
-``task|bug|feature @username <low|normal|default|high|urgent> Task subject ...\nDescription`` - create task on bug tracker/etc
-
-``contact info <@username>`` - Show everyone's/username emergency contact info
-
-``remind me to TEXT at|on|in TIME`` - Set a reminder for a thing, at a time
-
-``remind @user1,@user2 to TEXT at|on|in TIME`` - Set a reminder for a thing, at a time for somebody else
-
-
-Development
------------
-
-**Standard**
-
-``build VERSION`` - Run build
-
-``test CURRENT|COMMIT`` - Run tests
-
-
-**Stage servers**
-
-``ls`` - List all available staging servers
-
-``make COMMIT <fake> or <-1h|d>`` - Create stage server. optional: hour|day ago
-
-``rm ID`` - Remove server by ID
-
-
-DevOps
-------
-
-**Standard**
-
-``maintenance on|off`` - Show maintenance page on website
-
-
-**AWS**
-
-``ls ami server1|server2|all`` - Show aws AMI's
-
-``make ami server1|server2`` - Create a new ami
-
-``upgrade|downgrade server1|server2|all <ami>`` - upgrade|downgrade AMI's
-
-``stop upgrade|downgrade`` - Stop upgrade|downgrade
-
-
-**Distributed version control system**
-
-``update|rollback server1|server2|all <commit>`` - Update code on servers
-
-``stop update|rollback`` - Stop update|rollback
-
-
-**Configuration management system**
-
-``cms update repo`` - Update own repository (such git pull)
-
-``cms update server1|sever2|all`` - Run server provision (e.g. puppet/chef/ansible)
-
-
-Internal commands for Bot
--------------------------
-
-``version`` - Get current revision and matterbot version
-
-``selfupdate`` - Update bot and plugins from your CVS repo
-
-``busy`` - Get num of busy workers. Used for monitoring and for check before update
-
-``uptime`` - MatterBot uptime
-
-``ping`` - Ping bot and waiting ``pong``
-
-
-
-Everything is limited only by your imagination :)


Test patch:
diff --git a/docs/testing.rst b/docs/testing.rst
deleted file mode 100644
index 1ccd0cd3..00000000
--- a/docs/testing.rst
+++ /dev/null
@@ -1,161 +0,0 @@
-.. _testing:
-
-
-Testing
-=======
-
-mmpy_bot develops all tests based on pytest. If you need to add your own tests and run tests, please install pytest first.
-
-.. code-block:: bash
-
-	$ pip install -U pytest
-
-All the tests are put in `mmpy_bot\tests`.
-There are two test packages: :code:`unit_tests` and :code:`behavior_tests`.
-
-Tests which can be performed by a single bot without mattermost server or other bots should be kept in :code:`unit_tests` package.
-Other tests requiring interactions between bots on mattermost server belong to :code:`behavior_tests` package.
-
-
-Add unit tests
---------------
-
-There are multiple test modules inside unit_tests package.
-Each module collects tests of specific mmpy_bot class or module.
-The naming convention of these modules is *test_classname* or *test_modulename*.
-Inside each module, there will be several test functions with naming convention *test_functionname* or *test_methodname*.
-Each test function performs unit test against a specific class or module.
-If you need to add more unit tests, please consider following these conventions.
-
-
-Run unit tests
---------------
-
-To run unit tests, simply:
-
-.. code-block:: bash
-
-	$ pytest tests\unit_tests
-
-
-Add behavior tests
-------------------
-
-The behavior tests are done by interactions between a DriverBot (driver) and a TestBot (responder).
-You can find these two bots in package :code:`tests.behavior_tests.bots` .
-These two bots will be instantiated as pytext fixture in :code:`tests.behavior_tests.fixture`, and be used in various behavior tests.
-
-Relevant behavior tests should be collected inside the same module, following the naming convention *test_behaviorset*. For example, we have :code:`tests.behavior_tests.test_conversation` to test general bot conversation.
-The conversation was made by default :code:`mmpy_bot.plugins` .
-
-Each behavior test might look like this:
-
-.. code-block:: python
-
-	from tests.behavior_tests.fixture import driver
-
-	def test_bot_respond_to_simple_message(driver):
-	    driver.send_direct_message('hello')
-	    driver.wait_for_bot_direct_message('hello sender!')
-
-This is a simple hello test. 
-The driver is imported from fixture, and send into test function.
-In the test, firstly the driver sends a direct message 'hello', and waits for response 'hello sender!'.
-If the response does not show up in 10 seconds (default), an *AssertionError* will be raised.
-
-For more message sending methods and options, please checkout :code:`tests.behavior_tests.bots.driver.py`. 
-
-
-Run behavior tests
-------------------
-
-To perform behavior tests, some settings are necessary for bots to login to mattermost server for test.
-
-1. Set up a mattermost server for test
-2. Create two user accounts for bots, e.g. 'driverbot' and 'testbot'
-3. Create a team, e.g. 'test-team', and add 'driverbot' and 'testbot' into the team
-4. Make sure the default public channel 'off-topic' exists
-5. Create a private channel, e.g. 'test', in team 'test-team', and add 'driverbot' and 'testbot' into the private channel
-
-Optionally, if you like to test webhooks and other behaviors which requires admin privilege, please also assign 'drivebot' ADMIN privilege on your testing server, and set 
-
-.. code-block:: python
-
-	config.pytest_config.DRIVER_ADMIN_PRIVILEGE = True
-
-If :code:`DRIVER_ADMIN_PRIVILEGE` is not set True, relevant tests will be skipped.
-
-Set these environment variables (replace set command depending on your OS):
-
-.. code-block:: bash
-
-	$ set BOT_URL = 'http://SERVER_HOST_DN/api/v4'
-	$ set DRIVERBOT_LOGIN = 'driverbot@mail'
-	$ set DRIVERBOT_NAME = 'driverbot'
-	$ set DRIVERBOT_PASSWORD = 'driverbot_password'
-	$ set TESTBOT_LOGIN = 'testbot@mail'
-	$ set TESTBOT_NAME = 'testbot_name'
-	$ set TESTBOT_PASSWORD = 'testbot_password'
-	$ set BOT_TEAM = 'test-team'
-	$ set BOT_CHANNEL = 'off-topic'
-	$ set BOT_PRIVATE_CHANNEL = 'test'
-
-Then you should be ready to run behavior tests:
-
-.. code-block:: bash
-
-	$ pytest tests\behavior_tests
-
-
-Run all the tests:
-------------------
-
-Set environment variables needed for behavior tests as mentioned above.
-
-.. code-block:: bash
-
-	$ pytest
-
-
-Test coverage:
---------------
-
-Install pytest-cov_:
-
-.. _pytest-cov: https://pypi.org/project/pytest-cov/
-
-.. code-block:: bash
-
-	$ pip install pytest-cov
-
-Set necessary configuration as described above, and run:
-
-.. code-block:: bash
-
-	$ py.test --cov=mmpy_bot tests\
-
-It automatically runs tests and measures code coverage of modules under mmpy_bot root dir.
-Using "--cov-report" parameter to write report into "cov_html" folder by html format.
-
-.. code-block:: bash
-
-	py.test --cov-report html:logs\cov_html --cov=mmpy_bot tests\
-
-Running tests in docker:
-------------------------
-
-If you have docker installed you can spawn a temporary mattermost instance and
-run all the tests by running the following:
-
-.. code-block:: bash
-
-    $ ./tests/run-tests-in-docker.sh
-
-This has predefined environment variables which you can customise.
-Variables in addition to the ones above are:
-
-:code:`DOCKER_CONTAINER_NAME` which specifies the name of the conatiner that runs mattermost
-
-:code:`DOCKER_NETWORK` the docker network to run in, this is created automatically if it doesn't exist
-
-:code:`PYTHON_VERSION` the python version to use