I made a Mattermost bot with Python (+ Flask)

Caution

The article was asleep for about 9 months, kept in the draft, so the information may be out of date. .. .. Since I wrote it with much effort, I will post it as a memorial service. ..

background

It was decided to move the internal communication tool to Mattermost. I wish I could migrate my existing bot to Mattermost, so I also reimplemented Bot for Mattermost in Python [^ 1] for my own study. I've researched a lot when implementing the bot, so I'll summarize the information for myself.

environment

--CentOS 7.7 (Mattermost server) --Ubuntu 18.04 LTS (Bot server)

Somehow I felt like I wanted to touch both CentOS and Ubuntu. Ubuntu 18.04 comes with Python 3 by default, so I tried using it. In short, it doesn't mean anything.

Environment

Introduction of Mattermost

Mattermost can be easily installed with Docker, so install Docker first.

CentOS


#Install Docker
$ yum install -y yum-utils device-mapper-persistent-data lvm2
$ yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ yum install docker-ce docker-ce-cli containerd.io

#Installation confirmation
$ docker --version
Docker version 19.03.5, build 633a0ea
$

#Start Docker
$ systemctl status docker
$ systemctl enable docker
$ systemctl start docker
$ systemctl status docker

#Operation check
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete
Digest: sha256:4df8ca8a7e309c256d60d7971ea14c27672fc0d10c5f303856d7bc48f8cc17ff
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

$

Once you have Docker installed, you can install and start Mattermost below.

$ docker run --name mattermost-preview -d --publish 8065:8065 --add-host dockerhost:127.0.0.1 mattermost/mattermost-preview

Once installed, it can be started / stopped as follows.

#Start-up
$ docker start mattermost-preview

#Stop
$ docker stop mattermost-preview

#Process confirmation
$ docker ps

■ Reference ・ Qiita: Install Docker on CentOS7 -Docker docs: Get Docker Engine --Community for CentOS

Mattermost settings

Once Mattermost is up and running, you can access it from your browser.

http://xxx.xxx.xxx.xxx:8065

If you can access it, create an administrator account. Any email address is OK. 192.168.56.101_8065_signup_email_02.png

Create a team appropriately. 192.168.56.101_8065_create_team_display_name.png 192.168.56.101_8065_create_team_team_url_MASK.png

Translate to Japanese.

Main Menu -> Account Settings -> Display -> Language 192.168.56.101_8065_bot-test_channels_town-square.png

By default, connection restrictions are applied to private networks. If the server to which the Webhook communicates is in the same segment, it is necessary to set the communication permission. (I was quite addicted here.)

Main Menu-> System Console-> Developers-> Allow Untrusted Internal Connections 192.168.56.101_8065_admin_console_environment_developer.png

By default, you cannot create a bot account, so enable it.

Main Menu-> System Console-> Bot Accounts-> Enable Bot Account Creation 192.168.56.101_8065_admin_console_integrations_bot_accounts.png

Create an outgoing webhook.

Main Menu-> Integration-> Outward Web Hook

Content type: application / json (I chose JSON this time) Channel: Channel that monitors the trigger Trigger word: A word that triggers the activation of the Webhook Callback URL: URL of the message destination (Bot server) (Since Flask is used this time, the default port 5000 is specified) 192.168.56.201_8065_bot-test_integrations_outgoing_webhooks_add_01.png 192.168.56.201_8065_bot-test_integrations_outgoing_webhooks_add_02.png 192.168.56.201_8065_bot-test_integrations_outgoing_webhooks.png

Create a bot account.

Main Menu-> Integration-> Bot Account 192.168.56.101_8065_bot-test_integrations_bots_add.png Bot post permissions are set here.

192.168.56.101_8065_bot-test_integrations_confirm_type=bots&id=5o4eejp977fqzqdprd7ji13gac&token=3tpdq6cc1tfi3cshkrazr7a6be_MASK.png Be sure to make a note of the Bot account token as there is no way to check it later on the menu screen. However, even if you forget it, you can check it with the following command.

$ docker exec -t -e MYSQL_PWD=mostest mattermost-preview mysql -u mmuser mattermost_test -e "select * from UserAccessTokens;"
+----------------------------+----------------------------+----------------------------+-------------+----------+
| Id                         | Token                      | UserId                     | Description | IsActive |
+----------------------------+----------------------------+----------------------------+-------------+----------+
| xxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxx | ?????????   |        1 |
+----------------------------+----------------------------+----------------------------+-------------+----------+
$

Check if you can post from your bot account with the curl command. First, check the channel ID of the posting destination.

Channel-> Show information 192.168.56.101_8065_bot-test_channels_town-square_03.png 192.168.56.101_8065_bot-test_channels_town-square_02_MASK.png

You can also check with the following command.

$ docker exec -t -e MYSQL_PWD=mostest mattermost-preview mysql -u mmuser mattermost_test -e "select * from Channels;"
+----------------------------+---------------+---------------+----------+----------------------------+------+-------------+--------------------------------------------------------+--------+---------+---------------+---------------+---------------+-----------+----------+------------------+
| Id                         | CreateAt      | UpdateAt      | DeleteAt | TeamId                     | Type | DisplayName | Name                                                   | Header | Purpose | LastPostAt    | TotalMsgCount | ExtraUpdateAt | CreatorId | SchemeId | GroupConstrained |
+----------------------------+---------------+---------------+----------+----------------------------+------+-------------+--------------------------------------------------------+--------+---------+---------------+---------------+---------------+-----------+----------+------------------+
| xxxxxxxxxxxxxxxxxxxxxxxxxx | 1574487544576 | 1574487544576 |        0 | xxxxxxxxxxxxxxxxxxxxxxxxxx | O    | Off-Topic   | off-topic                                              |        |         | 1574487544652 |             0 |             0 |           | NULL     |             NULL |
| xxxxxxxxxxxxxxxxxxxxxxxxxx | 1574489265292 | 1574489265292 |        0 |                            | D    |             | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx |        |         | 1574489265315 |             1 |             0 |           | NULL     |             NULL |
| xxxxxxxxxxxxxxxxxxxxxxxxxx | 1574487544570 | 1574487544570 |        0 | xxxxxxxxxxxxxxxxxxxxxxxxxx | O    | Town Square | town-square                                            |        |         | 1574487544611 |             0 |             0 |           | NULL     |             NULL |
+----------------------------+---------------+---------------+----------+----------------------------+------+-------------+--------------------------------------------------------+--------+---------+---------------+---------------+---------------+-----------+----------+------------------+
$

Specify the channel ID and Bot token, and make a POST request to the API with the curl command.

$ curl -i -X POST -H 'Content-Type: application/json' -d '{"channel_id":"[Channel ID]", "message":"This is a message from a bot"}' -H 'Authorization: Bearer [Bot Token]' http://xxx.xxx.xxx.xxx:8065/api/v4/posts

192.168.56.101_8065_bot-test_channels_town-square_04.png I was able to confirm that I was able to post with my Bot account.

■ Reference ・ Usual Software Engineer: How to create a Bot Account with Mattermost

Response to webhook

It seemed easy to do it in Flask to do it in Python, so I implemented it in Flask. I'm just POSTing JSON to the API as I do with curl.

Ubuntu


$ sudo apt install python3-pip
$ pip3 -V
pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)
$
$ pip3 install flask
$ pip3 install requests

bottest.py


import json

import requests
from flask import Flask, request

BOT_TOKEN = '[Bot Token]'
CHANNEL_ID = '[Channel ID]'
MM_API_ADDRESS = 'http://xxx.xxx.xxx.xxx:8065/api/v4/posts'

app = Flask(__name__)

@app.route('/bot-test', methods=['POST'])  #Outgoing webhook callback URL
def bot_reply():
    posted_user = request.json['user_name']
    posted_msg  = request.json['text']

    reply_headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + BOT_TOKEN,
    }

    reply_data = {
        "channel_id": CHANNEL_ID,
        "message": f"@{posted_user} Bot reply message.",
        "props": {
            "attachments": [
                    {
                "author_name": posted_user,
                "text": posted_msg,
                }
            ]
        },
    }

    reply_request = requests.post(
        MM_API_ADDRESS,
        headers = reply_headers,
        data = json.dumps(reply_data)
    )

    return reply_request

if __name__ == '__main__':
    app.debug = True
    app.run(host='0.0.0.0')

Test if you can get a response. Start Flask's test server.

Ubuntu


$ python3 bottest.py

Post a message containing a trigger word in Mattermost. 192.168.56.201_8065_bot-test_channels_town-square.png

I was able to confirm that I was able to post from my Bot account using the Webhook as a trigger. All you have to do is implement the bot logic.

By the way, the JSON that comes from Mattermost's Webhook looks like this.

{
    'token': 'xxxxxxxxxxxxxxxxxxxxxxxxxx', 
    'team_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxx', 
    'team_domain': 'bot-test', 
    'channel_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxx', 
    'channel_name': 'town-square', 
    'timestamp': 1234567890123, 
    'user_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxx', 
    'user_name': 'mmadmin', 
    'post_id': 'xxxxxxxxxxxxxxxxxxxxxxxxxx', 
    'text': '#bot test', 
    'trigger_word': '#bot', 
    'file_ids': ''
}

■ Reference ・ [NOBWAK'S LAIR: Let's make BOT of SLACK / MATTERMOST with FLASK of PYTHON](http://april.fool.jp/blogs/2016/06/02/slackmattermost%E3%81%AEbot%E3%82%92python % E3% 81% AEflask% E3% 81% A7% E4% BD% 9C% E3% 82% 8D% E3% 81% 86 /) -Qiita: Make a simple bot with Mattermost and Python + Flask

bonus

Once the bot is complete, you'll need a web server and a WSGI server to run it as a web application. After a lot of research, I chose Nginx + Gunicorn. (Because I felt it was the simplest and easiest) This area is still unclear, so I'll just rush to write it down as a memo. ..

Gunicorn is installed with pip.

Ubuntu


$ sudo pip3 install gunicorn

gunicorn [Python script name]: Launched with [Flask instance name]. (Default port is 8000) (The following launches an app instance in bottest.py)

#Run in the directory where the Python script is located
$ cd /path/bot
$ sudo gunicorn bottest:app
[YYYY-MM-DD hh:mm:ss +0900] [1699] [INFO] Starting gunicorn 20.0.2
[YYYY-MM-DD hh:mm:ss +0900] [1699] [INFO] Listening at: http://127.0.0.1:8000 (1699)
[YYYY-MM-DD hh:mm:ss +0900] [1699] [INFO] Using worker: sync
[YYYY-MM-DD hh:mm:ss +0900] [1703] [INFO] Booting worker with pid: 1703

If you want to use the same port as Flask's test server, OK (When it is troublesome to change the Webhook settings on the Mattermost side)

$ sudo gunicorn bottest:app -b 0.0.0.0:5000

You can use the chdir option without having to go to the location of the Python script.

$ sudo gunicorn --chdir /path/bot bottest:app -b 0.0.0.0:5000

When stopped, press Ctrl + C to kill the process.

If you want to run Gunicorn alone, this is fine, but you can't work with Nginx as it is. First, install Nginx.

Ubuntu


$ sudo apt install nginx
$ nginx -v
nginx version: nginx/1.14.0 (Ubuntu)
$

Set Nginx. Socket is required for communication with Gunicorn. (Created in / tmp below)

/etc/nginx/sites-available/default


upstream bot-test {
    server unix:/tmp/bot-test.sock;
}

server {
    listen 5000;

    root path/bot;
    server_name localhost;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location /bot-test {
        try_files $uri @flask;
    }

    location @flask {
        proxy_pass http://bot-test;
    }
}

Operation check of Nginx.

$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
$
$ sudo systemctl status nginx
$ sudo systemctl restart nginx
$ sudo systemctl status nginx

If there is no problem with Nginx settings, create a Gunicorn configuration file. The location and name of the configuration file can be anything Describe the Socket described in the Nginx configuration file earlier.

$ vim gunicorn_conf.py

gunicorn_conf.py


bind = 'unix:/tmp/bot-test.sock'
daemon = True
reload = True

Specify the configuration file with the -c option and OK if it can be started

$ sudo gunicorn bottest:app -c gunicorn_conf.py

When stopped, kill the process.

$ sudo pkill gunicorn

To be honest, I'm doing the extra part with the spirit of moving for the time being, so There must be a better way. ..

■ Reference ・ Qiita: gunicorn + Flask + nginx + Systemd ・ [Diary of "Technical college student who entered the wrong department": Procedure to manually deploy Ubuntu + Nginx + Gunicorn](https://nnsnodnb.hatenablog .jp / entry / ubuntu-nginx-gunicorn-deploy) -Qiita: A memo to daemonize python

[^ 1]: Reinventing the wheel https://www.slideshare.net/RansuiIso/python-115121978 https://www.youtube.com/watch?v=kO4FNg648qE

Recommended Posts

I made a Mattermost bot with Python (+ Flask)
I made a fortune with Python.
I made a daemon with Python
I made a Twitter BOT with GAE (python) (with a reference)
I made a LINE BOT with Python and Heroku
I made a character counter with Python
I made a simple book application with python + Flask ~ Introduction ~
I made a Hex map with Python
I made a roguelike game with Python
I made a simple blackjack with Python
I made a configuration file with Python
I made a neuron simulator with Python
I made a Nyanko tweet form with Python, Flask and Heroku
I made a stamp substitute bot with line
I made a competitive programming glossary with Python
I made a weather forecast bot-like with Python.
I made a GUI application with Python + PyQt5
I made a Twitter fujoshi blocker with Python ①
[Python] I made a Youtube Downloader with Tkinter.
I made a LINE Bot with Serverless Framework!
I made blackjack with python!
I made a python text
I made a discord bot
I made wordcloud with Python.
I made a household account book bot with LINE Bot
I made a Christmas tree lighting game with Python
I made a net news notification app with Python
SNS Python basics made with Flask
I made a Line-bot using Python!
I made a wikipedia gacha bot
I made a simple typing game with tkinter in Python
I made a LINE BOT that returns parrots with Go
I made a package to filter time series with python
I made LINE-bot with Python + Flask + ngrok + LINE Messaging API
I made a puzzle game (like) with Tkinter in Python
[AWS] I made a reminder BOT with LINE WORKS (implementation)
I made a payroll program in Python!
I drew a heatmap with seaborn [Python]
I tried a functional language with Python
What I did with a Python array
I made a life game with Numpy
I made a stamp generator with GAN
After studying Python3, I made a Slackbot
I made a WEB application with Django
[Python] I made a Line bot that randomly asks English words.
I made a simple circuit with Python (AND, OR, NOR, etc.)
I made a library to easily read config files with Python
I made a package that can compare morphological analyzers with Python
I made a lot of files for RDP connection with Python
I made a shuffle that can be reset (reverted) with Python
[Introduction] I want to make a Mastodon Bot with Python! 【Beginners】
I made a segment tree with python, so I will introduce it
I made a python dictionary file for Neocomplete
〇✕ I made a game
Make a Twitter trend bot with heroku + Python
Python beginners decided to make a LINE bot with Flask (Flask rough commentary)
I made a tool to automatically browse multiple sites with Selenium (Python)
I made my dog "Monaka Bot" with LineBot
Create a LINE BOT with Minette for Python
I want to make a game with Python
Procedure for creating a LineBot made with Python