Run LINE Bot implemented in Python (Flask) "without using Heroku"

About this article

As the title says. There are many articles about LINEBot in various languages, but as far as I can find, almost all of them are based on the assumption that they will run in a PaaS environment such as Heroku. In this article, I will describe how to operate in your own environment without using Heroku etc.

Application configuration

See the figure below. linebot_arch.png Flask Web application development framework for Python. Although it has a function as a Web server, it is for minimum operation check. A separate web server is required for production operation. Official: https://flask.palletsprojects.com/en/1.1.x/ uWSGI WSGI is an abbreviation for Web Server Gateway Interface, which connects a web server and an application. Python-specific specifications. uWSGI is a server that meets this specification, and this time it plays the role of connecting Flask applications and Nginx, which will be described later. It is also an application server that runs Flask. Official: https://uwsgi-docs.readthedocs.io/en/latest/ Nginx Web server. Receives a request from a client and returns a response to it. Since the LINE server is sandwiched between the client and the client this time, the request will be received from the LINE server. Since LINE Bot requires that the Web server be SSL-enabled, it is also necessary to obtain a separate domain and SSL certificate (Oreore certificate is not possible). Official: https://nginx.org/en/

LINE server

A place that communicates directly with the client's LINE app. Make various settings from the browser on the console called LINE Developers.

Development environment

Settings on the LINE Developers side

Creating a channel

It is OK if you set according to Official Reference Previously, I was asked to set up a plan here (free plan or paid), but as of May 2020, I have not been asked. Moreover, the PUSH API, which should not have been available in the free plan, has become available before I knew it. .. ..

Set up the bot

Make settings on the LINE Developers console. This is also OK if you set according to Official Reference You don't have to enter the Bot server endpoint URL yet at this point. The important points here are as follows

Create a LINE Bot in your own environment

First, install Flask, line-bot-sdk, uWSGI

$ pip install flask 
$ pip install line-bot-sdk
$ pip install uwsgi

Creating an application in Flask

Created under the line_bot directory with the following configuration. The configuration file is a separate file, but I wonder if I like it here

line_bot
 |-app.py
 |-conf.json
 |-logging.conf

app.py Created by referring to sample program of line-bot-sdk-python. When the user sends text, it returns a parrot, and when an image, video, or stamp is sent, it returns a fixed phrase.

app.py


# -*- coding: utf-8 -*-

from flask import Flask, request, abort

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, ImageMessage, VideoMessage, StickerMessage, TextSendMessage
)
import os
import sys
import json
from logging import getLogger, config

app = Flask(__name__)

ABS_PATH = os.path.dirname(os.path.abspath(__file__))
with open(ABS_PATH+'/conf.json', 'r') as f:
    CONF_DATA = json.load(f)

CHANNEL_ACCESS_TOKEN = CONF_DATA['CHANNEL_ACCESS_TOKEN']
CHANNEL_SECRET = CONF_DATA['CHANNEL_SECRET']

line_bot_api = LineBotApi(CHANNEL_ACCESS_TOKEN)
handler = WebhookHandler(CHANNEL_SECRET)

config.fileConfig('logging.conf')
logger = getLogger(__name__)

@app.route("/test", methods=['GET', 'POST'])
def test():
    return 'I\'m alive!'

@app.route("/callback", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    logger.info("Request body: " + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))

@handler.add(MessageEvent, message=ImageMessage)
def handle_image(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text="Image"))

@handler.add(MessageEvent, message=VideoMessage)
def handle_video(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text="Video"))

@handler.add(MessageEvent, message=StickerMessage)
def handle_sticker(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text="Sticker"))

if __name__ == "__main__":
    #Specify 9999 as the port this time
    port = int(os.getenv("PORT", 9999))
    #If Flask is executed by default, it will not be exposed to the outside, so specify the IP and port in the run argument.
    app.run(host="0.0.0.0", port=port)

conf.json Set the Channel secret and channel access token obtained by LINE Developers. This time I set it to an external file, but I think you can write it directly in app.py

conf.json


{
"CHANNEL_SECRET": "Set Channel secret",
"CHANNEL_ACCESS_TOKEN": "Set channel access token"
}

logging.conf I totally like it.

logging.conf


[loggers]
keys=root

[handlers]
keys=fileHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=INFO
handlers=fileHandler

[handler_fileHandler]
class=handlers.TimedRotatingFileHandler
formatter=simpleFormatter
args=('app.log','MIDNIGHT')

[formatter_simpleFormatter]
format=%(asctime)s %(levelname)s %(message)s

Nginx settings

Nginx proceeds as if it were in / etc / nginx /. If you haven't modified the config file, I think it's supposed to read /etc/nginx/conf.d/*.conf and set it. Create linebot.conf under /etc/nginx/conf.d/ and describe the following. Since port 9998 will be used this time, it is necessary to open the port separately.

linebot.conf


server {
    listen       9998 ssl;
    server_name  example.com;

    ssl_protocols       TLSv1.2 TLSv1.1 TLSv1;
    ssl_ciphers         ALL:!aNULL:!SSLv2:!EXP:!MD5:!RC4:!LOW:+HIGH:+MEDIUM;
    ssl_certificate     [Specify the location where the CRT file is placed];
    ssl_certificate_key [Specify the location where the key file is placed];
    ssl_session_timeout 10m;

    location / {
        include uwsgi_params;
        uwsgi_pass unix:///tmp/uwsgi.sock;
        proxy_pass http://127.0.0.1:9999/;
    }
}

Once set, restart Nginx

$ sudo systemctl restart nginx

uWSGI settings

Create uWSGI configuration file uwsgi.ini in the same layer as app.py and describe the following

uwsgi.ini


# uwsgi.ini

[uwsgi]

#wsgi file
wsgi-file=[app.py path]
callable=app

#Permission host:port
http=0.0.0.0:9999

socket=/tmp/uwsgi.sock
module=app
chmod-socket=666

pidfile=/home/[The path where you want to output the pid file]/uwsgi.pid

#If daemonize is specified, it becomes a daemon. Stdout to the specified path/output stderr
daemonize=/[The path where you want to output the log file]/uwsgi.log

After that, if you start uWSGI with the following command, you can access it.

$ uwsgi --ini myapp.ini

To stop it, just kill -QUIT with the PID of uwsgi.pid.

Operation check

It doesn't matter if it's a browser or curl, so when you visit https://www.example.com:9998/test, you should get" I'm alive! ". Once this is confirmed, specify https://www.example.com:9998/callback as the Webhook URL in LINE Developers and turn on Use webhook. (The "verify" button at the bottom of the URL input field causes an error for some reason. We are investigating this) The LINE Bot you created should now work. linebot_sc.png

Personal future challenges

--As mentioned above, you can now use the PUSH API that you couldn't use in the free plan before. I think that the range of development has expanded greatly even with the free plan, so I will investigate how to use it --Unsolved the problem that verify of Webhook URL is an error in the console of LINE Developers. Need to handle the request sent from the LINE server at the time of verification exclusively for it? -** 2020/05/13 postscript ** Article investigated about this was written

in conclusion

I haven't written any page that describes the procedure to run LINE Bot using Heroku. In the case of the free frame, Heroku will go to sleep if it does not operate for a certain period of time (about 30 minutes?) The connection times out, which is not very practical. If you are not thinking about introducing a paid plan, you should run it if you have your own environment.

I would appreciate it if you could let me know if there are any mistakes in the information I provided.

Recommended Posts

Run LINE Bot implemented in Python (Flask) "without using Heroku"
Asynchronous processing using LINE BOT: RQ (Redis Queue) in Python
Implemented in 1 minute! LINE Notify in Python
Favicon placement (when using Python, Flask, Heroku)
Develop slack bot in python using chat.postMessage
LINE heroku python
Implemented SimRank in Python
Implemented Shiritori in Python
Create a data collection bot in Python using Selenium
[Python] Using Line API [1st Creation of Beauty Bot]
I made a LINE BOT with Python and Heroku
Make Python segfault on one line without using ctypes
Lightweight thread performance benchmark using async / await implemented in Python 3.5
Display character strings without line breaks in python (personal memo)
Fizzbuzz in Python (in one line)
Touch Flask + run with Heroku
Run automatic jobs in python
Run shell commands in python
Try LINE Notify in Python
Disable SSL validation without using verify = False in Python requests
Try building a neural network in Python without using a library
Sudoku solver implemented in Python 3
Get Python scripts to run quickly in Cloud Run using responder
Broadcast on LINE using python
Translate using googletrans in Python
6 Ball puzzle implemented in python
Using Python mode in Processing
Easy! Implement a Twitter bot that runs on Heroku in Python
Python beginners tried Hello World in 30 seconds using the micro-framework Flask
Link SORACOM, home appliances and LINE Bot [Python / Flask / Raspberry Pi]
Make python segfault in one line
GUI programming in Python using Appjar
[Python] Make your own LINE bot
Implemented image segmentation in python (Union-Find)
Let's run "python -m antigravity" in python
Run shell command / python in R
[Python3] Google translate google translate without using api
Print with python3 without line breaks
Run the app with Flask + Heroku
Try using LevelDB in Python (plyvel)
Widrow-Hoff learning rules implemented in Python
Next, use Python (Flask) for Heroku!
Implemented label propagation method in Python
Create Gmail in Python without API
Using global variables in python functions
Slice without using Python, colon (:). a.__getitem__ (slice (3,5)).
Detect keystrokes in Python (without Enter)
Let's see using input in python
Infinite product in Python (using functools)
Edit videos in Python using MoviePy
Implemented Perceptron learning rules in Python
Run unittests in Python (for beginners)
Run Ansible from Python using API
Run a simple algorithm in Python
Handwriting recognition using KNN in Python
I tried Line notification in Python
Try using Leap Motion in Python
[Introduction] Insert line breaks in Python 3
Depth-first search using stack in Python
When using regular expressions in Python
Create a LINE Bot in Django