Gemini×LINE お手軽に生成AIchatbot

![{0C7743AE-45C7-49CC-B6D9-961602973485}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2617007/ceb087e4-a860-f891-0403-cd987f78b05a.png)




テキストを送ればGeminiのAPIを通してプロンプトに沿った返答をする 返答内容をスプレッドシートに保存
画像を送れば画像のURLをスプレッドシートに保存
という機能が出来ます

# 1. Googleアカウントを準備する
Googleアカウントを持っていない場合は、Googleアカウントを作成してください。
Googleアカウントが必要です。このアカウントでGoogleドライブやGoogleスプレッドシートにアクセスします。

## 2. スプレッドシートの準備
Googleドライブで新しいGoogleスプレッドシートを作成します。
名前は何でもいいです。
スプレッドシートに「Sheet1」と「Sheet2」という名前のシートを作成します（デフォルトで存在する場合もあります）。
「Sheet1」は解析結果や画像のURLを保存します。
「Sheet2」はログメッセージを保存します。


# 3. Google Apps Script (GAS) プロジェクトの作成
スプレッドシートにアクセスし、「拡張機能」→「Apps Script」を選択します。
新しいスクリプトプロジェクトが作成されます。このプロジェクトにコードを書いていきます。

![{CAFFC4F7-41A7-42B9-A494-91E05999FEB6}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942125/730bf432-d8cb-4e5c-0ab8-ecb64b127794.png)

![{A0001576-9ED0-4381-98B7-15048E833DE8}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942125/758ec20c-872d-08dc-fcfa-6d06b7990f5a.png)








# 3. コードのコピー＆ペースト
下記のコード（doPost(e)から始まる）をすべてコピーします。
作成したGoogle Apps Scriptプロジェクトのエディタに貼り付けます。

```GAS:LINE.gs

function doPost(e) {
  var replyToken;
  try {
    // LINEからのPOSTデータをJSON形式にパース
    var json = JSON.parse(e.postData.contents);
    logEvent("Received event: " + JSON.stringify(json));


    // 最初のイベント情報を取得
    var event = json.events[0];
    replyToken = event.replyToken;

    // メッセージの種類に応じて処理を分岐
    if (event.message.type === "image") {
      handleImageMessage(event, replyToken);
    } else if (event.message.type === "text") {
      handleTextMessage(event, replyToken);
    } else {
      replyToLine(replyToken, "画像またはテキストを送信してください。");
    }
  } catch (error) {
    // エラーが発生した場合、ログに記録し、LINEにエラーメッセージを返信
    logEvent("Error in doPost: " + error.message);
    if (replyToken) {
      replyToLine(replyToken, "エラーが発生しました: " + error.message);
    }
  }
}

function handleImageMessage(event, replyToken) {
  try {
    // 画像のIDを取得
    var imageId = event.message.id;
    // 画像URLを取得するための関数を呼び出し
    var imageUrl = getImageUrl(imageId);

    // 画像を受け取った旨をLINEに返信
    replyToLine(replyToken, "画像を受け取りました。スプレッドシートに保存します。");

    // 画像URLをスプレッドシートに保存
    saveToSheet("画像が送信されました", imageUrl);

    // 画像の保存が完了した旨をLINEに返信
    replyToLine(replyToken, "画像を保存しました。URL: " + imageUrl);
  } catch (error) {
    // エラーが発生した場合、ログに記録し、LINEにエラーメッセージを返信
    logEvent("Error in handleImageMessage: " + error.message);
    replyToLine(replyToken, "画像の処理中にエラーが発生しました: " + error.message);
  }
}

function handleTextMessage(event, replyToken) {
  try {
    // テキストメッセージを取得
    var message = event.message.text;
    var replyMessage = "";

    // テキストをGemini APIで解析
    replyMessage = analyzeTextWithGemini(message);

    // 解析結果をLINEに返信
    replyToLine(replyToken, replyMessage);
  } catch (error) {
    // エラーが発生した場合、ログに記録し、LINEにエラーメッセージを返信
    logEvent("Error in handleTextMessage: " + error.message + " | Stack: " + error.stack);
    replyToLine(replyToken, "テキストの処理中にエラーが発生しました: " + error.message);
  }
}

function getImageUrl(imageId) {
  try {
    // 画像取得のためのログ
    logEvent("Attempting to fetch image with ID: " + imageId);
    
    // 画像データを取得するためのURLを生成
    var url = "https://api-data.line.me/v2/bot/message/" + imageId + "/content";
    // LINE APIを使用して画像データを取得
    var response = UrlFetchApp.fetch(url, {
      headers: {
        "Authorization": "Bearer " + getChannelAccessToken(),
      },
      muteHttpExceptions: true,  // HTTPエラーを無視せずレスポンスを取得
    });

    var responseCode = response.getResponseCode();
    var responseText = response.getContentText();

    // 画像取得のレスポンスをログに記録
    logEvent("LINE API response code for image fetch: " + responseCode);
    logEvent("LINE API response text for image fetch: " + responseText);

    // 画像取得に失敗した場合、エラーをスロー
    if (responseCode !== 200) {
      throw new Error("Failed to fetch image. Response code: " + responseCode + ", Response text: " + responseText);
    }

    // 取得した画像データをGoogleドライブに保存し、URLを取得
    var blob = response.getBlob();
    var file = DriveApp.createFile(blob).setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.VIEW);
    var imageUrl = file.getUrl();

    // 画像URLをログに記録
    logEvent("Image URL successfully retrieved: " + imageUrl);
    return imageUrl;
  } catch (error) {
    // エラーが発生した場合、ログに記録し、エラーメッセージをスロー
    logEvent("Error in getImageUrl: " + error.message + " | Stack: " + error.stack);
    throw new Error("画像URLの取得中にエラーが発生しました。");
  }
}

function analyzeTextWithGemini(message) {
  logEvent("Starting text analysis with Gemini API...");
  try {
    // Gemini APIのAPIキーとURLを設定
    const apiKey = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY'); 
    const url = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=' + apiKey;

    const objHeaders = {
      'Content-Type': 'application/json'
    };

    // リクエストボディを設定
    const objPayload = {
      contents: [
        {
          parts: [
            {
              text:  "ユーザーからのLINEメッセージの内容を博識な文豪が書く日記風に変えてください: " + message
            }
          ]
        }
      ]
    };

    // リクエストオプションを設定し、Gemini APIにリクエストを送信
    const objOptions = {
      method: 'post',
      headers: objHeaders,
      payload: JSON.stringify(objPayload),
      //muteHttpExceptions: true
    };

    logEvent("Sending request to Gemini API...");
    const response = UrlFetchApp.fetch(url, objOptions);
    const responseCode = response.getResponseCode();
    const responseText = response.getContentText();

    // Gemini APIのレスポンスをログに記録
    logEvent("Gemini API response code: " + responseCode);
    logEvent("Gemini API response text: " + responseText);

    // APIリクエストが失敗した場合、エラーをスロー
    if (responseCode !== 200) {
      throw new Error("Gemini API request failed with response code: " + responseCode + ", response text: " + responseText);
    }

    // 解析結果をパースして取得
    const jsonResponse = JSON.parse(responseText);
    logEvent("Received response from Gemini API: " + JSON.stringify(jsonResponse));

    // テキストの取得部分を修正し、スプレッドシートに保存
    const description = jsonResponse.candidates[0].content.parts[0].text;
    saveToSheet(description, ""); // テキスト解析結果をシートに保存

    return description;
  } catch (error) {
    // エラーが発生した場合、ログに記録し、エラーメッセージをスロー
    logEvent("Error in analyzeTextWithGemini: " + error.message + " | Stack: " + error.stack);
    throw new Error("Gemini APIの解析中にエラーが発生しました。");
  }
}

function saveToSheet(description, imageUrl) {
  try {
    // シートを取得し、データを保存
    var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
    sheet.appendRow([description, imageUrl]);  // 解析結果と画像URLをシートに保存
    logEvent("データをスプレッドシートに保存しました。");
  } catch (error) {
    // エラーが発生した場合、ログに記録し、エラーメッセージをスロー
    logEvent("Error in saveToSheet: " + error.message + " | Stack: " + error.stack);
    throw new Error("スプレッドシートへの保存中にエラーが発生しました。");
  }
}

function replyToLine(replyToken, message) {
  try {
    // LINE APIに返信するためのURLとヘッダーを設定
    var url = "https://api.line.me/v2/bot/message/reply";
    var headers = {
      "Content-Type": "application/json",
      "Authorization": "Bearer " + getChannelAccessToken(),
    };
    
    // メッセージが空でないことを確認
    if (!message) {
      throw new Error("Reply message is empty. Cannot send an empty message to LINE.");
    }

    // 返信メッセージを設定し、LINE APIにリクエストを送信
    var postData = {
      "replyToken": replyToken,
      "messages": [{
        "type": "text",
        "text": message
      }]
    };

    var response = UrlFetchApp.fetch(url, {
      "method": "post",
      "headers": headers,
      "payload": JSON.stringify(postData)
    });

    var responseCode = response.getResponseCode();
    var responseText = response.getContentText();

    // 返信結果をログに記録
    logEvent("LINE API response code: " + responseCode);
    logEvent("LINE API response text: " + responseText);

    // 返信が失敗した場合、エラーをスロー
    if (responseCode !== 200) {
      throw new Error("Failed to send message to LINE. Response: " + responseText);
    }

    logEvent("Replied to LINE: " + message);
  } catch (error) {
    // エラーが発生した場合、ログに記録し、エラーメッセージをスロー
    logEvent("Error in replyToLine: " + error.message + " | Stack: " + error.stack);
    throw new Error("LINEへの返信中にエラーが発生しました。");
  }
}

function logEvent(message) {
  try {
    // ログを記録するためのシートを取得または作成
    var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
    if (!sheet) {
      sheet = SpreadsheetApp.getActiveSpreadsheet().insertSheet("Sheet2");
    }
    var date = new Date();
    var formattedDate = Utilities.formatDate(date, Session.getScriptTimeZone(), "yyyy-MM-dd HH:mm:ss");
    sheet.appendRow([formattedDate, message]);  // ログメッセージをSheet2に保存
  } catch (error) {
    // ログ記録のエラーをLoggerに記録
    Logger.log("Error in logEvent: " + error.message + " | Stack: " + error.stack);
  }
}

function getChannelAccessToken() {
  // スクリプトプロパティからアクセス・トークンを取得
  var scriptProperties = PropertiesService.getScriptProperties();
  var token = scriptProperties.getProperty('YOUR_CHANNEL_ACCESS_TOKEN');
  logEvent("Access token retrieved: " + (token ? "Success" : "Failed"));
  return token;
}

```

![{4FA9AFA3-A6C9-4484-904F-CB845975A2AA}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942125/dc67e271-e5a7-81ea-e8af-fcc8517018b5.png)
こんな感じにはりつけ

↓ ここのtext： のとこにプロンプトを書きます

![{455A69EC-89FA-4661-8918-D03568A9ED60}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942125/24a97f27-62f3-6044-e48b-5c30ead9ad50.png)

# 4. 必要なライブラリやトークンの設定
### LINEチャネルのアクセストークン取得:

LINE Developers にアクセスし、LINEアカウントでログインします。
新しいチャネルを作成し、アクセストークンを取得します。

参考サイト

https://qiita.com/okinakamasayoshi/items/bf9593b2af20148c4d19

https://qiita.com/zono345/items/fefeb13ff493ce8b432b

https://qiita.com/course_k/items/00804420e61b7514bf30



### Gemini APIキーの取得:

Gemini APIを利用するために、Google Cloud Consoleにアクセスし、APIキーを取得します。

1．Google AI StudioのGemini APIのページにアクセス

[Google AI StudioのGemini APIのページ](https://aistudio.google.com/app/prompts/new_chat)

2．左メニューにある「Get API Key」を選択

3．API Keysのページで「Create API keys in new project」もしくは「APIを作成」をクリックします

![{7898610E-963E-4040-A935-7FAB9CA8CAD4}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942125/8536b3a5-d221-eeb1-87ed-c2fcc202c075.png)
4．少し待つとAPIキーが発行されるので、「Copy」をクリック、APIキーがクリップボードにコピーされます



### スクリプトのプロパティ設定
取得したトークンをGoogle Apps Scriptのプロジェクトに保存します。
「プロジェクト」→「プロジェクトのプロパティ」→「スクリプトのプロパティ」からトークンを設定します。
例えば、YOUR_CHANNEL_ACCESS_TOKEN という名前でアクセストークンを設定します。
取得したAPIキーをGoogle Apps Scriptのスクリプトプロパティに設定します。名前はGEMINI_API_KEYとします。
![{F9FB2528-9043-4B5A-B81F-AD8014BEEC37}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942125/8b3c4298-f9f8-bc21-93d8-d6383ecccf81.png)
![{2FAF135A-EF51-49AD-9334-B59AAAF3E9B8}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942125/fb59842f-9106-453b-98cd-01c7c2444f88.png)


## 6. Webアプリケーションとしてデプロイ
Google Apps Scriptエディタで「デプロイ」→「ウェブアプリケーションとしてデプロイ」を選択します。
次のユーザーとして実行を「自分」に設定
アクセスできるユーザーを「全員」に設定
デプロイ後に表示されるURL（ウエブアプリURL）が、LINEのWebhook URLとして使用されます。
![{7547BB3F-C536-465B-97B2-B75FB51D044E}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942125/5c3180cc-3a46-c9b1-3b2d-c65fb2b66517.png)


## 7. LINE Developers コンソールでの設定
LINE Developers コンソールに戻り、作成したチャネルの設定ページに移動します。
Webhook URLとして、上記で取得したGoogle Apps ScriptのウェブアプリURLを設定します。
Webhookの使用を有効にします。

※Webhookの利用のチェック忘れずに!!

![{AB0D25D0-3171-4164-B340-E6C757865A87}.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2942125/f4c7563c-5e43-a473-d5ab-93180d54328d.png)


## 8. テスト
LINEアプリからチャネルにテキストメッセージや画像を送信してみてください。
送信したテキストや画像が正常に解析・保存され、LINEに返信が返ってくることを確認します。
Googleスプレッドシートに解析結果や画像のURLが保存されていることを確認します。