爆速BrainDump：GAS編

# はじめに

最近、評判の高いNotionを導入し、個人的な情報やタスクを管理し始めました。
移動時間にはXを使って技術情報をチェックしたり、週末にやりたいことを思いついたら、Notionのデータベースにメモを残しています。
しかし、スマートフォンからNotionのデータベースに情報を入力しようとすると、クリック回数が多く（特にURLを入力する際には、クリックが多くなってしまいます）、利便性に欠けると感じました。
そこで、爆速（できれば1クリックで完了するような）でデータベースにメモを残せるような仕組みを構築したので、3回に分けて紹介していきます。

0. [Notion / Notion API](https://qiita.com/tkmsk/items/d0aeb61c3cb819d6bcc2)
0. Google Apps Script ← 今回
0. iPhone ショートカット

# アーキテクチャ構成

![アーキテクチャ図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/35e68fd5-ee40-bf2d-c579-ec8accc46121.png)

# 実装

Google Apps Script(GAS)でAPIを公開し、そのAPIにリクエストすることでNotion Databaseにレコードを追加するための設定を行います。

## 要約

* WebアプリとしてAPIを公開する
* API内でレコードのデータを補完する
    * URLを渡された場合、リンク先のタイトルを取得する
    * 時間はNotion APIをコールするときの時間を自動で付与する

## A. GASプロジェクトを作成

①任意の場所にGASプロジェクトを作成する
　ここでは「爆速BrainDump」フォルダを作成し、その中に作成した。
 
 ![gasプロジェクト作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/13b4e5e7-f3d5-1a63-7138-4ec7573547c9.png)

②注意喚起のポップアップが出た場合は、「スクリプトを作成」を押下
 
![gasプロジェクト作成_承認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/1742a6bd-a671-07da-ddcf-ffdd71f39639.png)

③GASプロジェクトの名前を任意の名前に変更する
　ここでは「爆速BrainDumpスクリプト」とした。

![GASプロジェクト名前変更前.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/5b5ed5be-31b1-5b46-4ee5-0f406c242538.png)

![GASプロジェクト名前変更後.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/8f83f830-daa7-a912-99e1-654ac9aff7b5.png)

## B. コーディング

コード.gsファイルの中を以下のように書き換える
※最初に書かれている`funcion myFunction() { }`は使用しないので削除してOK。
※コード量の削減のためにログ出力等は省略しています。

 ```javascript
function doPost(e) {
    const record = createRecord(JSON.parse(e.postData.contents));
    send2Notion(record);

    const output = ContentService.createTextOutput(JSON.stringify({ result: 'OK' }));
    output.setMimeType(ContentService.MimeType.JSON);
    return output;
}

function createRecord(body) {
    const isUrl = (str) => {
        return (str.startsWith('https://') || str.startsWith('http://'));
    }

    let title = '';
    let url = null;

    if (isUrl(body.message)) {
        url = body.message;
        
        const html = UrlFetchApp.fetch(url).getContentText('UTF-8');
        const titleStartString = '<title>';
        const titleStartNum = html.indexOf(titleStartString) + titleStartString.length;
        const titleEndNum = html.indexOf('</title>');
        
        title = html.substring(titleStartNum, titleEndNum);
    } else {
        title = body.message;
    }
    
    const record = {
        title: title,
        url: url,
        date: Utilities.formatDate(new Date(), 'JST', 'yyyy-MM-dd')
    };
    
    return record;
}

function send2Notion(record) {
    const createHeader = () => {
        const notionVersion = PropertiesService.getScriptProperties().getProperty('NOTION_VERSION');
        const notionApiKey = PropertiesService.getScriptProperties().getProperty('NOTION_API_KEY');
        
        return {
            'Accept': 'application/json',
            'Authorization': `Bearer ${notionApiKey}`,
            'Notion-Version': `${notionVersion}`,
            'Content-Type': 'application/json'
        };
    }

    const notionDatabaseId = PropertiesService.getScriptProperties().getProperty('NOTION_DATABASE_ID');
    const url = 'https://api.notion.com/v1/pages';
    
    const payload = {
        'parent': { 'type': 'database_id', 'database_id': notionDatabaseId },
        'properties': {
            'Title': {
                'type': 'title',
                'title': [{ 'type': 'text', 'text': { 'content': record.title } }]
            },
            'URL': {
                'url': record.url
            },
            'Date': {
                'type': 'date',
                'date': { 'start': record.date }
            }
        }
    };
    const option = {
        'method': 'POST',
        'headers': createHeader(),
        'payload': JSON.stringify(payload),
        'muteHttpExceptions': true,
    };
    UrlFetchApp.fetch(url, option);
}
```

### `doPost()`関数

Webアプリとしてデプロイした際、POSTメソッドでリクエストしたときにinvokeされる関数です。
POSTメソッドのリクエストボディは、`e.postData.contents`で取得できます。

この関数では、主に以下の3つの処理をしています。

0. `createRecord()`関数を使用して、Notionに保存するレコードを作成
0. `send2Notion()`関数を使用して、1で作成したレコードをNotionに送信
0. 正常終了のhtmlを作成して、クライアントへ返却

### `createRecord()`関数

クライアントからのリクエストボディからNotionのデータベースに登録するレコードを作成する関数です。

この関数では、主に以下のつの処理をしています。

0. `message`プロパティの値に応じて以下の処理をする
    * URLの場合
      `title`: サイトにアクセスして取得したサイトのタイトル
      `url`: `message`プロパティの値
    * URLでない場合
      `title`: `message`プロパティの値
      `url`: `null`
0. `date`に現在の時刻を入れる

:::note warn
`message`がURLでない場合、`url`には`null`を設定しないとNotion APIでエラーになる
:::

### `send2Notion()`関数

Notion APIをコールして、Notionのデータベースにレコードを登録する関数です。
[前回の記事で動作確認した際のNotion API仕様](https://qiita.com/tkmsk/items/d0aeb61c3cb819d6bcc2#notion-api%E4%BB%95%E6%A7%98)に則り、データをリクエストします。

## C. スクリプトプロパティの設定

Bのソースコードの中で`PropertiesService.getScriptProperties().getProperty('xxx');`と記述している箇所があります。
これは、GASプロジェクトのスクリプトプロパティから値を取得しています。
スクリプトプロパティに入れることで、再デプロイせずにレコードを追加するデータベースを変更することができます。

①プロジェクトの設定を開く

![スクリプトプロパティに追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/8bd20477-fe9a-e091-0551-235dde4b251b.png)

②スクリプトプロパティにプロパティと値を設定
　「スクリプトプロパティを追加」を押下し、
　・`NOTION_VERSION`: 公式リファレンスに記載されている最新のVersion
　・`NOTION_API_KEY`: インテグレーションのシークレット
　・`NOTION_DATABASE_ID`: レコードを追加するデータベースのデータベースID

![スクリプトプロパティを設定完了.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/25b1ea4c-88d7-78df-9278-52ddaf790cb2.png)

## D. デプロイ

ここまでで準備が完了したので、デプロイしてAPIを使用できるようにします。

①右上にある「デプロイ」ボタンから「新しいデプロイ」を押下

![デプロイ開始.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/81b0c071-f15c-abb7-1167-e819f350e422.png)

②デプロイ用の画面が表示されたら、「ウェブアプリ」を選択

![デプロイ種類選択.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/6f00c401-1b87-62ef-bd87-4a0619264df5.png)

③デプロイの詳細画面が開くので、アクセスできるユーザーを「全員」に設定して「デプロイ」ボタンを押下

![デプロイ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/87361867-2a08-b1c8-37b0-216753df2511.png)

:::note warn
デプロイ完了後、URLさえ知っていれば誰でもアクセスできるので、URLの取り扱いには充分に注意してください。
:::

④「アクセスを承認」ボタンを押下
　`UrlFetchApp.fetch()`関数を使用するので、アクセスを承認する必要があります。

![デプロイ承認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/6f4fd4ec-e098-342e-3b23-f12bbd896124.png)

⑤爆速BrainDumpスクリプトを実行するGoogleアカウントを選択

![スクリプトを実行するアカウントを選択.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/af7c8fb1-4485-5027-13f8-c311a8cc46da.png)

⑥全員に公開するので警告が表示されますが、承認を続けるので、「Advanced」を押下

![安全じゃない確認.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/5d42da28-eb55-95f5-b697-e2fba82ef837.png)

⑦「Go to 爆速BrainDumpスクリプト(unsafe)」を押下

![安全じゃない警告を無理やり突破.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/03e9dab6-7b9e-f7c7-df29-c8e678308bc5.png)

⑧「Allow」を押下

![GASスクリプトに許可を付与.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/055fb98d-6371-4620-4336-a08146f728c8.png)

⑨デプロイが完了したので、ウェブアプリのURLをコピーして、完了を押下

![デプロイ完了.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/40ad9b43-7da3-e8ee-d4c8-e2cb28ce2ac9.png)

# 動作確認

## GAS API仕様

### URL

エンドポイント：WebアプリのURL
メソッド：`POST`

### Body

```json-doc
{
    "message": "${message}" // ${message}: メモするデータ
}
```

## テストデータをリクエストする

API仕様の各変数に以下の値を設定して、リクエストする。

### URLをリクエストする場合

| 変数名 | 値 |
|:-:|:-:|
| message | 'https://qiita.com' |

### メモをリクエストする場合

| 変数名 | 値 |
|:-:|:-:|
| message | 'test' |

### リクエストする際に注意すること

GASのWebアプリの`POST`メソッドは、GCP側で`GET`メソッドにリダイレクトされます。
そのため、リクエストする際に少し注意が必要です。
ここでは、`curl`を使用する場合のリクエスト例を示します。

```bash
curl -d ${message} -L ${url}
```

## 結果

リクエストした内容でデータベースにレコードが登録されていることを確認する。

![動作確認結果_GAS.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1561074/3cc80a05-51c8-706a-393a-2d7441c0a546.png)

# 参考

### GASのWebアプリ仕様

https://developers.google.com/apps-script/guides/web?hl=ja

### `curl`でのGASのWebアプリでの`POST`の仕方

https://qiita.com/cajonito/items/9e66ef60831d51105bc0