【Python】Salesforceを使わずに自力で実用的な有人機能付きのLINEBotを作った話【MessagingAPI】

#初めに
だいぶ説明を端折ります。ごめんなさい。
多分、コードも汚いと思います。ごめんなさい。--

日本ではほぼすべての人が使っている(？)であろうLINEBotをビジネス用途で利用すると有人機能が必要になりますよね？(多分)
そのため、なるべく簡単に有人対応ができるような予約システムを構築します！
やるぞ！！！

#ゴール
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493888/8d78f0cf-2543-aaaa-5641-1d6ca798cbb5.png)

#結論：制作物はこれです。
LINEBOT：予約システム
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/493888/95d6c15f-17b9-54f1-00a2-b254ff2734b8.png)

実際にどんなものを作ったコードが、実物を見たほうがわかりやすいかと！
オペレーターは僕につながっています（笑）
データベースとPUSHMessageを連携することによって有人対応機能は可能です！！
無料版だと一か月１０００件までしかできないので、実際に業務で使う場合は有料のライトプランとかで利用されるといいと思います。

##必要なインストール等

```
代表的なもの
pip install Flask
pip install line-bot-sdk
pip install flask_sqlalchemy
pip install Pillow
・
・

その他もろもろ
```

#ディレクトリ構成
こんな感じです。少し端折った部分もあるかもですが、アクションに関して最低限分割してわかりやすく管理しようという魂胆です。

```
├── projects
    ├── static
 　 └── contents
         ├── FollowAction.py
         ├── ImageAction.py
         ├── PostBackAction.py
         └── TextAction.py

 　 └── server.py
 　 └── Settings.py
 　 └── config.ini
```

#『server.py』
ここでは、サーバーを立てる役割をしているのですが、データベースの設計もここでやってます。本当は分けたほうがいいですが、
今回は、まあいいでしょう。

```python:server.py
# coding:utf-8
from flask import Flask, request, abort
from flask_sqlalchemy import SQLAlchemy
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage, PostbackEvent, ButtonsTemplate, PostbackTemplateAction, TemplateSendMessage, FollowEvent
    , LocationSendMessage, LocationMessage, ImagemapSendMessage, MessageTemplateAction, ConfirmTemplate, DatetimePickerAction, DatetimePickerTemplateAction
    , ImageMessage, ImageSendMessage, FlexSendMessage, BoxComponent, TextComponent, BubbleContainer, ButtonComponent,MessageAction
    , CarouselContainer, URIAction, ImageComponent, PostbackAction, CameraAction, CameraRollAction, QuickReplyButton, QuickReply
)

import re
from PIL import Image
from io import BytesIO
import configparser

#各アクションモジュール
from contents import FollowAction, ImageAction, TextAction, PostBackAction

app = Flask(__name__)

""" ------------------------          設定ファイルの読み込み　　　---------------------------"""
#設定ファイルの読み込み
cfg = configparser.ConfigParser()
cfg.read('config.ini')

#SQLALCHEMY
url = cfg["SQL"]["URL"]
app.config['SQLALCHEMY_DATABASE_URI'] = url
db = SQLAlchemy(app)

#LINEの設定
chansec = cfg["LINE"]["SECRET"]
acctoken = cfg["LINE"]["TOKEN"]
line_bot_api = LineBotApi(acctoken)
handler = WebhookHandler(chansec)

superuser_securekey = cfg["SUPERUSER"]["SECUREKEY"]
superuser_securepwd = cfg["SUPERUSER"]["SECUREPASS"]

#オウム返しを有効にするかどうか１が有効
Parrot_return = False

#ドメインとなるサーバーURL
base_url = cfg["SERVER"]["URL"]
"""--------------------------------------------------------------------------------------------"""


"""--------------------------------          データベースの定義           ----------------------------"""
class lineuser(db.Model):#一般ユーザー用データベース
    __tablename__ = "lineuser"
    __table_args__ = {'mysql_collate': 'utf8_general_ci'}
    user_id = db.Column(db.String(80), primary_key=True)
    username = db.Column(db.String(255))
    tel = db.Column(db.String(255))
    plan = db.Column(db.String(255))
    usermessage = db.Column(db.String(255))
    step = db.Column(db.Integer)
    status = db.Column(db.String(255))
    retention = db.Column(db.String(255))
    requested_at = db.Column(db.String(255))


    def __init__(self, user_id, username, tel, plan, usermessage, step, status, retention, requested_at):
        self.user_id = user_id
        self.username = username
        self.tel = tel
        self.plan = plan
        self.usermessage = usermessage
        self.step = step
        self.retention = retention
        self.status = status
        self.requested_at = requested_at

    def __repr__(self):
        return '<lineuser %r>' % self.user_id


class administrator(db.Model):#管理者用データベース
    __tablename__ = "administrator"
    __table_args__ = {'mysql_collate': 'utf8_general_ci'}
    admin_id = db.Column(db.String(80), primary_key=True)
    admin_name = db.Column(db.String(255))
    password = db.Column(db.String(255))
    adminstatus = db.Column(db.String(255))
    to_user = db.Column(db.String(255))
    page_options = db.Column(db.Integer)

    def __init__(self, admin_id, admin_name, password, adminstatus, to_user, page_options):
        self.admin_id = admin_id
        self.admin_name = admin_name
        self.password = password
        self.adminstatus = adminstatus
        self.to_user = to_user
        self.page_options = page_options

    def __repr__(self):
        return '<administrator %r>' % self.admin_id

"""---------------------------------------------------------------------------------------------------"""



"""--------------------------------          MessagingAPIの処理           ----------------------------"""
#LINEからのリクエストを受け取る箇所
@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)
    return 'OK'

#もし、フォローされたら
@handler.add(FollowEvent)
def on_follow(event):
    FollowAction.Start(line_bot_api, event, db, lineuser, administrator)

#もしメッセージが来たら
@handler.add(MessageEvent, message=TextMessage)
def on_message(event):
    TextAction.Start(line_bot_api, event, db, lineuser, administrator, Parrot_return)

#ボタンとかおされたら
@handler.add(PostbackEvent)
def on_postback(event):
    PostBackAction.Start(line_bot_api, event, db, lineuser, administrator)

#画像が送られてきたら
@handler.add(MessageEvent, message=ImageMessage)
def Image_message(event):
    ImageAction.Start(line_bot_api, event, db, lineuser, administrator)


"""----------------------------------------------------------------------------------------------------"""



if __name__ == "__main__":
    app.run(port=????)#好きなポート番号を指定

```

#設定ファイル『Settings.py』とconfig.ini

```python
import configparser

cfg = configparser.ConfigParser()
cfg['DEFAULT'] = {
    'debug': True
}

cfg['LINE'] = {
    'SECRET': "ここにLINEのシークレットキー"
    , 'TOKEN': "ここにアクセストークン"
}

cfg["SQL"] = {
    "URL": "ここにSQLALCHEMYで利用するDBのURIを記述"
}
cfg["SUPERUSER"] = {
    "SECUREKEY": "管理者利用するためのKEY"
    , "SECUREPASS": "管理者利用するためのPASS"
}
cfg["SERVER"] = {
    "URL": "サーバーのURL"
}
with open('config.ini', 'w') as config_file:
    cfg.write(config_file)
```

#DBのテーブル作ります
作ります


```
>>python Settings.py
>>python
from server import db
db.create_all()
exit()
```

#『TextAction.py』
次のコード、LINEからTEXT入力を受け取ったときの処理です。

```python:TextAction.py
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage, PostbackEvent, ButtonsTemplate, PostbackTemplateAction, TemplateSendMessage, FollowEvent
    , LocationSendMessage, LocationMessage, ImagemapSendMessage, MessageTemplateAction, ConfirmTemplate, DatetimePickerAction, DatetimePickerTemplateAction
    , ImageMessage, ImageSendMessage, FlexSendMessage, BoxComponent, TextComponent, BubbleContainer, ButtonComponent,MessageAction
    , CarouselContainer, URIAction, ImageComponent, PostbackAction, CameraAction, CameraRollAction, QuickReplyButton, QuickReply
)
import re
import configparser


""" ------------------------          設定ファイルの読み込み　　　    ---------------------------"""
#設定ファイルの読み込み
cfg = configparser.ConfigParser()
cfg.read('config.ini')

superuser_securekey = cfg["SUPERUSER"]["SECUREKEY"]
superuser_securepwd = cfg["SUPERUSER"]["SECUREPASS"]
"""-----------------------------------------------------------------------------------------"""


def Start(line_bot_api, event, db, lineuser, administrator, Parrot_return):
    txt = event.message.text
    user_id = event.source.user_id
    reply_token = event.reply_token
    if Parrot_return == 1:
        line_bot_api.reply_message(reply_token, TextSendMessage(text=txt))
    else:
        if db.session.query(administrator).filter(administrator.admin_id == user_id).count() == True and txt == "管理者利用":
            user = db.session.query(lineuser).filter_by(user_id=user_id).first()
            if user.status == "end":
                line_bot_api.push_message(to=user_id, messages=TextSendMessage(text="管理者として利用を開始します。"))
                user.status = "admin"
                db.session.add(user)
                db.session.commit()
            else:
                line_bot_api.reply_message(reply_token, TextSendMessage(text="通常利用を完了してからお願いします！"))

        if not db.session.query(lineuser).filter(lineuser.user_id == user_id).count():
            reg = lineuser(user_id, None, None, None, None, 0, None, None, None)
            db.session.add(reg)
            db.session.commit()
        user = db.session.query(lineuser).filter_by(user_id=user_id).first()

        if txt == superuser_securekey and user.status == "end":
            if not db.session.query(administrator).filter(administrator.admin_id == user_id).count():
                user.status = "preadmin"
                db.session.add(user)
                db.session.commit()
                line_bot_api.push_message(to=user_id,
                                          messages=TextSendMessage(text="あともう一歩！パスワードを教えて下さい。"))  # 管理者登録一歩前
            else:
                line_bot_api.push_message(to=user_id,
                                          messages=TextSendMessage(text="すでに管理者権限があります。"))  # すでに管理者登録されてるよ

        #############  ステータスチェックと修正確認   ##########
        status = user.status
        if status == None:
            user.status = "name"
            db.session.add(user)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(text="あなたのお名前を教えてください！"))

        elif status == "name":
            yestxt = "はい"
            notxt = "いいえ"
            bubble = BubbleContainer(
                body=BoxComponent(
                    layout='horizontal', margin="xs",
                    contents=[
                        TextComponent("『%s』様 でよろしいですか？" % txt, size='md', wrap=True, type="text", margin="xs", )]),
                footer=BoxComponent(
                    layout="horizontal", spacing="sm",
                    contents=[ButtonComponent(style="link", height="sm",
                                              action=PostbackAction(label=yestxt,
                                                                    data="name@@@0@@@" + txt,
                                                                    display_text=yestxt)),
                              ButtonComponent(style="link", height="sm",
                                              action=PostbackAction(label=notxt, data="no@@@0",
                                                                    display_text=notxt))], flex=0))
            image_confirm_to_message = FlexSendMessage(alt_text="メッセージの確認", contents=bubble)
            line_bot_api.reply_message(reply_token, image_confirm_to_message)

        elif status == "tel":
            yestxt = "はい"
            notxt = "いいえ"
            bubble = BubbleContainer(
                body=BoxComponent(
                    layout='horizontal', margin="xs",
                    contents=[
                        TextComponent("お電話番号は、『%s』でよろしいですか？" % txt, size='md', wrap=True, type="text", margin="xs", )]),
                footer=BoxComponent(
                    layout="horizontal", spacing="sm",
                    contents=[ButtonComponent(style="link", height="sm",
                                              action=PostbackAction(label=yestxt,
                                                                    data="tel@@@1@@@" + txt,
                                                                    display_text=yestxt)),
                              ButtonComponent(style="link", height="sm",
                                              action=PostbackAction(label=notxt, data="no@@@1",
                                                                    display_text=notxt))], flex=0))
            image_confirm_to_message = FlexSendMessage(alt_text="メッセージの確認", contents=bubble)
            line_bot_api.reply_message(reply_token, image_confirm_to_message)

        elif status == "plan":
            plan_ls = ["Aプラン", "Bプラン", "Cプラン", "GOLDプラン"]
            if txt in plan_ls:
                yestxt = "はい"
                notxt = "いいえ"
                bubble = BubbleContainer(
                    body=BoxComponent(
                        layout='horizontal', margin="xs",
                        contents=[
                            TextComponent("ご希望のプランは、『%s』でよろしいですか？" % txt, size='md', wrap=True, type="text", margin="xs", )]),
                    footer=BoxComponent(
                        layout="horizontal", spacing="sm",
                        contents=[ButtonComponent(style="link", height="sm",
                                                  action=PostbackAction(label=yestxt,
                                                                        data="plan@@@2@@@" + txt,
                                                                        display_text=yestxt)),
                                  ButtonComponent(style="link", height="sm",
                                                  action=PostbackAction(label=notxt, data="no@@@2",
                                                                        display_text=notxt))], flex=0))
                image_confirm_to_message = FlexSendMessage(alt_text="メッセージの確認", contents=bubble)
                line_bot_api.reply_message(reply_token, image_confirm_to_message)

        elif status == "message":
            yestxt = "はい"
            notxt = "いいえ"
            bubble = BubbleContainer(
                body=BoxComponent(
                    layout='horizontal', margin="xs",
                    contents=[
                        TextComponent("最後のメッセージは、『%s』でよろしいですか？" % txt, size='md', wrap=True, type="text", margin="xs", )]),
                footer=BoxComponent(
                    layout="horizontal", spacing="sm",
                    contents=[ButtonComponent(style="link", height="sm",
                                              action=PostbackAction(label=yestxt,
                                                                    data="message@@@3@@@" + txt,
                                                                    display_text=yestxt)),
                              ButtonComponent(style="link", height="sm",
                                              action=PostbackAction(label=notxt, data="no@@@3",
                                                                    display_text=notxt))], flex=0))
            image_confirm_to_message = FlexSendMessage(alt_text="メッセージの確認", contents=bubble)
            line_bot_api.reply_message(reply_token, image_confirm_to_message)

        elif status == "end":
            if txt == "接続":
                yestxt = "はい"
                notxt = "いいえ"
                bubble = BubbleContainer(
                    body=BoxComponent(
                        layout='horizontal', margin="xs",
                        contents=[
                            TextComponent("オペレーターに接続申請をしますか？", size='md', wrap=True, type="text", margin="xs", )]),
                    footer=BoxComponent(
                        layout="horizontal", spacing="sm",
                        contents=[ButtonComponent(style="link", height="sm",
                                                  action=PostbackAction(label=yestxt,
                                                                        data="end@@@4@@@" + txt,
                                                                        display_text=yestxt)),
                                  ButtonComponent(style="link", height="sm",
                                                  action=PostbackAction(label=notxt, data="no@@@1",
                                                                        display_text=notxt))], flex=0))
                image_confirm_to_message = FlexSendMessage(alt_text="確認", contents=bubble)
                line_bot_api.reply_message(reply_token, image_confirm_to_message)
            else:
                line_bot_api.reply_message(reply_token, TextSendMessage(text="オペレーターへの接続申請をする場合は、『接続』とご入力ください。"))

        elif status == "preadmin" and txt != superuser_securekey:
            if txt == cfg["SUPERUSER"]["SECUREPASS"]:
                add_admin = administrator(user_id, None, 0, None, None, None)
                db.session.add(add_admin)
                db.session.commit()
                user.status = "end"
                db.session.add(user)
                db.session.commit()
                line_bot_api.reply_message(reply_token, TextSendMessage(text="あなたを管理者として付与しました。『管理者利用』とご入力ください。"))
            else:
                user.status = "end"
                db.session.add(user)
                db.session.commit()
                line_bot_api.reply_message(reply_token, TextSendMessage(text="もう一度最初からご入力ください。"))

        elif status == "response":
            text = txt + " --from %s"%user.username
            line_bot_api.push_message(to=user.retention, messages=TextSendMessage(text=text))

        elif status == "admin":
            admin = db.session.query(administrator).filter_by(admin_id=user_id).first()
            # 管理者名の入力
            if admin.admin_name == None and admin.adminstatus != "admin_name":
                admin.adminstatus = "admin_name"
                db.session.add(user)
                db.session.commit()
                line_bot_api.push_message(to=user_id, messages=TextSendMessage(text="管理者様のお名前をご入力ください。"))

            # 管理者名の入力確認
            elif admin.adminstatus == "admin_name":
                qtxt = "管理者：『%s』様でよろしいですか？" % txt
                yestxt = "はい"
                notxt = "いいえ"
                bubble = BubbleContainer(
                    body=BoxComponent(
                        layout='horizontal',
                        margin="xs",
                        contents=[TextComponent(text=qtxt, size='md', wrap=True, type="text", margin="xs", )]),
                    footer=BoxComponent(
                        layout="horizontal",
                        spacing="sm",
                        contents=[ButtonComponent(style="link", height="sm",
                                                  action=PostbackAction(label=yestxt,
                                                                        data="admin_name@@@1@@@" + txt,
                                                                        display_text=yestxt)),
                                  ButtonComponent(style="link", height="sm",
                                                  action=PostbackAction(label=notxt, data="no@@@1@@@no",
                                                                        display_text=notxt))], flex=0))
                message = FlexSendMessage(alt_text="管理者名を入力してください。", contents=bubble)
                line_bot_api.push_message(to=user_id, messages=message)

            elif txt == "切断":
                if admin.to_user != None:
                    line_bot_api.push_message(to=admin.to_user, messages=TextSendMessage(text="オペレーターとの接続が終了しました。"))
                    line_bot_api.reply_message(reply_token, TextSendMessage(text="有人接続を切断しました。"))

                    to_user = db.session.query(lineuser).filter_by(user_id=admin.to_user).first()
                    to_user.retention = None
                    to_user.status = "end"
                    db.session.add(to_user)
                    db.session.commit()

                    admin.to_user = None
                    db.session.add(user)
                    db.session.commit()

            # 「通常利用」の入力受け取り
            elif txt == "通常利用":
                if admin.to_user != None:
                    line_bot_api.reply_message(reply_token,
                                               TextSendMessage(text="有人対応中です。『通常利用』の切り替えは必ず、『切断』で有人対応を切ってからご使用ください。"))
                else:
                    line_bot_api.reply_message(reply_token, TextSendMessage(text="通常利用に切り替えました。有人対応する際は、『管理者利用』とご入力ください。"))
                    user.status = "end"
                    db.session.add(user)
                    db.session.commit()
                    to_user = db.session.query(lineuser).filter_by(user_id=user_id).first()
                    to_user.retention = None
                    to_user.status = "end"
                    db.session.add(to_user)
                    db.session.commit()
                    admin.to_user = None
                    db.session.add(user)
                    db.session.commit()

            else:
                if admin.to_user != None:
                    line_bot_api.push_message(to=admin.to_user, messages=TextSendMessage(text=txt))
                    line_bot_api.reply_message(reply_token, TextSendMessage(text="%s とメッセージを送信しました。"%txt))
                else:
                    line_bot_api.reply_message(reply_token, TextSendMessage(text="誰にも接続されていません。"))

```

めっちゃ雑な記事ですみません。
どんどんいきます。

#『PostBackAction.py』
ボタン入力とかそういう特殊な入力を受け取った際に
処理する機構です。

```python:PostBackAction.py
Learn more or give us feedback
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage, PostbackEvent, ButtonsTemplate, PostbackTemplateAction, TemplateSendMessage, FollowEvent
    , LocationSendMessage, LocationMessage, ImagemapSendMessage, MessageTemplateAction, ConfirmTemplate, DatetimePickerAction, DatetimePickerTemplateAction
    , ImageMessage, ImageSendMessage, FlexSendMessage, BoxComponent, TextComponent, BubbleContainer, ButtonComponent,MessageAction
    , CarouselContainer, URIAction, ImageComponent, PostbackAction, CameraAction, CameraRollAction, QuickReplyButton, QuickReply, FillerComponent,IconComponent
)

#from .ExtraCreateMessage import admin_sight_client_list
import configparser

""" ------------------------          設定ファイルの読み込み　　　    ---------------------------"""
#設定ファイルの読み込み
cfg = configparser.ConfigParser()
cfg.read('config.ini')

base_url = cfg["SERVER"]["URL"]

def create_image_container(title_name, url):
    container = BubbleContainer(
        body=BoxComponent(layout="vertical", spacing="sm", margin="sm", contents=[
            BoxComponent(layout="vertical", contents=[
                ImageComponent(url=url, size="full", aspect_mode="cover", aspect_ratio="1:1", gravity="top"),
                BoxComponent(layout="vertical", contents=[
                    BoxComponent(layout="vertical", contents=[
                        TextComponent(text=title_name, size="xl", color="#ffffff", weight="bold")
                    ]),
                    BoxComponent(layout="vertical", contents=[
                        FillerComponent(),
                        BoxComponent(layout="baseline", spacing="sm",contents=[
                            FillerComponent(),
                            TextComponent(text="このプランにする", offset_top="-2px", color="#ffffff",flex=0, action=MessageAction(text=title_name)),
                            FillerComponent(),
                        ]),
                        FillerComponent(),
                    ], border_width="1px", border_color="#ffffff", corner_radius="4px",
                                 height="40px", spacing="sm", margin="xxl"),
                ], position="absolute", offset_bottom="0px", offset_start="0px", offset_end="0px",
                             background_color="#03303Acc", padding_all="20px", padding_top="18px"),
            ], padding_all="0px"),
        ],padding_all="0px")
        )
    return container

def Start(line_bot_api, event, db, lineuser, administrator):
    user_id = event.source.user_id
    reply_token = event.reply_token
    postback_msg = event.postback.data
    msg = postback_msg.split("@@@")  # @@@に変更する
    user = db.session.query(lineuser).filter_by(user_id=user_id).first()
    step = user.step

    if len(msg) == 2:
        post_msg = msg[0]  # YESかNO
        post_num = int(msg[1])  # 質問ナンバー

    elif len(msg) == 3:
        post_msg = msg[0]
        post_num = int(msg[1])
        data = msg[2]

    if post_msg == "admin_name":
        if post_num == 1:
            ad_name = data
            admin = db.session.query(administrator).filter_by(admin_id=user_id).first()
            admin.admin_name = data
            admin.adminstatus = None
            db.session.add(admin)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(text="%s さん、管理者登録ありがとうございます！"%ad_name))

    elif step == post_num:
        if post_msg == "name":
            user.username = data
            user.status = "tel"
            user.step = step + 1
            db.session.add(user)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(text="%sさん、ありがとうございます。"
                                                                         "次にお電話番号をご入力ください！"
                                                                         "※デモ用なので、本当の電話番号を入れないでください！！" % data))
        elif post_msg == "tel":
            user.tel = data
            user.step = step + 1
            user.status = "plan"
            db.session.add(user)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(
                text="%sさん、ありがとうございました。次にご利用になるプランをご選択ください。" % user.username))
            plan_ls = ["Aプラン", "Bプラン", "Cプラン", "GOLDプラン"]
            url_ls = ["A.png", "B.png", "C.png", "GOLD.png"]
            bubble_container = []
            for (p, url) in zip(plan_ls, url_ls):
                url_ = base_url + "static/" + url
                container = create_image_container(p, url_)
                bubble_container.append(container)
            bubble2 = CarouselContainer(contents=bubble_container)
            select_messa = FlexSendMessage(alt_text="プランを選択してください。", contents=bubble2)
            line_bot_api.push_message(to=user_id, messages=select_messa)

        elif post_msg == "plan":
            user.plan = data
            user.step = step + 1
            user.status = "message"
            db.session.add(user)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(
                text="%sさん、ありがとうございました。最後に何かメッセージがあればご入力ください！なしの場合、『なし』とご入力ください。" % user.username))

        elif post_msg == "message":
            user.usermessage = data
            user.step = step + 1
            user.status = "end"
            db.session.add(user)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(
                text="%sさん、ありがとうございました。これで申請が完了です！※そのまま、オペレーターに接続したい場合は、『接続』とご入力ください。" % user.username))

        ###  --------    有人対応処理　　----------    ####
        elif post_msg == "end":
            line_bot_api.reply_message(reply_token, TextSendMessage(
                text="オペレーター(製作者)に接続申請しています。そのまましばらくお待ちください。"))
            yestxt = "接続"
            id_ls = db.session.query(administrator.admin_id).all()
            for id_ in id_ls:
                try:
                    txt = "%s 様よりオペレーター接続申請が来ております。『接続』ボタンを押して対応をお願いします。" %user.username
                    bubble = BubbleContainer(
                        body=BoxComponent(
                            layout='horizontal',
                            margin="xs",
                            contents=[TextComponent(txt, size='md', wrap=True, type="text", margin="xs", )]),
                        footer=BoxComponent(
                            layout="horizontal",
                            spacing="sm",
                            contents=[ButtonComponent(style="link", height="sm",
                                                      action=PostbackTemplateAction(label=yestxt,
                                                                                    data="demand@@@4@@@" + user_id,
                                                                                    display_text=yestxt))], flex=0))
                    admin_access_message = FlexSendMessage(alt_text="対応申請が来ております。", contents=bubble)
                    line_bot_api.push_message(to=id_[0], messages=admin_access_message)
                except:
                    pass

        elif post_msg == "demand":
            to_user_id = data
            to_user = db.session.query(lineuser).filter_by(user_id=to_user_id).first()
            to_user.retention = user_id
            to_user.status = "response"
            db.session.add(to_user)
            db.session.commit()
            admin = db.session.query(administrator).filter_by(admin_id=user_id).first()
            admin.to_user = to_user_id
            db.session.add(admin)
            db.session.commit()
            adminuser = db.session.query(lineuser).filter_by(user_id=user_id).first()
            adminuser.status = "admin"
            db.session.add(adminuser)
            db.session.commit()
            adminname = admin.admin_name
            line_bot_api.push_message(to=to_user_id, messages=TextSendMessage(text="オペレーターの%s と接続されました"%adminname))
            line_bot_api.reply_message(reply_token, TextSendMessage(text="%s 様と接続されました。"%to_user.username))

        ###----------------------------------------------------------####
        else:
            line_bot_api.reply_message(reply_token, TextSendMessage(text="もう一度お願いします。"))
    else:
        if user.status == "end":
            line_bot_api.reply_message(reply_token, TextSendMessage(text="キャンセルしました。"))
        else:
            line_bot_api.reply_message(reply_token, TextSendMessage(text="それは最新ではありません！"))

```

#『ImageAction.py』
画像が送られてきたときに反応するコードです。


```python:ImageAction.py

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage, PostbackEvent, ButtonsTemplate, PostbackTemplateAction, TemplateSendMessage, FollowEvent
    , LocationSendMessage, LocationMessage, ImagemapSendMessage, MessageTemplateAction, ConfirmTemplate, DatetimePickerAction, DatetimePickerTemplateAction
    , ImageMessage, ImageSendMessage, FlexSendMessage, BoxComponent, TextComponent, BubbleContainer, ButtonComponent,MessageAction
    , CarouselContainer, URIAction, ImageComponent, PostbackAction, CameraAction, CameraRollAction, QuickReplyButton, QuickReply, FillerComponent,IconComponent
)

#from .ExtraCreateMessage import admin_sight_client_list
import configparser

""" ------------------------          設定ファイルの読み込み　　　    ---------------------------"""
#設定ファイルの読み込み
cfg = configparser.ConfigParser()
cfg.read('config.ini')

base_url = cfg["SERVER"]["URL"]

def create_image_container(title_name, url):
    container = BubbleContainer(
        body=BoxComponent(layout="vertical", spacing="sm", margin="sm", contents=[
            BoxComponent(layout="vertical", contents=[
                ImageComponent(url=url, size="full", aspect_mode="cover", aspect_ratio="1:1", gravity="top"),
                BoxComponent(layout="vertical", contents=[
                    BoxComponent(layout="vertical", contents=[
                        TextComponent(text=title_name, size="xl", color="#ffffff", weight="bold")
                    ]),
                    BoxComponent(layout="vertical", contents=[
                        FillerComponent(),
                        BoxComponent(layout="baseline", spacing="sm",contents=[
                            FillerComponent(),
                            TextComponent(text="このプランにする", offset_top="-2px", color="#ffffff",flex=0, action=MessageAction(text=title_name)),
                            FillerComponent(),
                        ]),
                        FillerComponent(),
                    ], border_width="1px", border_color="#ffffff", corner_radius="4px",
                                 height="40px", spacing="sm", margin="xxl"),
                ], position="absolute", offset_bottom="0px", offset_start="0px", offset_end="0px",
                             background_color="#03303Acc", padding_all="20px", padding_top="18px"),
            ], padding_all="0px"),
        ],padding_all="0px")
        )
    return container

def Start(line_bot_api, event, db, lineuser, administrator):
    user_id = event.source.user_id
    reply_token = event.reply_token
    postback_msg = event.postback.data
    msg = postback_msg.split("@@@")  # @@@に変更する
    user = db.session.query(lineuser).filter_by(user_id=user_id).first()
    step = user.step

    if len(msg) == 2:
        post_msg = msg[0]  # YESかNO
        post_num = int(msg[1])  # 質問ナンバー

    elif len(msg) == 3:
        post_msg = msg[0]
        post_num = int(msg[1])
        data = msg[2]

    if post_msg == "admin_client_list_next":
        admin = db.session.query(administrator).filter_by(admin_id=user_id).first()
        page_options = admin.page_options
        admin.page_options = page_options + 1
        db.session.add(admin)
        db.session.commit()
        #admin_sight_client_list(user_id)

    elif post_msg == "admin_client_list_prev":
        admin = db.session.query(administrator).filter_by(admin_id=user_id).first()
        page_options = admin.page_options
        if page_options > 0:
            admin.page_options = page_options - 1
            db.session.add(admin)
            db.session.commit()
            #admin_sight_client_list(user_id)
        else:
            pass
            #admin_sight_client_list(user_id)

    elif post_msg == "admin_name":
        if post_num == 1:
            ad_name = data
            admin = db.session.query(administrator).filter_by(admin_id=user_id).first()
            admin.admin_name = data
            admin.adminstatus = None
            db.session.add(admin)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(text="%s さん、管理者登録ありがとうございます！"%ad_name))

    elif step == post_num:
        if post_msg == "name":
            user.username = data
            user.status = "tel"
            user.step = step + 1
            db.session.add(user)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(text="%sさん、ありがとうございます。"
                                                                         "次にお電話番号をご入力ください！"
                                                                         "※デモ用なので、本当の電話番号を入れないでください！！" % data))
        elif post_msg == "tel":
            user.tel = data
            user.step = step + 1
            user.status = "plan"
            db.session.add(user)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(
                text="%sさん、ありがとうございました。次にご利用になるプランをご選択ください。" % user.username))
            plan_ls = ["Aプラン", "Bプラン", "Cプラン", "GOLDプラン"]
            url_ls = ["A.png", "B.png", "C.png", "GOLD.png"]
            bubble_container = []
            for (p, url) in zip(plan_ls, url_ls):
                url_ = base_url + "static/" + url
                container = create_image_container(p, url_)
                bubble_container.append(container)
            bubble2 = CarouselContainer(contents=bubble_container)
            select_messa = FlexSendMessage(alt_text="プランを選択してください。", contents=bubble2)
            line_bot_api.push_message(to=user_id, messages=select_messa)

        elif post_msg == "plan":
            user.plan = data
            user.step = step + 1
            user.status = "message"
            db.session.add(user)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(
                text="%sさん、ありがとうございました。最後に何かメッセージがあればご入力ください！なしの場合、『なし』とご入力ください。" % user.username))

        elif post_msg == "message":
            user.usermessage = data
            user.step = step + 1
            user.status = "end"
            db.session.add(user)
            db.session.commit()
            line_bot_api.reply_message(reply_token, TextSendMessage(
                text="%sさん、ありがとうございました。これで申請が完了です！※そのまま、オペレーターに接続したい場合は、『接続』とご入力ください。" % user.username))

        ###  --------    有人対応処理　　----------    ####
        elif post_msg == "end":
            line_bot_api.reply_message(reply_token, TextSendMessage(
                text="オペレーター(製作者)に接続申請しています。そのまましばらくお待ちください。"))
            yestxt = "接続"
            id_ls = db.session.query(administrator.admin_id).all()
            for id_ in id_ls:
                try:
                    txt = "%s 様よりオペレーター接続申請が来ております。『接続』ボタンを押して対応をお願いします。" %user.username
                    bubble = BubbleContainer(
                        body=BoxComponent(
                            layout='horizontal',
                            margin="xs",
                            contents=[TextComponent(txt, size='md', wrap=True, type="text", margin="xs", )]),
                        footer=BoxComponent(
                            layout="horizontal",
                            spacing="sm",
                            contents=[ButtonComponent(style="link", height="sm",
                                                      action=PostbackTemplateAction(label=yestxt,
                                                                                    data="demand@@@4@@@" + user_id,
                                                                                    display_text=yestxt))], flex=0))
                    admin_access_message = FlexSendMessage(alt_text="対応申請が来ております。", contents=bubble)
                    line_bot_api.push_message(to=id_[0], messages=admin_access_message)
                except:
                    pass

        elif post_msg == "demand":
            to_user_id = data
            to_user = db.session.query(lineuser).filter_by(user_id=to_user_id).first()
            to_user.retention = user_id
            to_user.status = "response"
            db.session.add(to_user)
            db.session.commit()
            admin = db.session.query(administrator).filter_by(admin_id=user_id).first()
            admin.to_user = to_user_id
            db.session.add(admin)
            db.session.commit()
            adminuser = db.session.query(lineuser).filter_by(user_id=user_id).first()
            adminuser.status = "admin"
            db.session.add(adminuser)
            db.session.commit()
            adminname = admin.admin_name
            line_bot_api.push_message(to=to_user_id, messages=TextSendMessage(text="オペレーターの%s と接続されました"%adminname))
            line_bot_api.reply_message(reply_token, TextSendMessage(text="%s 様と接続されました。"%to_user.username))

        ###----------------------------------------------------------####
        else:
            line_bot_api.reply_message(reply_token, TextSendMessage(text="もう一度お願いします。"))
    else:
        if user.status == "end":
            line_bot_api.reply_message(reply_token, TextSendMessage(text="キャンセルしました。"))
        else:
            line_bot_api.reply_message(reply_token, TextSendMessage(text="それは最新ではありません！"))
```

長い！！！でも次で最後！

#『FollowAction.py』
なぜ最初にフォローアクションを描かなかったのか・・
まぁ最初しか実行されないコードだし、あってもなくてもいいかな、なんて

```python:FollowAction.py
from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage, PostbackEvent, ButtonsTemplate, PostbackTemplateAction, TemplateSendMessage, FollowEvent
    , LocationSendMessage, LocationMessage, ImagemapSendMessage, MessageTemplateAction, ConfirmTemplate, DatetimePickerAction, DatetimePickerTemplateAction
    , ImageMessage, ImageSendMessage, FlexSendMessage, BoxComponent, TextComponent, BubbleContainer, ButtonComponent,MessageAction
    , CarouselContainer, URIAction, ImageComponent, PostbackAction, CameraAction, CameraRollAction, QuickReplyButton, QuickReply
)

def Start(line_bot_api, event, db, lineuser, adminstrator):
    reply_token = event.reply_token
    line_bot_api.reply_message(reply_token, TextSendMessage(text="フォローありがとうございます！有人対応機能付きの予約システムです！"))
    user_id = event.source.user_id
    # ユーザー情報を登録する。
    if not db.session.query(lineuser).filter(lineuser.user_id == user_id).count():
        reg = lineuser(user_id, None, None, None, None, 0, None, None, None)
        db.session.add(reg)
        db.session.commit()
```

#お疲れ様でした
お疲れ様でした。
自分のメモ書きみたいなところも強いので、だれかの参考になればと思いました。

結構非効率な処理があると思いますが、まぁテストなんで問題ない！
と思って、爆速で作りました。
Python最高！！！