[PYTHON] [AWS] Ich habe BOT mit LINE WORKS (Implementierung) in Erinnerung gerufen.

LINEWORKS Adventskalender Tag 14

Dieses Mal werden wir die Implementierung des Erinnerungs-BOT vorstellen, der in LINE WORKS Adventskalender Tag 7 eingeführt wurde.

[Repost] BOT-Bildschirm und Gesamtkonfiguration

review.png

Der Erinnerungs-BOT besteht aus drei Lambdas und ist in Python 3.7 implementiert.

① Verarbeitung von Nachrichten, die von LINE WORKS gesendet wurden, und Benachrichtigung an SQS ② In der Tabelle gespeicherte Abfrageereignisse und Benachrichtigung von SQS ③ Benachrichtigen Sie den LINE WORKS-Server über die von SQS empfangene Nachricht

Dieses Mal werde ich mich auf ① konzentrieren.

Statusübergangstabelle und Nachrichtenliste

Der BOT-Austausch mit dem Benutzer wird in der Zustandsübergangstabelle ausgedrückt. Der Erinnerungs-BOT behandelt die folgenden vier Ereignisse.

--Benutzerbeteiligung

table.png

Es verwaltet vier Zustände für jedes Benutzerereignis. BOT antwortet dem Benutzer mit einer Nachricht, die dem Benutzerereignis und dem Status des BOT entspricht. Der Inhalt der Nachricht wird als Nachrichtenliste definiert.

Lambda-Implementierung

Hier ist also die Lambda-Implementierung des Hauptthemas.

Die erste ist die Gesamtverarbeitung der Lambda-Funktion. Rufen Sie Ihre eigene Funktion "on_event" auf, die für die Validierung des Anforderungshauptteils und die Verarbeitung der Hauptnachricht verantwortlich ist. Die Validierung des Anforderungskörpers basiert auf dem Wert von "x-works-Signatur" im Header.

"""
index.py
"""

import os
import json
from base64 import b64encode, b64decode
import hashlib
import hmac

import reminderbot

API_ID = os.environ.get("API_ID")


def validate(payload, signature):
    """
    x-works-Signaturvalidierung
    """

    key = API_ID.encode("utf-8")
    payload = payload.encode("utf-8")

    encoded_body = hmac.new(key, payload, hashlib.sha256).digest()
    encoded_base64_body = b64encode(encoded_body).decode()

    return encoded_base64_body == signature


def handler(event, context):
    """
Hauptfunktion
    """

    #Körpervalidierung anfordern
    if not validate(event["body"], event["headers"].get("x-works-signature")):
        return {
            "statusCode": 400,
            "body": "Bad Request",
            "headers": {
                "Content-Type": "application/json"
            }
        }

    body = json.loads(event["body"])

    #Hauptnachrichtenverarbeitung
    reminderbot.on_event(body)

    return {
        "statusCode": 200,
        "body": "OK",
        "headers": {"Content-Type": "application/json"}
    }

Als nächstes folgt die Funktion on_event. Definieren Sie 4 Zustände, 4 Benutzerereignisse und eine Nachrichtenliste, die diesmal vorbestimmt sind, mit Konstanten.

"""
reminderbot.py
"""

import os

import json
import datetime
import dateutil.parser
from dateutil.relativedelta import relativedelta

import boto3
from boto3.dynamodb.conditions import Key, Attr

#Definieren Sie vier Zustände basierend auf der Zustandsübergangstabelle
STATUS_NO_USER = "no_user"
STATUS_WATING_FOR_BUTTON_PUSH = "status_waiting_for_button_push"
STATUS_WATING_FOR_NAME_INPUT = "status_waiting_for_name_input"
STATUS_WATING_FOR_TIME_INPUT = "status_waiting_for_time_input"

#Definiert basierend auf der Nachrichtenliste
MESSAGE_LIST = [
    "Hallo, ich erinnere Bot. Drücken Sie die Menütaste.",
    "Bitte geben Sie den Veranstaltungsnamen ein",
    "Drücken Sie die Menütaste.",
    "Klicken Sie hier für die Details der Veranstaltung!",
    "Bitte geben Sie die Veranstaltungszeit ein.",
    "Abschluss der Registrierung!",
    "Es ist ein Fehler. Bitte geben Sie es erneut ein.",
]

#Benutzerereignis als Postback-Ereignis definieren
#Stellen Sie bei der Registrierung des BOT-Menüs den Wert des folgenden Postback-Ereignisses ein.
POSTBACK_START = "start"
POSTBACK_MESSAGE = "message"
POSTBACK_PUSH_PUT_EVENT_BUTTON = "push_put_event_button"
POSTBACK_PUSH_GET_EVENT_BUTTON = "push_get_event_button"

#Tabelle, die den Status verwaltet
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("lineworks-sample-table")


def on_event(event):
    """
Behandlung des gesamten Ereignisses des Bots
    """

    account_id = event["source"]["accountId"]
    content = event["content"]

    postback =  content.get("postback") or "message"

    #Überprüfen Sie den aktuellen Status des Benutzers
    response = table.get_item(
        Key={
            "Hash": "status_" + account_id,
            "Range": "-"
        }
    )

    status = STATUS_NO_USER
    message = None
    
    if response.get("Item") is not None:
        status = response.get("Item")["Status"]
    
    #Jedes Benutzerereignis(postback)Verzweigungsverarbeitung für jeden
    try:
    
        if postback == POSTBACK_START:
            message = on_join(account_id, status)

        elif postback == POSTBACK_MESSAGE:
            text = content["text"]
            message = on_message(account_id, status, text)

        elif postback == POSTBACK_PUSH_PUT_EVENT_BUTTON:
            message = on_pushed_put_event_button(account_id, status)

        elif postback == POSTBACK_PUSH_GET_EVENT_BUTTON:
            message = on_pushed_get_event_button(account_id, status)

    except Exception as e:
        print(e)
        message = MESSAGE_LIST[6]
    
    #Benachrichtigen Sie SQS über den Nachrichteninhalt
    sqs = boto3.resource("sqs")
    queue = sqs.get_queue_by_name(QueueName="lineworks-message-queue")
    
    queue.send_message(
        MessageBody=json.dumps(
            {
                "content": {
                    "type": "text",
                    "text": message,
                },
                "account_id": account_id,
            }
        ),
    )

    return True

Schließlich die Implementierung der Verarbeitung für jedes Ereignis. In jedem Fall wird die Verzweigungsverarbeitung für jeden Zustand basierend auf der Zustandsübergangstabelle implementiert. Die doppelte Verarbeitung wird zusammengefasst.

def on_join(account_id, status):
    """
Ereignisbehandlung beim Hinzufügen eines Bots
    """

    #Filialverarbeitung nach Status
    if status == STATUS_NO_USER:

        table.put_item(
            Item={
                "Hash": "status_" + account_id,
                "Range": "-",
                "Status": STATUS_WATING_FOR_BUTTON_PUSH,
            }
        )
        return MESSAGE_LIST[0]
    
    else:

        table.delete_item(
            Key={
                "Hash": "status_" + account_id,
                "Range": "-"
            }
        )
        
        table.put_item(
            Item={
                "Hash": "status_" + account_id,
                "Range": "-",
                "Status": STATUS_WATING_FOR_BUTTON_PUSH,
            }
        )
        
        return MESSAGE_LIST[0]

def on_message(account_id, status, text):
    """
Behandlung von Ereignissen bei der Texteingabe
    """

    if status == STATUS_WATING_FOR_BUTTON_PUSH:

        table.put_item(
            Item={
                "Hash": "status_" + account_id,
                "Range": "-",
                "Status": STATUS_WATING_FOR_BUTTON_PUSH,
            }
        )
        return MESSAGE_LIST[2]

    elif status == STATUS_WATING_FOR_NAME_INPUT:

        table.update_item(
            Key={
                "Hash": "status_" + account_id,
                "Range": "-",
            },
            UpdateExpression="set #st = :s, Title = :t",
           	ExpressionAttributeNames = {
                "#st": "Status" #Status ist ein reserviertes Wort#Ersetzen Sie durch st
            },
            ExpressionAttributeValues={
                ":s": STATUS_WATING_FOR_TIME_INPUT,
                ":t": text,
            },
        )
        return MESSAGE_LIST[4]

    elif status == STATUS_WATING_FOR_TIME_INPUT:

        # dateutil.Konvertieren Sie Daten mit Parser
        time_dt = dateutil.parser.parse(text)
        time = time_dt.strftime("%Y/%m/%d %H:%M:%S")

        response = table.get_item(
            Key={
                "Hash": "status_" + account_id,
                "Range": "-",
            }
        )

        table.put_item(
            Item={
                "Hash": "event_" + account_id,
                "Range": time,
                "Title": response["Item"]["Title"],
                # utc ->Nehmen Sie einen Unterschied von 9 Stunden für die japanische Zeitumrechnung
                # utc ->Ursprünglicher Plan+Nach 1 Stunde löschen
                "ExpireTime": int((time_dt - relativedelta(hours=9) + relativedelta(hours=1)).timestamp()),
                "SentFlag": False
            }
        ),

        table.put_item(
            Item={
                "Hash": "status_" + account_id,
                "Range": "-",
                "Status": STATUS_WATING_FOR_BUTTON_PUSH,
            }
        )

        return MESSAGE_LIST[5]

def on_pushed_put_event_button(account_id, status):
    """
Ereignisverarbeitung, wenn die Schaltfläche "Ereignisregistrierung" gedrückt wird
    """

    if status == STATUS_WATING_FOR_BUTTON_PUSH:
    
        table.put_item(
            Item={
                "Hash": "status_" + account_id,
                "Range": "-",
                "Status": STATUS_WATING_FOR_NAME_INPUT,
            }
        )
        return MESSAGE_LIST[1]
    
    elif status == STATUS_WATING_FOR_NAME_INPUT:

        return MESSAGE_LIST[1]
    
    elif status == STATUS_WATING_FOR_TIME_INPUT:

        table.put_item(
            Item={
                "Hash": "status_" + account_id,
                "Range": "-",
                "Status": STATUS_WATING_FOR_NAME_INPUT,
            }
        )
        return MESSAGE_LIST[1]

def on_pushed_get_event_button(account_id, status):
    """
Ereignisverarbeitung, wenn die Schaltfläche "Ereignis durchsuchen" gedrückt wird
    """

    current_jst_time = (datetime.datetime.utcnow() + relativedelta(hours=9)).strftime("%Y/%m/%d %H:%M:%S")

    #Ereigniserfassungsprozess
    response = table.query(
        KeyConditionExpression=Key("Hash").eq("event_" + account_id) & Key("Range").gt(current_jst_time)
    )

    items = response["Items"] or []
    
    message = MESSAGE_LIST[3]

    if len(items) == 0:
        message += "\n-----"
        message += "\n Keine"
        message += "\n-----"

    for item in items:

        message += "\n-----"
        message += "\n Titel: {title}".format(title=item["Title"]) 
        message += "\n Datum und Uhrzeit: {time}".format(time=item["Range"]) 
        message += "\n-----"
    
    return message

Zusammenfassung

Welche Art von Verarbeitung sollte bei jedem Ereignis implementiert werden, indem eine Zustandsübergangstabelle erstellt wird? Da klar wurde, welche Nachricht zurückgegeben werden sollte, konnte ich sie ohne zu zögern umsetzen.

Diesmal war es eine einfache App, daher ist die Anzahl der Zustände und Ereignisse gering. Ich denke, dass die Zustandsübergangstabelle nützlicher ist, wenn Sie versuchen, BOT zu einer komplizierteren Verarbeitung zu bewegen.

Recommended Posts

[AWS] Ich habe BOT mit LINE WORKS (Implementierung) in Erinnerung gerufen.
[AWS] Ich habe BOT mit LINE WORKS daran erinnert
Ich habe einen Stempelersatzbot mit Linie gemacht
Ich habe einen LINE Bot mit Serverless Framework erstellt!
Ich habe versucht, LINE BOT mit Python und Heroku zu machen
Erstellen Sie mit Amazon Lex einen LINE WORKS-Bot
Mattermost Bot mit Python gemacht (+ Flask)
Ich habe einen Zwietrachtbot gemacht
Ich habe einen Twitter BOT mit GAE (Python) gemacht (mit einer Referenz)
Ich habe einen Wikipedia Gacha Bot gemacht
Ich habe mit Python eine Lotterie gemacht.
Bis Django etwas mit einem Linienbot zurückgibt!
Ich habe einen harten Pomodoro-Timer entwickelt, der mit CUI funktioniert
Ich habe mit Python einen Daemon erstellt
Ich habe einen Twitter-Bot mit Go x Qiita API x Lambda erstellt
Ich habe mit Python einen Zeichenzähler erstellt
Ich habe mit Python eine Hex-Map erstellt
Ich habe ein Lebensspiel mit Numpy gemacht
Ich habe einen Hanko-Generator mit GAN gemacht
Ich habe mit Python ein schurkenhaftes Spiel gemacht
Ich habe mit Python eine Einstellungsdatei erstellt
Ich habe eine WEB-Bewerbung bei Django gemacht
Ich habe versucht, "Sakurai-san" LINE BOT mit API Gateway + Lambda zu erstellen
Ich habe einen Slack-Bot geschrieben, der Verzögerungsinformationen mit AWS Lambda benachrichtigt
Ich habe mit Python eine Bot-Wettervorhersage gemacht.
Ich habe eine GUI-App mit Python + PyQt5 erstellt
Ich habe meinen Hund "Monaka Bot" mit Line Bot gemacht
Erstellen Sie mit Minette für Python einen LINE BOT
Ich habe einen Bot erstellt, um ihn auf Twitter zu posten, indem ich mit AWS Lambda eine dynamische Site im Internet abgekratzt habe (Fortsetzung).
Ich habe versucht, mit Python einen Twitter-Blocker für faule Mädchen zu machen
[Python] Ich habe mit Tkinter einen Youtube Downloader erstellt.
LINE BOT mit Python + AWS Lambda + API Gateway
Ich habe eine einfache Brieftasche aus Bitcoin mit Pycoin gemacht
Serverloser LINE-Bot mit IBM Cloud-Funktionen
Ich habe mit Numpy eine Grafik mit Zufallszahlen erstellt
Ich habe mit Python ein Bin-Picking-Spiel gemacht
Ich habe einen LINE BOT erstellt, der mithilfe der Flickr-API ein Bild von Reis-Terroristen zurückgibt
Ich habe einen Zeilenbot erstellt, der Python verwendet, um ungelesene E-Mails aus Google Mail abzurufen!
Ich habe einen LINE-Bot erstellt, der jeden Tag pünktlich empfohlene Bilder sendet
[Python] Ich habe einen LINE-Bot erstellt, der Gesichter erkennt und Mosaikverarbeitungen durchführt.
[Für Anfänger] Ich habe mit Raspberry Pi einen menschlichen Sensor erstellt und LINE benachrichtigt!
In Python habe ich einen LINE-Bot erstellt, der Polleninformationen aus Standortinformationen sendet.
Ich habe mit Play with Docker einen gebrauchsfertigen Syslog-Server erstellt
Ich habe mit Python ein Weihnachtsbaum-Beleuchtungsspiel gemacht
Ich habe mit Tkinter ein Fenster für die Protokollausgabe erstellt
Ich habe mit Python eine App für die Benachrichtigung über Netznachrichten erstellt
Lassen Sie einen Papagei LINE Bot mit AWS Cloud9 zurückgeben
Ich habe eine Python3-Umgebung unter Ubuntu mit direnv erstellt.
[Valentine Spezialprojekt] Ich habe eine LINE-Kompatibilitätsdiagnose gestellt!
[Super einfach] Machen wir einen LINE BOT mit Python.
Ich habe mit Sense HAT ein gefallenes Monospiel gemacht
Eine Geschichte, die stolperte, als ich mit Transformer einen Chat-Chat-Bot erstellte
Ich habe ein einfaches Tippspiel mit tkinter of Python gemacht
Erstellen Sie mit GoogleAppEngine / py einen LINE-Bot. Einfache nackte Version
Ich habe ein Paket erstellt, um Zeitreihen mit Python zu filtern
Ich habe versucht, LINE-Bot mit Python + Flask + ngrok + LINE Messaging API zu erstellen
Ich habe eine einfache Buch-App mit Python + Flask ~ Introduction ~ erstellt
Ich habe einen Ressourcenmonitor für Raspberry Pi mit einer Tabelle erstellt