TypeScriptでSlack Appを作成してみたので、その方法の紹介です。
作成したアプリはこちら
https://github.com/irori-dev/SlackApp
前回までで、
- ローカルで動くところまで
- GAEへのデプロイと、GirtHub Actionsでデプロイできるように
- slackからGitHub APIを叩いて、GirtHub Actionsを実行できるように
という状態になっています。
今回は、Firestoreを活用してNotion APIとSlackとの連携をしようと思います😎
前回までの記事はこちら。
単語紹介
Firestore
Cloud Firestore は、Firebase と Google Cloud からのモバイル、ウェブ、サーバー開発に対応した、柔軟でスケーラブルなデータベースです。Firebase Realtime Database と同様に、リアルタイム リスナーを介してクライアント アプリ間でデータを同期し、モバイルとウェブのオフライン サポートを提供します。これにより、ネットワーク レイテンシやインターネット接続に関係なく機能するレスポンシブ アプリを構築できます。
https://firebase.google.com/docs/firestore
Notion
Notionは全てをAll-in-oneで管理し、かつユーザーそれぞれが柔軟にカスタマイズできるツール
https://www.notion.so/Notion-c14d4f434be24c98a2815e66bd98ddf2
まずはNotionのAPIを触れるようにする
https://www.notion.so/my-integrations
まずはこちらから、integrationを作成しましょう。
公式ドキュメントを参考に、作成してみてください。
その後、Tokenを発行して、.envに格納しましょう。
.env
NOTION_API_TOKEN=secret_*************
Firestoreを入れて、slack IDとNotionのIDを連携させる
NotionのIDを取得する
まずは、slackから呼び出したい対象のDBをwebで開きます。
この部分の?より前の文字列eae53f7e3…
がDBのIDになります。
Firestoreをimportする
次にFirestoreを導入します。
npm install firebase-admin
ただ、入れるだけではローカルで使えないので、ローカルで使える設定をしましょう。
サービス アカウント – IAM と管理 – Google Cloud Platformからプロジェクトを指定して、Firebase Admin SDKの「操作」から鍵を作成しダウンロードしておきます。
今回はそれをserviceAccountKey.json
という名前でプロジェクトルートに置いておくことにしましょう。
serviceAccountKey.json
には、databaseURLが入っていない可能性があります。
(僕の場合は入っていませんでした。)
入っていない場合、以下のように追加しておきましょう。
{
"type": "service_account",
"project_id": "sample",
"private_key_id": "66…",
"private_key": "-----BEGIN PRIVATE KEY-----\nMI…",
"client_email": "sample@appspot.gserviceaccount.com",
"client_id": "11…",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadat…",
"databaseURL": "https://sample.firebaseio.com" // ここです
}
.gitignore
に追加するのを忘れないようにしましょう。
FirestoreにIDのデータを入れる
GCP内のコンソールで、情報を以下のように入れます。
/
├── users
| ├ *****(UID)
| | ├ name
| | ├ notion_id
| | └ slack_id
| └ *****(UID)
└── notion
├ *****(UID)
| ├ title(ex: Tasks)
| └ id
└ *****(UID)
Firestoreのデータを呼び出す実装をする
次に、Firestoreのinitializersをアプリ内に追加しましょう。
import * as admin from 'firebase-admin'
if (process.env.NODE_ENV === `production`) {
admin.initializeApp({
credential: admin.credential.applicationDefault(),
})
} else {
const serviceAccount = require(`../../serviceAccountKey.json`)
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
})
}
export const firestore = admin.firestore()
次に、関数を作成しましょう。
- userのslackIDをNotionIDに変換する
- userのNotionIDをslackIDに変換する
関数をconvertIds
として作成します。
import { firestore } from '../initializers/firestore'
const notionIdToSlackId = async (id: string): Promise<string> => {
const targetUserRef = await firestore.collection('users').where('notion_id', '==', id).get()
const targetUser = await targetUserRef.docs.map(doc => {
return doc.data()
})[0]
return targetUser.slack_id
}
const slackIdToNotionId = async (id: string): Promise<string> => {
const targetUserRef = await firestore.collection('users').where('slack_id', '==', id).get()
const targetUser = await targetUserRef.docs.map(doc => {
return doc.data()
})[0]
return targetUser.notion_id
}
export { notionIdToSlackId, slackIdToNotionId }
次に、作成したNotionのDBのIDをfirestoreに入れたところから呼び出す関数をfetchNotionDbId
として作成します。
import { firestore } from '../initializers/firestore'
export default async (title: string): Promise<string> => {
const targetNotionDbRef = await firestore.collection('notion').where('title', '==', title).get()
const targetNotionDb = await targetNotionDbRef.docs.map(doc => {
return doc.data()
})[0]
return targetNotionDb.id
}
次に、Notion内のDB、Tasksを呼び出す関数、fetchTasks
を作成します。
import axios from 'axios'
import formatTaskResult from './formatTaskResult'
import fetchNotionDbId from './fetchNotionDbId'
interface Result { // ここはNotionに設定している値を元に作成します
url: string
title: string
startsAt?: string
endsAt?: string
assignee?: string
status?: string
[key: string]: any
}
export default async (bodyParameters: object): Promise<Result[]> => {
const tasksDbId = await fetchNotionDbId('Tasks')
const headers = {
headers: {
Authorization: `Bearer ${process.env.NOTION_API_TOKEN}`,
'Notion-Version': '2021-08-16'
}
}
try {
const results = await axios.post(`https://api.notion.com/v1/databases/${tasksDbId}/query`, bodyParameters, headers)
const array: Result[] = []
for (const result of results.data['results']) {
array.push(await formatTaskResult(result))
}
return array
} catch (error) {
return []
}
}
formatTaskResult
がまだありませんよね、こんな感じで作成します。
(ここは作成しているNotionの情報とリンクさせる必要があります、適宜読み替えてください。)
import { notionIdToSlackId } from './convertIds'
interface Result {
url: string
title: string
startsAt?: string
endsAt?: string
assignee?: string
status?: string
[key: string]: any
}
export default async (result: JSON): Promise<Result> => {
const regex = /[¥-]/g
const url = `https://www.notion.so/${result['id'].replace(regex, '')}`
const title = result['properties']['Name']['title'][0]['plain_text']
const startsAt = result['properties']['Starts at']['date'] != null ? result['properties']['Starts at']['date']['start'] : '未記入'
const endsAt = result['properties']['Ends at']['date'] != null ? result['properties']['Ends at']['date']['start'] : '未記入'
const assignee = await getNames(result['properties']['Asignee']['people'])
const status = result['properties']['status']['select'] != null ? result['properties']['status']['select']['name'] : '未設定'
return {
url: url,
title: title,
startsAt: startsAt,
endsAt: endsAt,
assignee: assignee,
status: status,
}
}
const getNames = async (people: JSON[]): Promise<string> => {
let mentions: string = ''
for (const person of people) {
const slackId = await notionIdToSlackId(person['id'])
mentions = `${mentions} <@${slackId}>`
}
return mentions
}
assigneeの部分では、ユーザーのIDをNotionIDからslackIDに変換をしています。
ついでに、slackのメンションが飛ぶように<@{slackId}>
としていますね。
そして、最後にslackアプリ内でこれらの関数を呼び出して返却するようにしましょう。
import { app } from '../initializers/bolt'
import fetchTasks from '../functions/fetchTasks'
export default (): void => {
app.message(`sample get all tasks`, async ({ _message, say }): Promise<void> => {
const taskTitles = await formattedText()
await say(taskTitles)
})
}
const formattedText = async (): Promise<string> => {
let text: string = ''
const bodyParameters = {
filter: {
property: 'status',
select: {
does_not_equal: 'DONE'
}
}
}
try {
const results = await fetchTasks(bodyParameters)
results.forEach((result) => {
text = `${text}\nassignee: ${result.assignee}, startsAt: ${result.startsAt}, endsAt: ${result.startsAt}, status: ${result.status}, <${result.url}|${result.title}>`
})
return text
} catch (error) {
return 'something happened'
}
}
これで、TaskのうちstatusがDONE以外のものをまとめて呼び出せます!
( irori get all tasks
を sample get all tasks
として呼び出してみてくださいね)
また、自分がAsigneeに入っているもののみを呼び出すsample get my tasks
も作成します。
import { app } from '../initializers/bolt'
import fetchTasks from '../functions/fetchTasks'
import { slackIdToNotionId } from '../functions/convertIds'
export default (): void => {
app.message(`sample get my tasks`, async ({ message, say }): Promise<void> => {
const userNotionId = await slackIdToNotionId(message.user)
const taskTitles = await formattedText(userNotionId)
await say(taskTitles)
})
}
const formattedText = async (userNotionId: string): Promise<string> => {
let text: string = ''
const bodyParameters = {
filter: {
and: [
{
property: 'status',
select: {
does_not_equal: 'DONE'
}
},
{
property: 'Asignee',
people: {
contains: userNotionId
}
},
]
}
}
try {
const results = await fetchTasks(bodyParameters)
results.forEach((result) => {
text = `${text}\nassignee: ${result.assignee}, startsAt: ${result.startsAt}, endsAt: ${result.startsAt}, status: ${result.status}, <${result.url}|${result.title}>`
})
return text
} catch (error) {
return 'something happened'
}
}
このように、bodyParametersを変えるだけで簡単に呼び出せますね。
次回は、slack内のテキストをNotionに保存させる機能を作成しようと思います!
お楽しみに〜😎