Claris Connectで小説家になろう更新通知Discord botを作る

# 前置き
${\Huge\text	iPadください！！！！！！！！！！}$


# 前置き2
欲望に包まれてこんにちは。この記事は見るからに[Qiita Engineer Festa 2022](https://qiita.com/official-campaigns/engineer-festa/2022)のうち1テーマ、[Claris Connect を使った SaaS 連携ユースケースを紹介しよう！](https://qiita.com/official-events/eeadea1de45a11be944e)の賞品である**iPad Pro 256GB**に釣られて書かれたものですし、実際そうです。iPadください。
今回何で記事を書くかは色々悩んだんですが、[Claris Connectの公式ページ](https://www.claris.com/ja/connect/)によれば

>「App が多すぎて、カオス！」な状態はもうおしまい。

だそうなので、逆に言えば**なるべくカオスなApp**の開発に使うのが効果的かな、という考えに至りました。
そういうわけで今回は、「**何もしてないのに機能が増えた**」をはじめとする恐怖現象の温床……**Discord bot**の開発に、このツールを使用していこうと思います。

# 方向性を考えておく
とりあえず、作っていくDiscord botの方向性を決めておきましょう。とはいえClaris Connectはそれ自体がGUIなので、ちょうどフローチャートのように「組みながらどうするか決めていく」ことも可能だと思います。なので今回は、簡単に3つくらいの指針を決めておきます。

1. 渡されたncode[^1]を持つ作品について定期的に[小説家になろう](https://syosetu.com)を**巡回**して
2. 更新があったら関連データを**取得**して
3. DiscordにWebhook経由で**投稿**する

# 実装
## 1. APIを叩く部分
### 1-1. 定期的に実行する
まず、巡回のための定期実行にあたる部分を作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/e8290b90-14f2-05a4-3f40-3da2b18c7903.png)
この画像における左下の`Schedules`を追加して進むと、遷移先の画面で発火頻度を聞かれます。今回はとりあえず**2分に1度**の頻度で実行するので、`Custom (advanced)`を選択します。
そこから進むとCron Expressionで頻度を入力するよう促されるので、「**毎時偶数分目**」を意味する
```
0/2 * * * *
```
を突っ込みます。
タイムゾーンには普通に`Asia/Tokyo`を入力して、あとはSave Triggerでトリガーの追加が完了します。

### 1-2. ストレージを読む
#### 1-2-1. 前置き
とりあえず実行上のデータを保持するために**ストレージ**を作ります[^2]。
[連携アプリケーションリスト](https://www.claris.com/connect/apps/)で「Database」「Cloud Storage」あたりのカテゴリを見ると[AWS S3](https://aws.amazon.com/jp/s3/)だの[Box](https://www.box.com)だの[FileMaker Cloud](https://www.claris.com/ja/filemaker/cloud/)だの色々なサービスがヒットします。Claris Connectを使ってることを考えるとFileMaker Cloudが王道かなと思うんですが、今回はそこまで大規模なデータを扱うわけでもないので、**Google Sheets**で運用しようと思います。

#### 1-2-2. ファイルを作っておく
一度Google Driveを開き、`NekochanRealSaikyou`などの**適当な名前**でGoogle Sheetsファイルを作成します。コンテキストメニューから共有用URLを取得すると、
```js
`https://drive.google.com/file/d/${ID}/view?usp=sharing`
```
みたいな文字列がクリップボードに来るので、この`ID`にあたる文字列を適当な手段で保管しておきます。
`NekochanRealSaikyou`には、とりあえず

- `config`シート：実行に関わる設定を保管するシート
- `novels`シート：監視対象の小説の情報を保管するシート

の二つを作成しておきます。
`novels`シートについては、何も書いておかないのもわかりにくいので、小説家になろうのトップページにある月間ランキングの上位5作品(執筆当時)のncodeを入力しておきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/caf30ea8-f505-9d78-3411-505f67588c9d.png)


#### 1-2-3. 取得の準備
これは罠なんですが、Claris ConnectのGoogle Sheetsモジュールには、ファイルを編集するコマンドはあっても**取得するコマンドはありません**。Google Driveモジュールも同様です[^3]。
ではどうやってSheetsのデータを持ってくるかというと、**[Google Cloud Platform](https://console.cloud.google.com)（GCP）のAPI**を叩くことになります。
詳しくは[この記事](https://qiita.com/tyuma/items/46cbb9d4e3d078e661dd)に譲るとして、今回やるべきは

1. GCPで適当なプロジェクトを作る
2. プロジェクト内で[Google Sheets API](https://console.cloud.google.com/marketplace/product/google/sheets.googleapis.com)を有効化する
3. **APIキー**を作成する
4. `NekochanRealSaikyou`を**リンクを知っている全員**を対象として共有する

という工程です。
この状態で、例えば
```js
`https://sheets.googleapis.com/v4/spreadsheets/${ID}/values/novels?key=${KEY}`
```
にアクセスすれば、`novels`シートの内容がjson形式で返ってきます。

#### 1-2-4. 取得
そういうわけで、実際に取得してみましょう。
これも罠なんですが、Claris Connectの`Webhook`モジュールは**実はWebhookではありません**。なぜそう言えるかというと、クリックした瞬間に
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/15ee9313-2fea-3817-ac6d-a1bcc53a0e67.png)
これが出てくるからです。
いや、**HTTP GET request**ではないだろ！[^4]という感が相当強いですが……まあいいでしょう。ここからGET requestを選択した状態でContinueして、前述のAPIアクセス用URLを入力する作業を`config`と`novels`で1回ずつ行います。
返ってきたデータを`Convert to JSON format`する必要があるかと思ったんですが、どうもデフォルトでjson形式として認識されているようなので割愛します。

### 1-3. APIを叩く
#### 1-3-1. 理屈
小説家になろうの更新を感知する方法はいろいろあります。具体的に言うとスクレイピング・Atomフィード・なろう小説APIなどですが、今回はそのうちAPIを使用します。
まず、今回取得すべきデータについて考えると
- 事前入力されたncodeを持つ作品が対象(=**検索は必要ない**)
- 念のため出力上限はできるだけ大きく
- 作品そのものに付随するデータとして必要なのは、
    - **最終更新日時**(更新されたことを確認するため)
    - **話数**
    - **文字数**(前回更新からの差分を表示するため)
    - **ncode**
    - **タイトル**
    - **作者名**

といったことが言えます。
[公式リファレンス](https://dev.syosetu.com/man/api/)をもとにこれを実装することを考えると、
`https://api.syosetu.com/novelapi/api/`のクエリを

- `out`:`json`
- `ncode`:*対象となるncodeを`-`で連結した文字列*
- `of`:`gl-ga-l-n-t`
- `lim`:`500`

に設定してGETすればいいでしょう。

#### 1-3-2. 実装
まず、前述の**対象となるncodeを`-`で連結**する工程をやります。
先ほどの仮入力例でいくと、Google Sheets APIの`novels`はこういうjsonを返してきます。
```json
{
  "range": "novels!A1:Z1000",
  "majorDimension": "ROWS",
  "values": [
    [
      "n8703hn"
    ],
    [
      "n5082hp"
    ],
    [
      "n6634hp"
    ],
    [
      "n4481hp"
    ],
    [
      "n8088hp"
    ]
  ]
}
```
基本は`values`内に入っている二次元リストから、ncode列(0列)に入っている文字列を抽出し、`-`で連結すればいいでしょう。
Claris Connectにも多分通常のプログラミング言語で言うところの`map`文がありそうな気がするんですが、今回は**見つけられなかった**ので気合いでループ実装します。
ここからはダイジェストでお送りします。

##### 結合用変数を作る
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/54b53347-24c6-03d0-2e3d-ca5e3ab3c0f9.png)
Claris Connectの`Variable`を追加、名前は`linked_ncodes`にしておきます。
この時どうやら**空文字列が使えない**っぽいので、先ほどAPIから取得した`novels`シートのうち**最初の行**のncodeを突っ込んでおきます。

##### ループを設定する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/539fa696-eff8-ac49-c496-c2f0aa84dcff.png)
Claris Connectでは追加メニューから`Repeat`を選択することで**ループ**を設定することが可能です。
`novels`シートのすべての行に対してforEachをかけるイメージですね。

##### `index==0`を弾く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/15f09ab3-d0bb-66bf-f570-4be41ec68d4b.png)
`linked_ncodes`の宣言時に既に最初の行のncodeは追加済みなので、`index==0`の場合も処理をすると同じncodeが**二重登録**されてしまいます。それを防ぐため、Claris Connectの`if-Then`で条件を付けています。
やっぱりこういうのはあんまりスマートじゃない感じがあるので、もうちょっといい方法がある気がしますね……

##### `linked_ncodes`に追記する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/710a588a-5531-a0ef-686a-f2840d0fe42f.png)
先ほどの`if-Then`の中に追記処理を入れます。
外見的には単純明快、`linked_ncodes`の中身と`-`とRepeatから渡された行内容のncode列を結合した結果を、`linked_ncodes`そのものに代入しています。
Claris Connectのこういうところは良いですね、文字列結合が外見的にわかりやすく行えます。

##### APIを叩く
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/d82b423e-4f61-c52e-2921-2034f0777841.png)
一度ifからもループからも抜けまして、`HTTP GET request`を追加します。
基本的には『理屈』のセクションに書いてある内容をそのまま実装している感じですね。
こういうめんどくさくなりがちなクエリ処理をGUIでできるのは結構楽しいと思います。

最終的な全体像はこうなります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/f3ee9550-8382-d9d2-5278-c6ce175c2348.png)


#### 1-3-3. 確認
ここまでで理論上は**小説家になろうAPIを叩く**処理が完成したはずなので、本当にできているか確認してみましょう。
`Flows`メニューを開いて今まで編集していたフローの横のラジオボタンをオン、フローに飛んで右上の`Run Now`を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/32217866-2263-d35e-09b3-eeeeeb29ef44.png)
すると`History`に飛ばされますので、実行を待った後履歴の個別リンクを押します。8番の`HTTP GET Request`命令のレスポンスを確認すると……
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/b096f38e-31dd-08e9-9bc6-a79b85ead706.png)
うまいことAPIが叩けているようです！良さそうですね。

### 1-4. 更新確認
#### 1-4-1. データ構造
とりあえずGoogle Sheetsに保管するデータについて、もう少し厳密に構造を考えます。
まあそうはいっても、基本的に更新確認は**比較**によって行われるので、APIを叩いた結果出てきた諸々をそのまま横に並べればいいでしょう。
この形状を一行として、作品数だけ下に伸ばしていく感じで行きましょう。
```json
["ncode","title","general_lastup","general_all_no","length"]
```

#### 1-4-2. 実装
##### 問題
基本的に先ほどAPIを叩いて出てきたデータを`Repeat`で回すだけ……**ではありません**。
あのですね……これは良くないところだと思うんですけど、Claris Connectは持ってきたjsonデータの加工にあんまり向いていないように見えます。FileMakerとの併用が前提だからだとは思うんですが、しかしもう少し何とかしてほしいところがあります。
何が問題かというと、返ってくるjson内の**最初の要素**です。
```jsonc
[
  {"allcount":887041},
  {
    "title":...,
    "ncode":...
  },
...
]
```
この`allcount`を持っているオブジェクトを、Claris Connectは配列における**典型**と認識します。つまり、この状態で`Repeat`を設定しようとすると……
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/759a595c-48a2-7b24-e324-623a99293948.png)
ご覧の通り、`item`のキーには **`title`も`ncode`もありません**。
典型というのはこういうことです。`Repeat`は回される要素の持つキーについて、『最初の要素』を参照して推測する……そのため、最初の一件だけ`allcount`以外のキーを持たない今回の場合相性が悪いのです。
これをどうにか解決しましょう。

##### `Remove item from list`について
この課題の解決にあたり最初に思いつくのは、`Lists`モジュールの`Remove item from list`コマンドを使うことです。
このコマンドは「任意のリストの任意番目のアイテムを**削除**してくれる」という非常にゴキゲンな存在です。これを使って取得したデータのうち**0番目**のみを削除すれば、特殊なキー内容を持つ要素が消えて良さそうです。実際にやってみましょう！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/35df9294-1f2c-29d3-1afa-ee82e93e679f.png)
**使えません**。
はい。
細かい理屈はわからないんですが、たぶんリスト内に存在する連想配列をこのコマンドでは解釈できないため、文字列化して扱ってしまった……みたいな感じだと思います。さすがにこれはもうちょっと何とかならなかったのか、という思いは割とありますね……。

##### ではどうするのか
こうします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/95ae1ea3-661c-b0a5-9289-903828b0aed5.png)
ご覧の通り、`Documents`モジュールのコマンド二つを使って、jsonを**一度csvに変換し、そのcsvをさらにリストに変換する**処理をしています。

`9 Convert JSON to CSV`のオプションは、
- `JSON` : step8の`data`
- `delimiter` : `,`
- `Column title` : `false`
- `Flatten` : `false`

`10 Convert text to list`のオプションは、
- `JSON` : step9の`csv`
- `Separator` : `\n`

をそれぞれ指定しています。
この処理を挟むことで、最終的に
<details><summary>これが</summary>

```json
[
    {
        "allcount": 5
    },{
        "title": "真の聖女らしい義妹をいじめたという罪で婚約破棄されて辺境の地に追放された騎士好き聖女は、憧れだった騎士団の寮で働けて今日も幸せ。【書籍化・コミカライズ決定】",
        "ncode": "N6634HP",
        "general_lastup": "2022-06-08 17:04:30",
        "general_all_no": 48,
        "length": 100518
    },{
        "title": "婚約者が浮気相手と駆け落ちしました。色々とありましたが幸せなので、今さら戻りたいと言われても困ります。【外伝更新中】",
        "ncode": "N5082HP",
        "general_lastup": "2022-06-07 20:00:00",
        "general_all_no": 45,
        "length": 125187
    },{
        "title": "天才魔術師を弟に持つと人生はこうなる",
        "ncode": "N8703HN",
        "general_lastup": "2022-06-06 11:00:00",
        "general_all_no": 57,
        "length": 136290
    },{
        "title": "「お前が代わりに死ね」と言われた私。妹の身代わりに冷酷な辺境伯のもとへ嫁ぎ、幸せを手に入れる",
        "ncode": "N4481HP",
        "general_lastup": "2022-05-08 13:51:59",
        "general_all_no": 16,
        "length": 61510
    },{
        "title": "前世魔術師団長だった私、「貴女を愛することはない」と言った夫が、かつての部下",
        "ncode": "N8088HP",
        "general_lastup": "2022-05-06 21:25:01",
        "general_all_no": 1,
        "length": 6319
    }
]
```

</details>

<details><summary>こうなります。</summary>

```json
[
    "5,,,,,",
    ",'真の聖女らしい義妹をいじめたという罪で婚約破棄されて辺境の地に追放された騎士好き聖女は、憧れだった騎士団の寮で働けて今日も幸せ。【書籍化・コミカライズ決定】','N6634HP','2022-06-08 17:04:30',48,100518",
    ",'婚約者が浮気相手と駆け落ちしました。色々とありましたが幸せなので、今さら戻りたいと言われても困ります。【外伝更新中】','N5082HP','2022-06-07 20:00:00',45,125187",
    ",'天才魔術師を弟に持つと人生はこうなる','N8703HN','2022-06-06 11:00:00',57,136290",
    ",'「お前が代わりに死ね」と言われた私。妹の身代わりに冷酷な辺境伯のもとへ嫁ぎ、幸せを手に入れる','N4481HP','2022-05-08 13:51:59',16,61510",
    ",'前世魔術師団長だった私、「貴女を愛することはない」と言った夫が、かつての部下','N8088HP','2022-05-06 21:25:01',1,6319"
]
```

</details>

これをRepeatでループして、適宜リスト化していく感じで行きましょう。

##### ループ基盤
さて、回していきましょう。
まず先ほどncodeを結合した時と同じように`Repeat`を追加、`index==0`を`If`で弾きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/827b7af2-5143-4b5e-4667-d1a44fd4fc58.png)

##### 表の整形
先ほど、Google Sheetsの`novels`が持つデータはこういう内容だと書きました。
```json
["ncode","title","general_lastup","general_all_no","length"]
```
ですが、現時点で存在するデータはこういう並びです。
 ```json
["空要素","title","ncode","general_lastup","general_all_no","length"]
```
下を上に整形するために手っ取り早い工程は、

1. 0番目の要素(空要素)を2番目の要素(`ncode`)で**置換**する
2. 2番目の要素を**削除**する

という流れでしょう。
それをやっているのがこの部分です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/7273d163-49c5-d570-c5cf-41230d5ac487.png)
13番でcsv状態のテキストをリストに変換し、14番と15番で前述の工程をやっています。
ここまでの処理を走らせてみたところ、`History`のログでは
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/c61b9e0a-c304-4760-cdda-65c27614c7a5.png)
こういうオブジェクトが帰ってきているようなので、たぶん大丈夫だと思います。

##### 対応するncodeのローをテーブルから探す
忘れがちなんですが、Google Sheets APIから返ってきたのはあくまでも**二次元配列**にすぎません。つまり、連想配列のようにキーを渡せばバリューが帰ってくるわけではなく……特定のncodeを持つ行を取得するためには**ループを回す**しかない、ということになります[^5]。
ということでstep3で取得したデータのうち`values`を`Repeat`し、`If`で`Equal to Case-Insensitive`(大文字と小文字を区別しないで比較)を使います。
これにより、step11のループとstep16のループで回されている`ncode`が**一致した場合**のみの処理を行うようにできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/089860d8-fa6a-374c-0c98-295a7408c5e7.png)

##### 更新されているかどうかを確認する
ついにこのフェーズですよ！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/2a30803f-c2f9-f1dc-5128-85bb6ce42fea.png)
まず、`flag`という名前で`variable`を作っておきます。判定処理を経て、これが`-1`なら更新されていない、`1`なら更新されている……という寸法でいきます。

更新確認は、

- 最終更新時刻`general_lastup`が前回の取得から変化している
- そもそも`general_lastup`が設定されていない

のうちどちらかの条件を満たすことで行われます。
まず、後者から実装していきましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/f02b1cb3-051a-aafa-8f41-9980ee34c9ae.png)
いちいちスクショを貼るのも面倒なので、3つの処理をまとめて解説します。ここでは、

- **19:** `novels`シートのうち処理中の行の**長さ**を取得
- **20:** 取得した長さが3未満、つまり **`general_lastup`の列を含まない**なら、条件分岐
- **21:** フラグを`1`に設定

という処理をしています。

次に前者です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/cfa83a99-810d-d67f-fa3e-46e97e034c27.png)
ここでは、

- **22:** `novels`シートのうち処理中の行の`general_lastup`列に位置する要素を取得
- **23:** 取得した要素が**APIから得られる内容と違う**なら、条件分岐
- **21:** フラグを`1`に設定

という処理です。

### 1-5. 更新時処理
#### 1-5-1. 実装
##### シートを書き換える処理
いよいよ、**更新された場合の処理**です！
まず`flag`が立てられているかで条件分岐します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/5b8202b5-71f5-5745-ca24-6665a3649d5d.png)
分岐内で`A,B,C,D,E`を内容としたリストを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/71d4c8b7-9a19-6fa0-d8d6-16b400d0c85d.png)
これはClaris ConnectのGoogle Sheetsモジュール用のリストです。作っている途中に気づいたんですが、どうも`Update spreadsheet row`は**行番号を変数で指定できない**という謎仕様があるみたいなんです！
`Update cell`では普通に変数指定が可能なのでこちらを使うことになりますが、それにあたり`A1`とか`C5`みたいな感じの指定方法をとる必要があって、それに使うためのアルファベットリストなわけですね。
ここからはダイジェストです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/1a930501-3c71-02a2-4e43-8bf6de91b464.png)

- **27:** API側の処理中の行が持つ`ncode,title,general_lastup,general_all_no,length`の5項目を`Repeat`
- **28:** 先ほどの`A,B,C,D,E`のリストから、step27で処理中の項目に対応するものを抜き出す
- **29:** Google Sheetsの行数は`1`から始まるので、行数カウンタに`1`を足しておく
- **30:** step28とstep29を組み合わせたセルに書き込む

これで、いよいよDiscordに投稿する部分だけです！

#### 1-5-2. 残念なお知らせ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/18cb8e58-f1d6-7109-053b-55d8ed7e0af6.png)
**APIのリクエスト制限に達しました**。
はい。
………………まあ、テストランができなくなっただけだと考えましょう。

#### 1-5-3. Discord側のWebhook設定
まあ、気を取り直して……取り直せるかこれ？……取り直して、やっていきましょう。
Discordの適当な自分に管理権限があるサーバーで、適当にチャンネルを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/fdfd9eaa-7eeb-04bb-6c16-eb207b4236db.png)
いかにも精神状態が反映されたチャンネル名ですね。


ここからチャンネル設定を開いて、『連携サービス』を押し……
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/3d6614ef-b20d-3542-7b6b-11a7e538e9ed.png)
『ウェブフックを作成』を押すとWebhookが作れます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/102e36b2-77dd-5271-6fc6-5b4fecd3d504.png)
『ウェブフックURLをコピー』を押し、出てきたやつを`NekochanRealSaikyou`の`config`シートの1行目にでも置いておきます[^6]。

#### 1-5-4. 投稿用の準備
いろいろ準備をします。

##### 文字数差分
「何文字増えました」みたいなのを知らせる機能が欲しいので、増えた文字数を取得します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/40151822-a48c-1f22-2092-64e10104d562.png)

- **31:** `length`列を取得
    - どうも前述の「**典型**」現象が発生した状態でAPIが使えなくなったせいで、アイテムからインデックス指定で取り出すにはいちいちこれをやる必要があるっぽいです。なんてこった！
- **32:** APIで取得した`length`と比較する

##### 日付のフォーマット
最終更新日時をフォーマットします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/6a48930d-a78c-337b-f0b7-88af958e10bf.png)
`Add date`とはいうものの、内部では1秒も足していません。`Format date`がなぜか失敗することに対する苦肉の策です。

##### 添えるjson
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/52688e98-0d53-d7df-51bf-d9e4436b5c5c.png)
```json
{"content":"小説が更新されました！","embeds":[{"color":2340239,"timestamp":"{{kmmX.add_date.date}}","title":"{{OuoH.list.list.[1]}}","url":"https://ncode.syosetu.com/{{OuoH.list.list.[0]}}/{{OuoH.list.list.[3]}}/","fields":[{"name":"話数","value":"第{{OuoH.list.list.[3]}}話","inline":true},{"name":"文字数","value":"{{OuoH.list.list.[4]}}文字(+{{VSIh.subtract.subtract}})","inline":true}],"author":{"name":"{{OuoH.list.list.[1]}}","url":"https://ncode.syosetu.com/{{OuoH.list.list.[0]}}/"},"image":{"url":"https://sbo.syosetu.com/{{OuoH.list.list.[0]}}/twitter.png"}}]}
```
詳細は省く……というか説明しようがありません。この二重波括弧はClaris Connectの変数なんかを使用した場合に出てくるやつのようで、これらを踏まえた解説は極めて難しいです。多分出力された結果を見たほうが分かりやすいと思うし、説明はしません。
Discordのembedsを活用しているため結構長いです。

##### Webhookにリクエストを投げる
ついにこの時です。
APIが制限されたとはいえ、どうも**Test Action**については普段通り使えるようです。なのでそれを踏まえ、生成したjsonを`config`から読んだURLにPOSTリクエストで投げてみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/024b3e64-18b8-e614-b751-3912ee31af07.png)
これをテストとして実行すると……
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/185fc9c6-9b33-bda5-6820-539360f56d70.png)
(知らない小説ですが)更新通知が届きました！！！！！！！
本当にありがとうございました。

# 今回のフローの最終的な全体像
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2703746/8d234525-1715-964d-676d-82c9dd0b1dfc.png)


# 感想
## 記事自体の感想
- **API利用は計画的に！**
- 本来はもうちょっとカオスにしたかったんですが、API枯渇が先でしたね……

## Claris Connectの感想
- ところどころ罠が目立つが、それを熟知したうえで使えば楽しいツールかもしれない
    - 罠の例：Webhookモジュール内にGETリクエストが置かれている
- 変数の中身が空だと宣言できないのってどうなんだろう
- データのフォーマッティングが雑にできるのは非常によかった

# 感想2
${\Huge\text	iPadください！！！！！！！！！！}$

# 参考
- https://www.claris.com/ja/blog/2021/claris-connect-google-form
- https://taiki-t.hatenablog.com/entry/2016/10/14/031124
- https://qiita.com/tyuma/items/46cbb9d4e3d078e661dd
- https://qiita.com/Eai/items/1165d08dce9f183eac74

# 脚注
[^1]: 小説家になろうの作品におけるユニークIDみたいなもの
[^2]: 本来であればこの『ストレージを読む』セクションはもっと前に配置したかったんですが、Claris Connectでは**トリガーが必須**となるため定期実行の後に置かざるを得ませんでした
[^3]: `Download Google Doc, Slide, or Sheet`という名前のコマンドはあるんですが、URLしか返ってきてないように見える
[^4]: 一般的にWebhookはPOSTリクエストを利用するものなので
[^5]: 長期的設計ではマネしちゃだめだぞ！Claris Connectの連想配列周りが面倒というのはわかるけどね！
[^6]: 今回使っているサーバーはWebhookを盗まれたところで特に実害がないような場所だからやっていますが、セキュリティ的には相当カスです。場合によりもっと堅牢にするのがいいと思います。