調整さんの入力を通知してくれる「調整さんお知らせさん」をDifyで作ってみた

調整さん、便利ですよね

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/db0ac3fb-c2cf-4a1a-b9f9-939152a40f8f.png" width="75%" />

https://chouseisan.com/

なんといっても候補日を挙げて、入力するだけというシンプルさが良いですね。
最近ではGoogleカレンダーと連携して自動で開いているところに日程を入れてくれるツールなんかもありますが、開催する側としては全体を俯瞰した上で意図をもって日程を決めたいと思う場面も少なくありません。

調整さんくらいのゆるさ、自由度が私は使いやすいなと思っています。

## いちいち見にくのがめんどくさいと思い始めた

難点を挙げるとすれば、調整結果は都度アクセスして見に行かなければいけないところでしょうか。
APIなども調べてみましたがパッと出てきませんでした。

> <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/db23a808-cb97-4ee6-b02a-a826eb23394d.png" width="75%" />
>
> 掲示板を毎日見に来るのは面倒だなあ

そこで、今回は調整さんの入力結果を定期的に通知してくれるBotを作成することにしました。

## 1. 構成

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/343ad883-b4c9-47f0-b13c-12b809b4b3a8.png" width="75%" />

```text
次のようなシステム構成を、図にしてください

GASで定期的にAPIを叩く
GASで叩くAPIの先としてDifyを利用
Difyがリクエストを受け取ると、Firecrawlを利用して調整さんのページをスクレイピング
スクレイピングしたデータをLLMに入れて、成形
成形したデータをLINE Messaging APIを利用して、LINEに通知
```

と`Claude3.7 Sonnet`にお願いしたら書いてくれました

### 1.1 Dify部分

#### 1.1.1. 環境変数

環境変数としてLINE Developersから取得した`UserID`と`Channel Access Token`を設定します。
LINE Developersからの取得方法は、次のリンク先記事を参照ください。

https://zenn.dev/protoout/articles/16-line-bot-setup

> <img src="https://i.gyazo.com/283f362c6471d7b0f28e0906ecb04608.png" width="75%" />
>
> ENVと書かれたアイコンをクリック

> <img src="https://i.gyazo.com/ada5ca4bf15bbab040c2530f946fc8ea.png" width="75%" />
>
> 環境変数を追加ボタンから設定します

`line_channel_access_token`にChannel Access Token、`to`にUserIDを指定しましょう。

#### 1.1.2. ノードの設定

ノードは次のような感じで繋げます

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/ecc66ebb-6ea9-4330-a568-80660000a201.png" width="75%" />

##### 1.1.2.1. `SCRAPE`

今回のキモ
`Firecrawl`というサービスをDifyから利用することで、調整さんをスクレイピングしていきます

https://www.firecrawl.dev/

:::note
Firecrawlは、初期時点ではDifyに登録されていませんので、market placeからダウンロードします

> <img src="https://i.gyazo.com/98a86885c77630ce4e8389800d742647.png" />
> 検索後は画面の指示に従ってインストールしてください
:::

https://marketplace.dify.ai/

ノードの設定自体は非常に単純で、`URL to scape`の欄に**作成した調整さんのURLを貼り付ける**だけです。

<img src="https://i.gyazo.com/d749cc407a705e7ea266a836162c11f6.png" />

##### 1.1.2.2. `LLM`

画像の通り、AIモデルは`gpt-4o`にしてみました

![スクリーンショット 2025-04-03 1.00.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/0780779d-e3bb-42de-8456-0a59813da739.png)

SYSTEMのプロンプトは次の通りにしています。
この言葉を調整することで、最終的な出力のフォーマットも調整できるかと思います。調整さんだけに。

```text
調整さんの日程候補が送られてきます
  - 回答者
  - 候補日程
  - 参加可否
  を抽出してリストアップしてください。
  
  ## 出力
  - マークダウン形式にせず、プレーンなテキストで出力してください。
  - 参加可否は○, ×で記述してください
```

##### 1.1.2.3. `コード`

`JS`のコードで、LLMからきたメッセージをLINEに送信する形式へ成形します。
JSにしているのは私が慣れているからなので、Pythonに慣れている方はそちらでも良いかと思います。
以下のサンプルにはJSのコードを記述しておきます。

![スクリーンショット 2025-04-03 1.03.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/f5e3729c-5aeb-415a-b974-e80bf95918b5.png)

```js
function main({to, text}) {
  const messageObj = {
    "to": to,
    "messages":[{
      "type":"text",
      "text": text
    }]
  };
  return {result: JSON.stringify(messageObj)};
}
```

`to`は事前に設定した環境変数です。自身のUserIDを指定することで、自分に対してLINEでメッセージが送れます。

##### 1.1.2.4. `HTTPリクエスト`

:::note warn
かなり間違えやすいので注意して設定しましょう。
:::

![スクリーンショット 2025-04-03 1.20.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/e938c094-b9a0-4dca-8477-cfc66c408c50.png)

APIの欄を`POST`にして

```bash
https://api.line.me/v2/bot/message/push
```

このURLを指定します。LINE Messaging APIのURLとなります。

ヘッダーの欄は
`Authorization`に対して`Bearer <line_channel_access_token>`と指定します。
Bearerとその後の半角スペースは打ち込み、環境変数をその後に続くように指定します。
`Content-Type`に対しては、`application/json`と記述してください。

ボディは`JSON`を指定して、コードのノードから来た結果を入れてください。

#### 1.1.3. 実行

実行してご自身のLINEにメッセージが届いたら成功です！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/e9c6d6a0-39ca-4b1c-bf46-4aa58af80f6e.jpeg" width="75%" />

#### 1.1.4. 公開・APIキーの発行

右上の`公開する`ボタンを押して公開します。
その後、サイドバーの`APIアクセス`の欄より、APIキーを発行し控えておきましょう。

<img src="https://i.gyazo.com/05e078d98d2bbe40f7a5797135787ef5.png" width="75%" />

### 1.2. Google Apps Script(GAS)部分

定期実行はGASのトリガーを利用します。

#### 1.2.1. コードの設定

まずはGASのコードをコピペします

```js
/**
 * 固定のbotとしてAPIを呼び出す関数
 */
function runWorkflow() {
  const apiKey = "app-******************";  // ご自身のAPIキーに置き換えてください
  const url = "https://api.dify.ai/v1/workflows/run";
  
  // 固定のbotユーザー識別子（例: bot_001）
  const botId = "bot_001";
  
  // ワークフローが要求するinputsのサンプル（必要に応じてキー/値を変更）
  const inputs = {
    text: "こんにちは、定期実行のbotです！",  // 例としてテキスト入力を設定
    // 他に必要なパラメータがあれば、ここに追加してください
  };

  // APIリクエスト用のpayloadを作成
  const payload = {
    inputs: inputs,
    user: botId,
    response_mode: "blocking"  // または "streaming" を利用することも可能
    // files: [...]  // ファイルリストが必要な場合は、ここにファイル情報を設定
  };

  // リクエストオプションの設定
  const options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload),
    headers: {
      "Authorization": "Bearer " + apiKey
    },
    muteHttpExceptions: true  // エラー発生時もレスポンスを取得するため
  };

  // APIリクエストの送信とログ出力
  try {
    const response = UrlFetchApp.fetch(url, options);
    Logger.log("Response Code: " + response.getResponseCode());
    Logger.log("Response Text: " + response.getContentText());
  } catch (e) {
    Logger.log("Error: " + e);
  }
}
```

コード中にある

```js
const apiKey = "app-******************";  // ご自身のAPIキーに置き換えてください
```

この部分は、先ほど`1.1.4.`で取得したAPIキーに置き換えてください。

この関数を実行して、再度ご自身のLINEにメッセージが届いたら成功です。GASからAPI経由でDifyのフローを起動できています。

#### 1.2.2. トリガーの設定

左サイドバーのトリガーにアクセスします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/38b80a87-0316-4313-93b8-6d790be6fe8a.png" width="75%" />

下記スクリーンショットの通り設定します。時間はご自身の通知してほしい時間を記入しましょう。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2100048/a12de96f-8d73-4083-a945-8beeae248316.png" width="75%" />

以上で完成です！
お疲れ様でした！

## 調整しよう！

これで、イベントの調整さんを立てたものの、すっかり忘れて調整せずに終わってしまうことを防げます！
どんどん飲み会しましょう！