【リリース】献立を考えてくれるAIラインボット「AI献立くん」

具材を入力するだけでレシピ完成!「AI献立くん」

この度、「AI献立くん」をリリースしました!
AI献立くん : https://lin.ee/sRdqjeO

具材を何個か入力するだけで、自動で何個か献立を考えてくれます。

今回のリリースでは、下記の3種類のレシピを考えてくれます。

  • すぐに作れるもの
  • 簡単に作れるもの
  • 美味しく作れるもの

同時にYouTubeでもリリース動画を出しています。

作り方

今回はLINEの公式アカウントをGoogle Apps Script (GAS) でスプレッドシートをデータベースに実装しています。AI部分はChatGPTのAIである「gpt-3.5-turbo」を使用しています。

詳しいGASでAI搭載のLINE公式アカウントの作り方に関しては、Udemy講座をご確認ください。
Udemy講座 : https://www.udemy.com/course/chatgpt-line-function-calling-ai/

今回リリースしているプログラム部分に関しては、下記に公開しておきます。
スプレッドシートは「制約」「ログ」「ユーザー」の3種類を用意しています。

コード.gs

function doPost(e) {
  // メッセージ受信
  const data = JSON.parse(e.postData.contents).events[0]
  // ユーザーID取得
  const lineUserId = data.source.userId
  // リプレイトークン取得
  const replyToken = data.replyToken
  // 送信されたメッセージ取得
  const postMessage = data.message.text
  // データのタイプを取得
  const dataType = data.type
  // フォロー時はユーザーデータを登録
  if (dataType == "follow")
    addUser(userId)
  
  // 個別のスプレッドシート情報取得
  // const SHEET_ID = findSheetId(lineUserId);
  // テキスト以外だった時(スタンプや写真など)
  if (postMessage === undefined)
    return sendMessage(replyToken, "献立を作るAIなので、お手元にある具材を伝えてね!\n例:ソーセージ、ポテト、人参");
  // データ生成&LINEに送信
  const replyText = chatGPT(postMessage);
  sendMessage(replyToken, replyText);
  debugLog(lineUserId, postMessage, replyText);
}

// 返答するメッセージを生成
function chatGPT(prompt) {
  // prompt = "こんにちは"
  let constraints;
  try {
    constraints = SHEET.getRange(1, 1).getValue()
  } catch (e) {
    console.log("制約なし");
  }
  const requestOptions = {
    "method": "post",
    "headers": {
      "Content-Type": "application/json",
      "Authorization": "Bearer "+ OPENAI_APIKEY
    },
    "payload": JSON.stringify({
      "model": CHAT_GPT_VER,
      "messages": [
        {"role": "system", "content": constraints},
        {"role": "user", "content": prompt}
       ]
    })
  }
  const response = UrlFetchApp.fetch(CHAT_GPT_URL, requestOptions);
  const responseText = response.getContentText();
  const json = JSON.parse(responseText);
  const text = json['choices'][0]['message']['content'].trim();
  console.log(text);
  const search_prompt = prompt.replace(/ /g, 'と').replace(/ /g, 'と').replace(/\n/g, 'と');
  const return_text = `${text}\n\nhttps://cookpad.com/search/${search_prompt}`
  return (return_text);
}

// LINEに返答するデータ生成
function sendMessage(replyToken, replyText) {
  const postData = {
    "replyToken" : replyToken,
    "messages" : [
      {
        "type" : "text",
        "text" : replyText
      }
    ]
  };
  return postMessage(postData);
}

// LINEに返答
function postMessage(postData) {
  const headers = {
    "Content-Type" : "application/json; charset=UTF-8",
    "Authorization" : "Bearer " + LINE_ACCESS_TOKEN
  };
  const options = {
    "method" : "POST",
    "headers" : headers,
    "payload" : JSON.stringify(postData)
  };
  return UrlFetchApp.fetch(LINE_REPLY_URL, options);
}
ろ

ログ.gs

function debugLog(userId, text, replyText) {
  if ('' == text) return; // テキストではない時はログなし
  const UserData = findUser(userId); // ユーザーシートにデータがあるか確認
  typeof UserData === "undefined" ? addUser(userId) : userUseChat(userId); // ユーザーシートにデータがなければユーザー追加、あれば投稿数だけ追加
  const date = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss'); // 現在の日付を取得
  SHEET_LOG.appendRow([userId, UserData, text, replyText, date]); // ログシートに情報追加
  // 日付順に並び替え
  const numColumn_LOG = SHEET_LOG.getLastColumn(); // 最後列の列番号を取得
  const numRow_LOG    = SHEET_LOG.getLastRow()-1;  // 最後行の行番号を取得
  const dataRange_LOG = SHEET_LOG.getRange(2, 1, numRow_LOG, numColumn_LOG);
  dataRange_LOG.sort([{column: 5, ascending: false}]); // 日付順に並び替え
}

// メンバーとしてユーザー登録されているか検索
function findUser(uid) {
  return getUserData().reduce(function(uuid, row) { return uuid || (row.key === uid && row.value); }, false) || undefined;
}

// ユーザー情報取得
function getUserData() {
  const data = SHEET_USER.getDataRange().getValues();
  return data.map(function(row) { return {key: row[0], value: row[1]}; });
}

// ユーザー追加
function addUser(userId) {
  const userName = getUserDisplayName(userId)
  const userIMG  = getUserDisplayIMG(userId)
  const SHEET_ID = sheetCopy(userName)
  const DATE = Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd HH:mm:ss'); // 現在の日付を取得
  SHEET_USER.appendRow([userId, userName, userIMG, 0, SHEET_ID, DATE, DATE])
}

// シートをコピー
function sheetCopy(userName) {
  // そのスプレッドシートのコピーを作成
  const SS_COPY = SS.copy(`[${userName}] ${SS.getName()}`)
  // そのスプレッドシートのIDを取得
  const SHEET_ID = SS_COPY.getId()
  // コピーしたシートのユーザー情報部分は削除
  const SHEET_LOG = SS_COPY.getSheetByName('ログ')
  SHEET_LOG.deleteColumns(1,2)
  const SHEET_USER = SS_COPY.getSheetByName('ユーザー')
  SS_COPY.deleteSheet(SHEET_USER)
  // シートのログデータは全て削除
  const numColumn = SHEET_LOG.getLastColumn() // 最後列の列番号を取得
  const numRow    = SHEET_LOG.getLastRow()-1  // 最後行の行番号を取得
  if (numRow != 0) {
    SHEET_LOG.getRange(2, 1, numRow, numColumn).clear()
  }
  return SHEET_ID
}


// ユーザーのプロフィール名取得
function getUserDisplayName(userId) {
  const url = 'https://api.line.me/v2/bot/profile/' + userId;
  const userProfile = UrlFetchApp.fetch(url,{
    'headers': {
      'Authorization' : 'Bearer ' + LINE_ACCESS_TOKEN,
    },
  })
  return JSON.parse(userProfile).displayName;
}

// ユーザーのプロフィール画像取得 
///////////////////////////////
function getUserDisplayIMG(userId) {
  const url = 'https://api.line.me/v2/bot/profile/' + userId;
  const userProfile = UrlFetchApp.fetch(url,{
    'headers': {
      'Authorization' :  'Bearer ' + LINE_ACCESS_TOKEN,
    },
  })
  return JSON.parse(userProfile).pictureUrl;
}

// ユーザーの投稿数をプラス1
function userUseChat(userId) {

  // 送信したユーザー先のユーザーを検索
  const textFinder = SHEET_USER.createTextFinder(userId);
  const ranges = textFinder.findAll();

  // ユーザーが存在しない場合エラー
  if(!ranges[0]) SHEET_USER.appendRow([userId, "???", '', 1]);;
  
  // ステータスを出勤中から離席中に変更
  const statusFinder = SHEET_USER.createTextFinder('投稿数');
  const statusRanges = statusFinder.findAll();
  const row    = ranges[0].getRow();
  const column = statusRanges[0].getColumn();

  // 投稿数追加 
  let number = SHEET_USER.getRange(row, column).getValue();
  SHEET_USER.getRange(row, column).setValue(++number);
}

config.gs

// API設定部分
const OPENAI_APIKEY     = "sk-*******************************************";
const LINE_ACCESS_TOKEN = "**************************************************************************************************************************************************************************=";

// 使用API
const LINE_REPLY_URL = 'https://api.line.me/v2/bot/message/reply';
const CHAT_GPT_URL   = "https://api.openai.com/v1/chat/completions";
const CHAT_GPT_VER   = "gpt-3.5-turbo";

// スプレッドシートの情報
const SS    = SpreadsheetApp.getActiveSpreadsheet();
const SHEET = SS.getSheetByName('制約');
const SHEET_LOG  = SS.getSheetByName('ログ');
const SHEET_USER = SS.getSheetByName('ユーザー');さ

さいごに

今後、毎月何かしら新しいサービスをリリースしていこうと思っています。今回は1時間くらいで作れたので、毎日新しいサービスを作ろうと思えば作れるんじゃないかとも思いますが、長く続けていくことのほうが大事だと思っているので、毎月に設定しています。

暖かく見守っていただけると嬉しいです。サービス開発で億り人になるぞっ!

Udemy講座もよろしくお願いします!
Udemy講座 : https://www.udemy.com/course/chatgpt-line-function-calling-ai/