背景
-
普段、todo管理にNotionを使用している。
-
todoの登録を通勤時間とかスキマ時間にぱぱっとしたいことがある。(Notionをいちいち開くのはめんどくさい)
ゴール
こんな感じで、LineからNotionへtodoを登録し、種別の設定もできるようにする。
前提
- Lineをインストールしていること
- Notionをインストールしていること
- AWSのアカウントがあること
全体構成
LINEから、Lambdaを通してNotionへ。
手順
以下の手順で、LineとNotionを連携していきます。
- Notion APIの設定
- Line Messaging APIの設定
- AWS LambdaとLineの連携
- AWS LambdaとNotionの連携
Notion APIの設定
My-Integrationsを設定しシークレットを取得
My-Integrationsより、Integrationsの設定を行い、シークレットを取得します。
データベースをMy Integrationにコネクトする。
データベースを作成したら、画像のようにMyIntegrationをコネクトする。
データベースのIDを取得
https://www.notion.so/xxxxxxxxxxxxxx?v=yyyyyyyyyyyyyyyy
のxxxxxxxxxxxxxxがデータベースのIDとなります。
Line Messaging APIの設定
-
Messaging apiにログインをし、Providersを作成する。
- Channelを作成する。(Channel typeをMessaging APIとする)
AWS LambdaとLineの連携
AWS Lambdaの初期設定
新しい関数をpythonで作成する。
関数URLを設定し、publicでアクセスできるようにする。
AWS LambdaとLineの連携
先程設定した関数URLをLine Messaging APIのWebhook URLに入力する。
また、Use WebhookもONにしておく。また、Auto-reply messagesの応答メッセージもOFFにしておく。
Verifyを押して、「Success」が表示されたら連携成功。
これで、このMessaging APIにアクセスがあった時、AWS Lambdaの関数が呼び出されるようになった。
Lineから送ったメッセージ情報をLambdaで取得する
手元のスマホから、このチャンネルにメッセージを送ってみる
この状態で、AWS Lambdaのログを見てみると、
ログストリームにイベントが来ていることがわかる。つまり、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!')
}
LambdaとNotionの連携
つぎは、LambdaとNotionを連携していきます。
Notion APIの設定で取得したIntegrationのシークレットキーとデータベースのIDをLambdaの環境変数に追加していきます。
こんな感じになっていると思います。
これで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になるようにしています。
しっかり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から取得したカテゴリの候補が表示されます。
このカテゴリ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!')
}
すると、
こんな感じでtodoの追加、カテゴリの更新ができる。
手元のスマホからNotionのデータベースにデータを追加できるのは使い勝手が良いので、皆様も是非試してください。