9
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LineからNotionにタスクを登録する

Last updated at Posted at 2024-01-28

背景

  • 普段、todo管理にNotionを使用している。

  • todo管理のデータベースは以下のようになっており、
    実施する時間とカテゴリを設定できるようになっている。
    Screenshot 2024-01-28 9.34.36.png

  • todoの登録を通勤時間とかスキマ時間にぱぱっとしたいことがある。(Notionをいちいち開くのはめんどくさい)

ゴール

output.gif
こんな感じで、LineからNotionへtodoを登録し、種別の設定もできるようにする。

前提

  • Lineをインストールしていること
  • Notionをインストールしていること
  • AWSのアカウントがあること

全体構成

LINEから、Lambdaを通してNotionへ。

手順

以下の手順で、LineとNotionを連携していきます。

  1. Notion APIの設定
  2. Line Messaging APIの設定
  3. AWS LambdaとLineの連携
  4. AWS LambdaとNotionの連携

Notion APIの設定

My-Integrationsを設定しシークレットを取得

My-Integrationsより、Integrationsの設定を行い、シークレットを取得します。
Screenshot 2024-01-28 9.49.38.png

データベースをMy Integrationにコネクトする。

データベースを作成したら、画像のようにMyIntegrationをコネクトする。
Screenshot 2024-01-28 14.44.46.png

データベースのIDを取得

https://www.notion.so/xxxxxxxxxxxxxx?v=yyyyyyyyyyyyyyyy
のxxxxxxxxxxxxxxがデータベースのIDとなります。

Line Messaging APIの設定

  1. Messaging apiにログインをし、Providersを作成する。
    Screenshot 2024-01-28 10.04.14.png
  2. Channelを作成する。(Channel typeをMessaging APIとする)
    Screenshot 2024-01-28 10.05.15.png

AWS LambdaとLineの連携

AWS Lambdaの初期設定

新しい関数をpythonで作成する。

↓作成直後画面。
Screenshot 2024-01-28 10.12.26.png

関数URLを設定し、publicでアクセスできるようにする。

Screenshot 2024-01-28 10.14.24.png
Screenshot 2024-01-28 10.14.51.png
認証タイプはNONE。

AWS LambdaとLineの連携

先程設定した関数URLをLine Messaging APIのWebhook URLに入力する。
Screenshot 2024-01-28 10.17.44.png
Screenshot 2024-01-28 10.17.55.png

また、Use WebhookもONにしておく。また、Auto-reply messagesの応答メッセージもOFFにしておく。
Verifyを押して、「Success」が表示されたら連携成功。
これで、このMessaging APIにアクセスがあった時、AWS Lambdaの関数が呼び出されるようになった。

Lineから送ったメッセージ情報をLambdaで取得する

手元のスマホから、このチャンネルにメッセージを送ってみる
Screenshot 2024-01-28 10.33.17.png
この状態で、AWS Lambdaのログを見てみると、
Screenshot 2024-01-28 10.34.30.png
ログストリームにイベントが来ていることがわかる。つまり、Lineから送ったメッセージがMessaging APIを通して、AWS Lambdaに来ていることがわかった。

Lambdaから、Lineのメッセージの情報を取得したい。詳細は説明しませんが、以下のコードで取得できます。CHANNEL_SECRETとCHANNEL_ACCESS_TOKENは、messaging-apiの設定画面より取得してください。

プログラム例
import json
import base64
import hashlib
import hmac
import os

CHANNEL_SECRET = os.environ['CHANNEL_SECRET']
CHANNEL_ACCESS_TOKEN = os.environ["CHANNEL_ACCESS_TOKEN"]


def lambda_handler(event, context):
    #signatureの取得
    x_line_signature = event["headers"]["x-line-signature"] if event["headers"]["x-line-signature"] else event["headers"]["X-Line-Signature"]
    body = event["body"]
    hash = hmac.new(CHANNEL_SECRET.encode('utf-8'), body.encode('utf-8'), hashlib.sha256).digest()
    signature = base64.b64encode(hash)
    print("event")
    print(event)
    
    #認証
    if signature != x_line_signature.encode():
        print("signature error")
        return {
            'statusCode': 200,
            'body': {"signature": x_line_signature}
        }
        
    for evt in json.loads(body)['events']:
        #イベント情報の取得
        if(evt["type"] == "message"):
            #メッセージタイプなら
            text = evt['message']['text']
            print("receive message", text) #textを表示
        
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
これで、textに、Lineから送ったメッセージが格納されています。

LambdaとNotionの連携

つぎは、LambdaとNotionを連携していきます。
Notion APIの設定で取得したIntegrationのシークレットキーとデータベースのIDをLambdaの環境変数に追加していきます。
Screenshot 2024-01-28 14.42.29.png
こんな感じになっていると思います。
これでLambdaからNotionのデータベースにアクセス可能となります。

Lambdaを介してLineとNotionを連携する

LineからNotionのデータベースへの追加

Lineで入力した項目をNotionのtodoデータベースに追加していきましょう。
コードは以下のとおりです。
※Line Messaging APIのreplyの細かい説明は省きます。

プログラム例
import json
import base64
import hashlib
import hmac
import os
import urllib.request
import datetime
from dateutil import tz

CHANNEL_SECRET = os.environ['CHANNEL_SECRET']
CHANNEL_ACCESS_TOKEN = os.environ["CHANNEL_ACCESS_TOKEN"]
NOTION_SECRET = os.environ["NOTION_SECRET"]
ID_DB = os.environ["ID_DB"]

time_zone = tz.gettz('Asia/Tokyo')

now = datetime.datetime.now(tz=time_zone)
date_format1 = '%Y-%m-%dT12:00:00.000+09:00'
date_format2 = '%Y-%m-%dT13:00:00.000+09:00'


def lambda_handler(event, context):
    #signatureの取得
    x_line_signature = event["headers"]["x-line-signature"] if event["headers"]["x-line-signature"] else event["headers"]["X-Line-Signature"]
    body = event["body"]
    hash = hmac.new(CHANNEL_SECRET.encode('utf-8'), body.encode('utf-8'), hashlib.sha256).digest()
    signature = base64.b64encode(hash)
    print("event")
    print(event)
    
    #認証
    if signature != x_line_signature.encode():
        print("signature error")
        return {
            'statusCode': 200,
            'body': {"signature": x_line_signature}
        }

    for evt in json.loads(body)['events']:
        #イベント情報の取得
        if(evt["type"] == "message"):
            #メッセージタイプなら
            text = evt['message']['text']
            print("receive message", text) #textを表示
        
            url =  "https://api.notion.com/v1/pages"
            headers = {"content-type": "application/json", "Notion-Version": "2022-02-22", "Authorization": f"Bearer {NOTION_SECRET}"}
            json_data = {
                "parent": {"database_id": ID_DB}, 
                "properties": 
                {
                    "Name":
                    {
                        "title":
                        [
                            {
                                "text": {"content": text} #Lineで入力した項目をcontentに入れる
                            }
                        ]
                    },
                    "Date": {
                        "date": {
                            "start": now.strftime(date_format1),
                            "end": now.strftime(date_format2)
                        }
                    },
                }
            }
            req = urllib.request.Request(url, data=json.dumps(json_data).encode('utf-8'), method='POST', headers=headers)
            with urllib.request.urlopen(req) as res:
                json_data = json.loads(res.read())
                print(json_data)
                todo_id = json_data["id"].replace("-", "")
                
                reply_body = {
                    'replyToken': json.loads(body)['events'][0]['replyToken'],
                    'messages': [
                        {
                         'type': 'text',
                         'text': f"{text}を追加しました。id={todo_id}",
                       }
                    ]
                }
                headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN}
                url = 'https://api.line.me/v2/bot/message/reply'
                req = urllib.request.Request(url, data=json.dumps(reply_body).encode('utf-8'), method='POST', headers=headers)
                with urllib.request.urlopen(req) as res:
                    print(res.read().decode("utf-8"))
                    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

※日付は、入力した日の12:00-13:00になるようにしています。
Screenshot 2024-01-28 15.20.38.png
Screenshot 2024-01-28 15.13.21.png
しっかりtodoが入っていますね。ここで、todoのidを取得しているのですが、理由は後述します。

次は、追加したLabelを更新していきましょう。

カテゴリの更新

Lambdaで取得したNotionのLabelをLineで返答するようにしてみましょう。
※Line Messaging APIのreplyの細かい説明は省きます。

プログラム例
import json
import base64
import hashlib
import hmac
import os
import urllib.request
import datetime
from dateutil import tz

CHANNEL_SECRET = os.environ['CHANNEL_SECRET']
CHANNEL_ACCESS_TOKEN = os.environ["CHANNEL_ACCESS_TOKEN"]
NOTION_SECRET = os.environ["NOTION_SECRET"]
ID_DB = os.environ["ID_DB"]


def lambda_handler(event, context):
    #signatureの取得
    x_line_signature = event["headers"]["x-line-signature"] if event["headers"]["x-line-signature"] else event["headers"]["X-Line-Signature"]
    body = event["body"]
    hash = hmac.new(CHANNEL_SECRET.encode('utf-8'), body.encode('utf-8'), hashlib.sha256).digest()
    signature = base64.b64encode(hash)
    print("event")
    print(event)
    
    #認証
    if signature != x_line_signature.encode():
        print("signature error")
        return {
            'statusCode': 200,
            'body': {"signature": x_line_signature}
        }
        
    #Notionで、Label(カテゴリ)を取得する。
    categoryList = []
    url =  "https://api.notion.com/v1/databases/" + ID_DB
    headers = {"content-type": "application/json", "Notion-Version": "2022-02-22", "Authorization": f"Bearer {NOTION_SECRET}"}
    req = urllib.request.Request(url, method='GET', headers=headers)
    with urllib.request.urlopen(req) as res:
        json_data = json.loads(res.read())
        print(json_data)
        options = json_data["properties"]["Label"]["select"]["options"]
        for option in options:
            #option["name"]にLabelの種類が入っている。
            categoryList.append(option["name"])


    #Lineでカテゴリを選ばせる
    items = [] #LineのquickReplyで選ばせるitems
    for c in categoryList:
        items.append({"type": "action", "action": {"type": "message", "label": c, "text": c}})

    headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN}
    print(json.loads(body)['events'][0]['replyToken'])
    body = {
        'replyToken': json.loads(body)['events'][0]['replyToken'],
        'messages': [
            {
             'type': 'text',
             'text': "カテゴリを選んでください",
             "quickReply": {
                "items": items
              }
           }
        ]
    }
    url = 'https://api.line.me/v2/bot/message/reply'
    req = urllib.request.Request(url, data=json.dumps(body).encode('utf-8'), method='POST', headers=headers)
    with urllib.request.urlopen(req) as res:
        print(res.read().decode("utf-8"))
        

    #一旦コメントアウト        
    # for evt in json.loads(body)['events']:
    #     #イベント情報の取得
    #     if(evt["type"] == "message"):
    #         #メッセージタイプなら
    #         text = evt['message']['text']
    #         print("receive message", text) #textを表示
        
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

すると、下の画像のようにNotion APIから取得したカテゴリの候補が表示されます。
Screenshot 2024-01-28 14.57.11.png

このカテゴリ1、カテゴリ2、カテゴリ3のいずれかをクリックしたら、todoのLabelを更新するようにしたいです。
todoを追加→カテゴリ選択の流れにするため、カテゴリ選択時、todoのid情報もセットでLineに送ってやる必要があります。

ここで一工夫します。先程取得したtodoのidをLineのreplyに追加します。

items.append({"type": "action", "action": {"type": "message", "label": c, "text": todo_id + "&&&" + c}})

これで、ユーザーがLine上でカテゴリを選択した時、todoのidも一緒にLambdaに送られます。
Lambda上では、&&&を区切り文字として区切ってやることで、todoのidとカテゴリ情報の2つの情報を扱うことができます。

tmp = message.split("&&%")
# tmp[0]にはtodoのid情報が、tmp[1]にはカテゴリ情報が格納されている。

ただこのままだと、Lambdaはカテゴリを更新することができないので、

Lineのメッセージに"&&&"が含まれたらtodo更新する。
Lineのメッセージに"&&&"が含まれなかったらtodo追加する。

として処理します。

最終的なコードは以下のとおりです。

プログラム例
import json
import base64
import hashlib
import hmac
import os
import urllib.request
import datetime
from dateutil import tz

CHANNEL_SECRET = os.environ['CHANNEL_SECRET']
CHANNEL_ACCESS_TOKEN = os.environ["CHANNEL_ACCESS_TOKEN"]
NOTION_SECRET = os.environ["NOTION_SECRET"]
ID_DB = os.environ["ID_DB"]

time_zone = tz.gettz('Asia/Tokyo')

now = datetime.datetime.now(tz=time_zone)
date_format1 = '%Y-%m-%dT12:00:00.000+09:00'
date_format2 = '%Y-%m-%dT13:00:00.000+09:00'


def lambda_handler(event, context):
    #signatureの取得
    x_line_signature = event["headers"]["x-line-signature"] if event["headers"]["x-line-signature"] else event["headers"]["X-Line-Signature"]
    body = event["body"]
    hash = hmac.new(CHANNEL_SECRET.encode('utf-8'), body.encode('utf-8'), hashlib.sha256).digest()
    signature = base64.b64encode(hash)
    print("event")
    print(event)
    
    #認証
    if signature != x_line_signature.encode():
        print("signature error")
        return {
            'statusCode': 200,
            'body': {"signature": x_line_signature}
        }

    for evt in json.loads(body)['events']:
        #イベント情報の取得
        if(evt["type"] == "message"):
            #メッセージタイプなら
            text = evt['message']['text']
            if("&&&" in text): #&&&が入ってたら、複合メッセージ。&&&が入ってなかったら、タスクを登録する。
                tmp = text.split("&&&") #&&&で分ける
                url =  "https://api.notion.com/v1/pages/" + tmp[0]
                headers = {"content-type": "application/json", "Notion-Version": "2022-02-22", "Authorization": f"Bearer {NOTION_SECRET}"}
                json_data = {
                    "parent": {"database_id": ID_DB}, 
                    "properties": 
                    {
                        "Label": {
                          "select": 
                            {
                              "name": tmp[1]
                            }
                        }                             
                    }
                }
                req = urllib.request.Request(url, data=json.dumps(json_data).encode('utf-8'), method='PATCH', headers=headers)
                with urllib.request.urlopen(req) as res:
                    headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN}
                    body = {
                        'replyToken': json.loads(body)['events'][0]['replyToken'],
                        'messages': [
                            {
                             'type': 'text',
                             'text': "更新しました。",
                           }
                        ]
                    }
                    url = 'https://api.line.me/v2/bot/message/reply'
                    req = urllib.request.Request(url, data=json.dumps(body).encode('utf-8'), method='POST', headers=headers)
                    with urllib.request.urlopen(req) as res:
                        print(res.read().decode("utf-8"))
            else:
                url =  "https://api.notion.com/v1/pages"
                headers = {"content-type": "application/json", "Notion-Version": "2022-02-22", "Authorization": f"Bearer {NOTION_SECRET}"}
                json_data = {
                    "parent": {"database_id": ID_DB}, 
                    "properties": 
                    {
                        "Name":
                        {
                            "title":
                            [
                                {
                                    "text": {"content": text} #Lineで入力した項目をcontentに入れる
                                }
                            ]
                        },
                        "Date": {
                            "date": {
                                "start": now.strftime(date_format1),
                                "end": now.strftime(date_format2)
                            }
                        },
                    }
                }
                req = urllib.request.Request(url, data=json.dumps(json_data).encode('utf-8'), method='POST', headers=headers)
                with urllib.request.urlopen(req) as res:
                    json_data = json.loads(res.read())
                    print(json_data)
                    todo_id = json_data["id"].replace("-", "")
                    
                    #Notionで、Label(カテゴリ)を取得する。
                    categoryList = []
                    url =  "https://api.notion.com/v1/databases/" + ID_DB
                    headers = {"content-type": "application/json", "Notion-Version": "2022-02-22", "Authorization": f"Bearer {NOTION_SECRET}"}
                    req = urllib.request.Request(url, method='GET', headers=headers)
                    with urllib.request.urlopen(req) as res:
                        json_data = json.loads(res.read())
                        print(json_data)
                        options = json_data["properties"]["Label"]["select"]["options"]
                        for option in options:
                            #option["name"]にLabelの種類が入っている。
                            categoryList.append(option["name"])
                
                
                    #Lineでカテゴリを選ばせる
                    items = [] #LineのquickReplyで選ばせるitems
                    for c in categoryList:
                        items.append({"type": "action", "action": {"type": "message", "label": c, "text": todo_id + "&&&" + c}})
                
                    headers = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN}
                    print(json.loads(body)['events'][0]['replyToken'])
                    body = {
                        'replyToken': json.loads(body)['events'][0]['replyToken'],
                        'messages': [
                            {
                             'type': 'text',
                             'text': "カテゴリを選んでください",
                             "quickReply": {
                                "items": items
                              }
                           }
                        ]
                    }
                    url = 'https://api.line.me/v2/bot/message/reply'
                    req = urllib.request.Request(url, data=json.dumps(body).encode('utf-8'), method='POST', headers=headers)
                    with urllib.request.urlopen(req) as res:
                        print(res.read().decode("utf-8"))
                    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

すると、
output.gif
こんな感じでtodoの追加、カテゴリの更新ができる。
手元のスマホからNotionのデータベースにデータを追加できるのは使い勝手が良いので、皆様も是非試してください。

9
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?