{%hackmd @kuouu/dark %} # Slack-Notion 機器人 ###### tags: `AInimal` 目標: - 每天中午十二點自動在 slack 發日報 - 自動抓取在 Notion 上的新聞稿 - 小編只需要在時間內完成新聞稿內容即可 ## Slack API https://api.slack.com ### 創建 App(機器人) 1. 需要 workspace owner 開啟機器人的權限 2. 在 Basic Infomation 頁面設定基本資訊 3. 在 App Home 頁面設定機器人名稱 4. Install App 成功之後即可開始使用 ### 設定 Webhook 簡單來說,Webhook 就是一個讓網站能夠被動接收訊息的入口,不然通常網站是當有什麼需求的時候,由網站主動向後端或是其他網站來做互動。 - 在 Incoming Webhooks 頁面建立 Webhook - 不同 Workspace 會有不同的 Webhook,建議先建立對自己的或是認識的人的 - Webhook 會是以一個網址的形式呈現,當對這個網址發送請求時,會在相對應的頻道發送訊息 #### 發送訊息範例 ==請將 `YOUR_WEBHOOK_HERE` 換成你的 webhook!== ##### Python :::spoiler ```python= import requests import json url = YOUR_WEBHOOK_HERE content = {"text":"Hello, World!"} headers = {'Content-type': 'application/json'} r = requests.post( url, data=json.dumps(content), headers=headers ) print(r.content) ``` ::: ##### cURL :::spoiler ```shell curl -X POST -H 'Content-type: application/json' --data '{"text":"Hello, World!"}' YOUR_WEBHOOK_HERE ``` ::: ##### JavaScript :::spoiler ```javascript= const url = YOUR_WEBHOOK_HERE; const data = {"text":"Hello, World!"}; fetch(url, { method: 'POST', body: JSON.stringify(data), headers: new Headers({ 'Content-Type': 'application/json' }) }) ``` ::: ### 訊息排版 https://app.slack.com/block-kit-builder 到官方的圖形化介面來排版,並且把排版完的東西複製並取代掉剛剛的 `{"text":"Hello, World!"}`,就能看到你的機器人說出你要讓他說的話啦! > 也可以從官方的 Template 開始排,就不用花太多時間了! ## Notion API https://developers.notion.com ### 建立 Notion 的 Integration [更詳盡的官方教學](https://developers.notion.com/docs) 1. 到 [這裡](https://www.notion.so/my-integrations) 建立新的 Integration 2. 依照流程填寫基本資料,其他選項可以使用預設的就好 3. 完成之後複製 Secrets 中的 `Internal Integration Token`,之後所有的操作都會用到這個 4. 到你設定的頁面點擊 Share,並把這個 Integration 加進來 :::warning 必須是該頁面的 Admin 才可以使用這個 API 對該頁面做操作 ::: ### Notion 的資料結構 [官方文件](https://developers.notion.com/reference) 大致上會用到三種操作 - Query(取得) - Update(更改) - Create(新增) #### Database  Notion 可以在同個頁面加入很多物件,包含文字、表格、日曆等等。但它也提供**單一功能的頁面**選項,且將它命名為 Database(實際上也真的有點像是資料庫的形式),這邊雖然有很多種選項,後端儲存的形式卻都是一樣的東西! ##### 取得 Database 資料 ``` https://api.notion.com/v1/databases/DATABASE_ID/query ``` ##### Python :::spoiler ```python= import requests import json SECRET_TOKEN = YOUR_SECRET_TOKEN DATABASE_ID = YOUR_DATABASE_ID URL = f'https://api.notion.com/v1/databases/{DATABASE_ID}/query' headers = { 'Content-type': 'application/json', 'Authorization': f'Bearer {SECRET_TOKEN}', 'Notion-Version': '2021-08-16' } datas = {} r = requests.get(URL, data=json.dumps(datas), headers=headers) pretty = json.dumps(r.json(), indent=2) print(pretty) ``` ::: ##### 回傳值參考 :::spoiler ```json { "object": "list", "results": [ { "object": "page", "id": "179f7545-fe2a-470f-9e55-8c8798ae6888", "created_time": "2022-01-19T10:21:00.000Z", "last_edited_time": "2022-01-20T09:10:00.000Z", "cover": null, "icon": null, "parent": { "type": "database_id", "database_id": "52227748-83c7-46ac-bf0f-c1566266be93" }, "archived": false, "properties": { "Tags": { "id": "%5EUPa", "type": "multi_select", "multi_select": [ { "id": "870e6921-50c3-4aa9-8221-fa20a526def7", "name": "tag3", "color": "orange" } ] }, "Column": { "id": "urFX", "type": "rich_text", "rich_text": [ { "type": "text", "text": { "content": "text3", "link": null }, "annotations": { "bold": false, "italic": false, "strikethrough": false, "underline": false, "code": false, "color": "default" }, "plain_text": "text3", "href": null } ] }, "Name": { "id": "title", "type": "title", "title": [ { "type": "text", "text": { "content": "page3", "link": null }, "annotations": { "bold": false, "italic": false, "strikethrough": false, "underline": false, "code": false, "color": "default" }, "plain_text": "page3", "href": null } ] } }, "url": "https://www.notion.so/page3-179f7545fe2a470f9e558c8798ae6888" }, { "object": "page", "id": "367f93c9-4dd0-4964-94da-2cf79bccb7f2", "created_time": "2022-01-19T10:21:00.000Z", "last_edited_time": "2022-01-20T09:10:00.000Z", "cover": null, "icon": null, "parent": { "type": "database_id", "database_id": "52227748-83c7-46ac-bf0f-c1566266be93" }, "archived": false, "properties": { "Tags": { "id": "%5EUPa", "type": "multi_select", "multi_select": [ { "id": "9d40778a-b298-497e-bd54-79e8db76d94c", "name": "tag2", "color": "purple" } ] }, "Column": { "id": "urFX", "type": "rich_text", "rich_text": [ { "type": "text", "text": { "content": "text2", "link": null }, "annotations": { "bold": false, "italic": false, "strikethrough": false, "underline": false, "code": false, "color": "default" }, "plain_text": "text2", "href": null } ] }, "Name": { "id": "title", "type": "title", "title": [ { "type": "text", "text": { "content": "page2", "link": null }, "annotations": { "bold": false, "italic": false, "strikethrough": false, "underline": false, "code": false, "color": "default" }, "plain_text": "page2", "href": null } ] } }, "url": "https://www.notion.so/page2-367f93c94dd0496494da2cf79bccb7f2" }, { "object": "page", "id": "e1f5ad45-8523-4c66-aa38-244e1ffa7bc2", "created_time": "2022-01-19T10:21:00.000Z", "last_edited_time": "2022-01-20T09:10:00.000Z", "cover": null, "icon": null, "parent": { "type": "database_id", "database_id": "52227748-83c7-46ac-bf0f-c1566266be93" }, "archived": false, "properties": { "Tags": { "id": "%5EUPa", "type": "multi_select", "multi_select": [ { "id": "5016dcb9-80a4-493a-a14c-4b534f41690b", "name": "tag1", "color": "gray" } ] }, "Column": { "id": "urFX", "type": "rich_text", "rich_text": [ { "type": "text", "text": { "content": "text1", "link": null }, "annotations": { "bold": false, "italic": false, "strikethrough": false, "underline": false, "code": false, "color": "default" }, "plain_text": "text1", "href": null } ] }, "Name": { "id": "title", "type": "title", "title": [ { "type": "text", "text": { "content": "page1", "link": null }, "annotations": { "bold": false, "italic": false, "strikethrough": false, "underline": false, "code": false, "color": "default" }, "plain_text": "page1", "href": null } ] } }, "url": "https://www.notion.so/page1-e1f5ad4585234c66aa38244e1ffa7bc2" } ], "next_cursor": null, "has_more": false } ``` ::: :::info DATABASE_ID 可以在網址列的地方找到喔 ``` https://www.notion.so/a8aec43384f447ed84390e8e42c2e089?v=... |--------- Database ID --------| ``` ::: #### Page 在 Notion 的 Database 當中,每一列都是一個 Page,$\underline{Aa}$ 就是 Page 的名稱,可以點擊進入該頁面,且**該欄位不能夠被刪除**,而其他屬性(Properties)都是可以增減的。  那我們來嘗試更改 Page 的資料! ##### 更改 Page 資料  目標是想把 1/21 的日報 `Status` 改成 `Completed`,於是我們來使用 [Update page](https://developers.notion.com/reference/patch-page) 這個 api ``` https://api.notion.com/v1/pages/PAGE_ID ``` ##### Python :::spoiler ```python= import requests import json SECRET_TOKEN = YOUR_SECRET_TOKEN PAGE_ID = YOUR_PAGE_ID URL = f'https://api.notion.com/v1/pages/{PAGE_ID}' headers = { 'Authorization': f'Bearer {SECRET_TOKEN}', 'Notion-Version': '2021-08-16', 'Content-type': 'application/json', } datas = { "properties": { "Status": { "select": { 'id': '9708bf0f-f1c1-461f-8621-4d23a3dd2731' } } } } r = requests.patch(URL, data=json.dumps(datas), headers=headers) pretty = json.dumps(r.json(), indent=2) print(pretty) ``` ::: :::info 這邊我們必須要讓後端知道要改 Database 的哪一列(Page)跟哪一欄(Property) Page 的話是用 `PAGE_ID` 來指定,它跟 `DATABASE_ID` 很像,可以從網址列找到(p= 的後面那一串就是了)。 而哪一個欄位就必須多給後端 `--data` 這個參數,至於有什麼選項可以改,可以改成什麼就有很多不同的選擇了。 舉我的範例來說,我希望將 `Status` 這個欄位改成 `Completed`,那我就會去更改 Properties -> Status -> select -> id,把那個 id 改成 `Completed` 對應的 id,Notion 上就會有相對應的更動了。 ```json datas = { "properties": { "Status": { "select": { 'id': '9708bf0f-f1c1-461f-8621-4d23a3dd2731' } } } } ``` 而那個 Properties -> Status -> select -> id 又是怎麼來的呢:) 在前面取得 Database 的時候其實就可以看到後端回傳類似的東西,除了 Database 的屬性(properties)外,其實也給了每個 Page 的屬性,有什麼欄位,目前欄位的值是什麼。 >`PAGE_ID` 也可以用這種形式取得,不一定要去找網址 因此我們可以先取得 Database 之後,再看看有什麼欄位可以做更動,形式是什麼,可以做怎樣的操作,來決定要傳給後端什麼資料。 ::: #### Block  使用 Notion 的時候,會發現它輸入的內容會被分為一塊一塊的,並且用藍底來標示出該區塊,那就是所謂的 Block。 那就來試試看取得 1/20 裡面的 Block 資料吧! ##### 取得 Block 資料 ``` https://api.notion.com/v1/blocks/BLOCK_ID ``` ##### Python :::spoiler ```python= import requests import json SECRET_TOKEN = YOUR_SECRET_TOKEN BLOCK_ID = YOUR_BLOCK_ID URL = f'https://api.notion.com/v1/blocks/{BLOCK_ID}' headers = { 'Authorization': f'Bearer {SECRET_TOKEN}', 'Notion-Version': '2021-08-16', } datas = {} r = requests.get(URL, data=json.dumps(datas), headers=headers) pretty = json.dumps(r.json(), indent=2) print(pretty) ``` ::: ##### 回傳值參考 :::spoiler ```json { "object": "block", "id": "7c34c35c-4ca4-4af2-a215-0fce122d81df", "created_time": "2022-01-21T07:55:00.000Z", "last_edited_time": "2022-01-21T09:02:00.000Z", "has_children": true, "archived": false, "type": "child_page", "child_page": { "title": "1/20 \u65e5\u5831" } } ``` ::: :::danger 這邊會發現兩個問題: 1. 該去哪裡找 `BLOCK_ID`? 2. 暫且用 `PAGE_ID` 試試發現成功了,但怎麼只有傳一個 Block 回來 ::: ##### 該去哪裡找 `BLOCK_ID`? A:其實每個物件的 ID 都可以是 `BLOCK_ID`,因此使用 `PAGE_ID` 時才會成功取得這個 Page 的一些屬性資料 ##### 怎麼只有傳一個 Block 回來? A:因為這個 api 是取得 Block 的屬性資料,而不是他裡面的內容。 :::success 在 [官方文件](https://developers.notion.com/reference) 當中其實可以看到另一個叫做 [Retrieve block children](https://developers.notion.com/reference/get-block-children) 的 api,這才是我們要的! ``` https://api.notion.com/v1/blocks/BLOCK_ID/children ``` ::: ### 資料庫設計 利用上面的 api 就可以完成很多操作了,以一開始「日報」的目的來看,大致上的流程可以像是 ```graphviz digraph G{ "取得 Database 資料\n(拿到 Page 的 id)"->"取得 Page 資料\n(Page 的 Block children)"->"取得新聞稿內容" -> "更改 Page 的 Status" } ``` 依據這個流程來設計相對應的 Notion 筆記,就可以完成 Notion 部分的串接啦! :::info 我的設計 :::spoiler  ::: ## Deployment 目前想到兩個方法來部署,分別是 1. [CronJob](http://kejyun.github.io/Laravel-4-Learning-Notes-Books/laravel-cronjob/laravel-cronjob-README.html) 2. 讓他以普通的後端形式來運行 但是都需要一台機器來讓他跑:) ### CronJob #### 指令 ```shell # 編輯 crontab -e # 運行列表 crontab -l ``` #### 使用方式 ```shell # 每小時的第 18 分鐘執行 18 * * * * # 8 點 10 分執行 10 8 * * * # 8 點的每分鐘執行一次(共執行 60 次) * 8 * * * # 在每個禮拜二每小時的第 18 分鐘執行 18 * * * 2 # 每 15 分鐘執行一次 */15 * * * * # 每 2 小時執行 0 */2 * * * # 每 2 小時又 20 分鐘執行 */20 */2 * * * ``` ## Future Work - 錯誤處理 - 格式錯誤 - 當天沒有寫 - 機器壞掉 - 動態新增/減少主題 - 同步 Notion 樣式到 Slack
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up