学校配布書類管理アプリ　ミキティーが学校の配布書類の管理が困ってるって言ってたので・・夫が寝た後に好きな夫の挑戦①

## 1 はじめに
私、50歳で初めて子供ができたじじパパ。プログラミング初心者ですが、プログラミングで子育てがもっと楽しくならないか模索中。
### 1-1 夫が寝た後にを見るのが好き
テレビでミキティーが、学校の配布書類の管理が大変！！って言ってたから、なんか管理が楽になるアプリ作れないかなと。
### 1-2 奥さんにAIを使って欲しい
AIつよつよ夫婦になって、今後のキャリア形成に活かせたらと思い、AI利用を奥さんに勧めるもなかなか上手く行きません(^^;;
なんかAIで便利になるものを実際に作れたら、もっと興味を持ってくれるのでは？？と考えました。

詳細はこちらに記載

https://note.com/mgdemg/n/n1f5d2add255f?sub_rt=share_pb

## 2 書類管理アプリ調査
AI検索ツール　Feloで検索。1分で調べて表にしてくれた。すごい。
[詳細：Felo　学校配布書類について教えて](https://felo.ai/search/fyBmBtnyxU8hDommukCr2G?invite=pPoJOqjzLKr0r "学校配布書類について教えて")
 
 
| **アプリ名**       | **主な機能**                                                                 | **料金**                     | **対応デバイス**       | **特徴**                                                                                     |
|--------------------|------------------------------------------------------------------------------|------------------------------|------------------------|--------------------------------------------------------------------------------------------|
| **おたよりクリップ** | - プリント撮影・保存<br>- フォルダ分け<br>- OCR（文字認識）<br>- カレンダー連携 | 無料（広告非表示：160円）    | iOS, Android           | シンプルで使いやすい。OCR機能でプリント内容をテキスト化可能。                           |
| **プリゼロ**       | - プリント撮影・保存<br>- カレンダー連携<br>- 家族での共有<br>- タグ付け       | 無料（プレミアム：月額200円）| iOS, Android           | カレンダー連携でスケジュール管理が簡単。家族全員で情報共有可能。                         |
| **がくぷり**       | - プリント配信<br>- アンケート集計<br>- 出欠連絡<br>- カレンダー連携         | 学校が導入費用を負担         | iOS, Android, PC       | 学校全体での導入を前提としたペーパーレス化サービス。事務作業の効率化に最適。             |
| **Evernote**       | - 書類保存<br>- OCR機能<br>- タグ付け<br>- デバイス間同期                   | 無料（プレミアム：月額1,100円）| iOS, Android, PC       | 汎用性が高く、学校プリント以外の書類管理にも活用可能。                                   |
| **Microsoft OneNote** | - 書類保存<br>- タグ付け<br>- デバイス間同期                              | 無料                         | iOS, Android, PC       | Microsoft Lensと連携して高品質なスキャンが可能。整理機能が柔軟。                       |
| **tetoru**         | - 学校連絡配信<br>- 出欠連絡<br>- 保護者とのメッセージ機能                  | 無料                         | iOS, Android           | 学校と保護者間の連絡を効率化するツール。小中学校向け。                                   |

正直、無料のアプリでも良さそうなのがあるな。でも自分で作ることに意味がある！はず。

## 3 記事の内容
学校からの配布書類を管理するアプリを作成したいのですが、難しいので以下記事のアプリ作成に挑戦しました。


https://qiita.com/mkthrkw/items/c3fd56a8cc21f13e3cb8

• LINEとGASを連携させ、ユーザーのアップロードした画像を処理する方法
• GASを使って画像をDifyに送信する方法
• DifyでAIを活用し画像からデータを抽出し、Googleスプレッドシートに保存する方法

## 4 アプリの流れ　（上記記事から引用）
### 4-1 利用イメージ
スマホで領収書を写メして、Lineから画像を送付すると、内容がGoogleのスプレッドシートに書き込まれ結果をLineで返してくれるというものです。

今後、学校配布書類管理に応用したいと思います。
配布書類をスマホ撮影
➡️Lineで画像送付
➡️自動で要約された文書と画像リンクが、Notionに保存
後で検索しやすくなれるようにしたいです

### 4-2 具体的な流れ
①ユーザーがLINEで領収書の写真をアップロード
②LINEからGASへwebhookが送信される
③GASがそのリクエストを受け取り、画像データをDifyに送信
④Difyを通してLLMにて画像を解析し、領収書の内容をJSON形式にする
⑤GASがそのJSONデータをGoogleスプレッドシートに保存し、画像もGoogleドライブに保存する
⑥GASが保存した内容をLINEに返す

![スクリーンショット 2024-12-08 10.49.23.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3531581/a173425f-0280-dfe6-f873-d817b1dfdc5a.jpeg)

## 

## 5 アプリ作成

### 5-1 ②LINEからGASへwebhookが送信される(GoogleDriveに画像保存）

<details><summary>webhookとは</summary>

Feloで検索

---

## **Webhookとは？**

Webhookは、**「特定のイベントが起きたときに、他のサービスやアプリに自動で通知を送る仕組み」**です。これにより、異なるシステム間でリアルタイムな連携が可能になります。

---

### **Webhookの仕組み**
1. **イベントの発生**  
   例: システムでデータが更新されたり、新しい情報が追加されたりする。
   
2. **通知の送信**  
   Webhookは、指定されたURL（受信先）にHTTPリクエストを送信します。このリクエストには、イベントに関するデータが含まれています。
   
   HTTPリクエストとは？
HTTPリクエストは、インターネットで情報を取得するための「お願い」のようなものです。例えば、あなたがウェブサイトを見たいとき、ブラウザ（インターネットを見るためのアプリ）がサーバー（情報を持っているコンピュータ）に「このページを見せて！」と頼むのがHTTPリクエストです。

3. **受信先での処理**  
   通知を受け取ったサービスが、そのデータを使って次のアクションを実行します。

---

### **Webhookの例**
- **チャット通知**  
  例: SlackやMicrosoft Teamsに新しいメッセージを自動投稿。
  
- **Eメール送信**  
  例: フォームに入力があった際に、管理者へメール通知。

- **データ連携**  
  例: kintoneでデータが更新されたら、別のシステムにその情報を送信。

---

### **Webhookのメリット**
- **リアルタイム性**  
  イベントが発生した瞬間に通知が送られる。
  
- **自動化**  
  手動で確認する必要がなく、効率的。

- **柔軟な連携**  
  異なるサービス間で簡単にデータをやり取りできる。

---

### **Webhookの注意点**
- **受信先の設定**  
  Webhookを受け取るサービスが正しく設定されている必要があります。
  
- **セキュリティ**  
  不正なリクエストを防ぐために、認証や署名の仕組みを導入することが推奨されます。

---

### **Webhookのイメージ**
Webhookは、シーソーのように「片方で何かが起きたら、もう片方に通知が飛ぶ」仕組みと考えると分かりやすいです。

---

このようにWebhookは、システム間の連携をスムーズにし、効率的なワークフローを実現するための重要なツールです。
[1] https://dev.classmethod.jp/articles/teams-webhook-markdown-workflows/
[2] https://note.com/seika_usa/n/n9125e43e9f41
[3] https://github.com/pact-foundation/pact_broker/blob/master/lib/pact_broker/doc/views/webhooks.markdown
[4] https://fider.io/docs/using-webhooks
[5] https://zenn.dev/yukun369/articles/ac2150ec6ab9de
[6] https://devforum.zoom.us/t/incoming-webhook-markdown/27218
[7] https://api.slack.com/incoming-webhooks
[8] https://kintone-blog.cybozu.co.jp/developer/000283.html
[9] https://jamstack.jp/blog/netlifycms_to_microcms/
[10] https://help.zoho.com/portal/ja/kb/connect/developer-tools/webhooks/articles/%E5%8F%97%E4%BF%A1webhook%E3%81%AE%E9%AB%98%E5%BA%A6%E3%81%AA%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E5%BD%A2%E5%BC%8F
[11] https://help.zoho.com/portal/ja/kb/connect/developer-tools/webhooks/articles/%E5%8F%97%E4%BF%A1webhook%E3%81%AE%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E5%BD%A2%E5%BC%8F-15-11-2021
[12] https://stackoverflow.com/questions/66823432/ms-teams-webhook-with-markdown
[13] https://devforum.zoom.us/t/does-incoming-webhook-full-message-format-support-markdown/28685
[14] https://help.zoho.com/portal/en/kb/connect/developer-tools/webhooks/articles/advanced-message-formatting-in-incoming-webhooks
[15] https://qiita.com/ik-fib/items/b4a502d173a22b3947a0
[16] https://community.cisco.com/t5/adopting-webex/webex-teams-webhooks-and-markdown/m-p/4450152
[17] https://thoughtbot.com/blog/blog-in-markdown-deploy-with-webhooks
[18] https://birdie0.github.io/discord-webhooks-guide/other/discord_markdown.html
[19] https://blog.microcms.io/create_markdown_blog/
[20] https://blog.trainocate.co.jp/blog/output_nocode_022
</details>

<details><summary>コード解説　doPost関数</summary>

作成したLINE公式アカウントにメッセージ（画像）を送ると
指定したURL（このあと設定）にWebhookイベントオブジェクトを含む
HTTP POSTリクエストが送られてきます。
今回はPOSTリクエストを受け取るためにdoPost関数を使います。

以下cloudによる解説

# 1. doPost関数の定義部分

```javascript
/**
 * LINE webhook からのリクエスト受け取り処理
 * @param {GoogleAppsScript.Events.DoPost} e
 * @return {GoogleAppsScript.Content.TextOutput} 
 */
function doPost(e) {
```

- `/**` から始まるブロックは「JSDoc」と呼ばれるドキュメンテーションコメントです
- このコメントは関数の説明を記述する特別な形式です
  - 1行目：この関数の概要説明
  - `@param`：関数が受け取る引数の説明
  - `@return`：関数が返す値の型の説明
- `function doPost(e)`：
  - Google Apps Script(GAS)の特別な関数名で、webhookのPOSTリクエストを受け取ります
  - `e`は「event」の略で、LINEから送られてくるデータ全体を含むオブジェクトです
  - この関数名は変更できません（GASの規約）

# 2. データ存在チェック

```javascript
if (!e) {
  Logger.log('データがありません');
  return ContentService.createTextOutput('データがありません');
}
```

- `if (!e)`：
  - `!`は「否定」を表す演算子です
  - `e`が以下の場合にtrueとなります：
    - null
    - undefined
    - 空文字列
    - 0
    - false
- `Logger.log()`：
  - GASのログ機能です
  - 実行ログに「データがありません」と記録します
  - デバッグ時に役立ちます
- `ContentService.createTextOutput()`：
  - GASの応答を作成するサービス
  - プレーンテキストの応答を生成します
  - LINEサーバーに返すレスポンスとなります

# 3. JSONデータのパース

```javascript
const json = JSON.parse(e.postData.contents);
```

- `const`：再代入できない変数を宣言します
- `e.postData`：POSTリクエストのデータを含むオブジェクト
- `e.postData.contents`：
  - リクエストの本文（JSON形式の文字列）
  - 例：
    ```json
    {
      "events": [{
        "type": "message",
        "replyToken": "12345...",
        "message": {
          "type": "image",
          "id": "789..."
        }
      }]
    }
    ```
- `JSON.parse()`：
  - JSON形式の文字列をJavaScriptオブジェクトに変換
  - エラーが発生する可能性がある処理です（try-catchで囲むとより安全）

# 4. 必要なデータの取り出し

```javascript
const replyToken = json.events[0].replyToken;
const message = json.events[0].message;
const messageType = message.type;
```

- `json.events[0]`：
  - `events`は配列で、複数のイベントを含む可能性があります
  - `[0]`で最初のイベントを取得
- `replyToken`：
  - LINEの応答に必要な一時的なトークン
  - 有効期限は約30分
  - このトークンを使って特定のメッセージに返信できます
- `message`：
  - 送られてきたメッセージの詳細情報を含むオブジェクト
  - 画像の場合：
    ```json
    {
      "type": "image",
      "id": "画像ID",
      "contentProvider": {
        "type": "line"
      }
    }
    ```
- `messageType`：
  - メッセージの種類を示す文字列
  - 可能な値：
    - "text"（テキスト）
    - "image"（画像）
    - "video"（動画）
    - "audio"（音声）
    - "file"（ファイル）
    - "location"（位置情報）
    - "sticker"（スタンプ）

# 5. リプライトークンの確認

```javascript
if (typeof replyToken === 'underfined') {
  return ContentService.createTextOutput('リプライトークンが見つかりません');
}
```

- `typeof`：変数の型を調べる演算子
- バグ修正：`'underfined'` → `'undefined'`が正しい
- このチェックの目的：
  - replyTokenが存在しない場合のエラー処理
  - LINE APIの仕様上、一部のイベントではreplyTokenが含まれない

# 6. メッセージタイプの確認と応答

```javascript
if (messageType !== 'image') {
  replyMessageToLine(replyToken, '画像を送ってください');
  return;
}
```

- `!==`：厳密な不等価演算子
  - 値と型の両方を比較
  - `!=`より安全
- 処理の流れ：
  1. メッセージが画像かチェック
  2. 画像でなければ「画像を送ってください」と返信
  3. `return`で関数を終了

# 7. 画像受信確認の応答

```javascript
replyMessageToLine(replyToken, '画像を受け取れました');
```

- `replyMessageToLine`：
  - 別途定義が必要な関数
  - LINE Messaging APIを使用して実際の返信を行う
  - 実装例：
    ```javascript
    function replyMessageToLine(replyToken, text) {
      const url = 'https://api.line.me/v2/bot/message/reply';
      const headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + CHANNEL_ACCESS_TOKEN
      };
      const postData = {
        'replyToken': replyToken,
        'messages': [{
          'type': 'text',
          'text': text
        }]
      };
      const options = {
        'method': 'post',
        'headers': headers,
        'payload': JSON.stringify(postData)
      };
      UrlFetchApp.fetch(url, options);
    }
    ```
</details>

<details><summary>コード解説　応答返却(replyMessageToLine関数)</summary>

このコードをより詳しく解説していきます。

# 1. 応答返却(replyMessageToLine)関数の定義部分

```javascript
/**
 * LINEでリプライメッセージを投稿する
 * @param {string} replyToken リクエストに含まれていたリプライトークン
 * @param {string} messageText 送りたいメッセージ
 * @return {UrlFetchApp.HTTPResponse} リクエスト結果のHTTPレスポンス
 */
function replyMessageToLine(replyToken, messageText) {
```

- JSDocによるドキュメント：
  - 1行目：関数の目的
  - `@param`：2つの引数の説明
    - `replyToken`：返信先を特定するための一時的なトークン
    - `messageText`：実際に送信するメッセージの内容
  - `@return`：返り値の型の説明
- 引数：
  - `replyToken`：前のコードで受け取ったLINEからのトークン
  - `messageText`：送信したいメッセージの文字列

# 2. LINE APIのURL設定

```javascript
const url = LINE_URL + '/reply';
```

- `LINE_URL`：
  - 別の場所で定義された定数（通常は`https://api.line.me/v2/bot/message`）
  - LINEのMessaging APIのベースURL
- `/reply`：
  - メッセージ返信用のエンドポイント
  - 完成するURL：`https://api.line.me/v2/bot/message/reply`

# 3. リクエストオプションの設定

```javascript
const options = {
  'headers': {
    'Content-Type': 'application/json; charset=UTF-8',
    'Authorization': 'Bearer ' + LINE_TOKEN,
  },
  'method': 'post',
  'payload': JSON.stringify({
    'replyToken': replyToken,
    'messages': [{
      'type': 'text',
      'text': messageText,
    }],
  }),
}
```

- `headers`：HTTPヘッダー情報
  - `Content-Type`：
    - データ形式がJSONであることを指定
    - 文字コードはUTF-8
  - `Authorization`：
    - LINE APIの認証情報
    - `Bearer`：トークン認証方式を示す接頭辞
    - `LINE_TOKEN`：LINE Developersで取得したチャネルアクセストークン
- `method`：
  - HTTPメソッドの指定
  - `post`：データを送信するための標準的なメソッド
- `payload`：送信するデータ
  - `JSON.stringify()`：JavaScriptオブジェクトをJSON文字列に変換
  - データの構造：
    ```javascript
    {
      'replyToken': 'reply-token-xxx...',  // 返信先の特定
      'messages': [{                        // メッセージの配列
        'type': 'text',                    // メッセージタイプ
        'text': '実際のメッセージ'          // 送信内容
      }],
    }
    ```

# 4. APIリクエストの実行と結果の処理

```javascript
const response = UrlFetchApp.fetch(url, options);
return JSON.parse(response);
```

- `UrlFetchApp.fetch()`：
  - Google Apps Scriptの HTTP通信用メソッド
  - 指定したURLにHTTPリクエストを送信
- `response`：
  - LINE APIからのレスポンス
  - 成功時のレスポンス例：
    ```json
    {
      "status": "ok"
    }
    ```
- `JSON.parse(response)`：
  - レスポンスをJavaScriptオブジェクトに変換
  - 呼び出し元で結果を扱いやすくするため


</details>

## 6 まとめ

基本的に、参考記事通りにやって成功しました。

✅画像をスマホ撮影
✅Lineで画像送付
✅GoogleDriveに保存（自動）
✅Lineに「画像受け取れました」のメッセージ受取（自動）

以後、続きも記事にします。
◻︎受取画像をDifyに送付
◻︎DifyのLLMで画像のテキストを読み取り
◻︎上記をGoogle Spreadsheetに保存

そして、上記が完成したら、
◻︎Notion に情報管理
に挑戦していきたいと思います。