ZeroScript

ゼロからわかるスクリプト

【コピペ】ゼロからわかる公式LINEに電子メニューBOTを作る方法_前編

アイキャッチ画像
どうも、たべみです。
今日は、公式LINEのトークルームに電子メニューを作成する方法を、前知識ゼロでも分かるよう解説いたします!


公式LINE始めたけど、トークルームを活用できてない、、、そんな方にオススメですよ。
ちなみに、これから紹介する内容は料金かかりません!幸せ。

完成イメージ

「メニュー」と送信すると、メニューのカテゴリが現れ、
それをタップするとそのカテゴリのメニューが表示されます。
「店舗情報」と送信すると、店舗情報が表示されます。

これらはGoogleスプレッドシートを操作して内容を更新することができる仕様になっています。

f:id:tabemi:20210523191252p:plain:w250
メニューと送信するとメニューカテゴリを選択できるようになります。
f:id:tabemi:20210523191257p:plain:w250
ハンバーガーを選択するとハンバーガーのメニューが現れます。
Googleスプレッドシートの画像
Googleスプレッドシート。ここからカテゴリを設定できる。
Googleスプレッドシートの画像
このシートからカテゴリ別にメニューが送信される。いつでも更新ができる。


サンプルを公開していますので、ご覧ください。
lin.ee


おおまかな4ステップ

それでは、作業を開始していきます!
まずは全体の流れを把握しておきましょう。

1.Googleスプレッドシートを用意してする

Googleスプレッドシートの画像
Googleスプレッドシート

Googleスプレッドシートという、Excelのような表アプリにメニュー表を作成していきます。

ここの手順では行いませんが、メニュー表の記入には場所やルールがありますので、それに従って埋めていくことになります。
とは言っても、全く難しくないのでご安心ください!!

2.Google Apps Scriptをコピペする

Google Apps Scriptエディタの画像
Google Apps Scriptエディタ

プログラミングなんてできないよ。。という方でも大丈夫なように、完全コピペ、、、とまではいきませんが、9割5分コピペで大丈夫な作業です。

コピペってなんか情報抜き取るヤバいコードが書いてあるんじゃないの??って不安な方(以前の私。)、大丈夫ですよ(笑)
「プログラミング学習してみたい!」という方、後日コードの解説をしていきます~!

3.公式LINEと紐づける

こちらも天下のLINEさんが素晴らしく分かりやすい設定画面を準備して下さっている(皮肉とかではなくほんとに。)ので、私の拙文でもわかりやすく解説いたします。

すでに公式LINEを持っていらっしゃる方は、なお理解しやすいかもしれませんね!

4.メニューを埋めて実機テスト!

あとは実際にメニュー表(Googleスプレッドシート)を埋めて、公式LINEで動作を確認してみましょう!
ここまでのすべての作業時間は1時間かかるかな?くらいだと思います。
晴れて完成ですよ!

前編では1と2の手順について解説します。

1.Googleスプレッドシートを用意してする

Googleスプレッドシート
Googleスプレッドシート

それでは、実際に作業を始めていきましょう!!

1-1.Googleにログインする

ここにアクセスしてみましょう。Googleドライブにアクセス出来たら成功です。
ログイン画面が出てきた方はログインしましょう。

Googleログイン画面
ログイン画面が出てきたらログインしましょう

Googleアカウント持ってないよ!という方は、「アカウントを作成」をクリックして新しく作成しましょう。
作り方は、こちら↓です
support.google.com
また、こちらの解説が大変分かりやすいので参考にされると良いかもしれません。
Googleアカウントの作成方法を丁寧に全解説【PC・スマホ】 | アクセス中古ドメイン
ちなみに、名前は本名でなくて大丈夫ですよ。

1-2.新しいGoogleスプレッドシートを作成する

ログイン後の画面がこちら。

Googleドライブの画像
Googleドライブ

画面左上の「+新規」をクリックし、「Googleスプレッドシート」を選択して下さい。

新規ファイルの作成の画像
新規ファイルの作成
f:id:tabemi:20210523162249p:plain:w200
スプレッドシートを選択する

これでGoogleスプレッドシートが作成できました。いやん、簡単。涙出てくる。

Googleスプレッドシートの画像
Googleスプレッドシート

「無題のスプレッドシート」とタイトルがついているかと思われますので、お好きなタイトルに変更すると良きかもしれないですよ。

2.Google Apps Scriptをコピペする

Google Apps Scriptのエディタ画面の画像
Google Apps Scriptのエディタ画面

プログラムをコピー&ペーストしていきます。なんども言いますが、情報を抜き取ったり、危ない感じになったり?、そんなことはありませんのでどうかご安心を。。。

2-1.コピペする場所(エディタ)に移動する

さて、先ほど作成したGoogleスプレッドを開いてください。開いてるよって?

そしたら、上部の「ツール」をクリックし、上から3番目の「〈〉スクリプト エディタ」を開いてください。すると別タブにそれっぽい画面が表示されると思います。

f:id:tabemi:20210523162415p:plain
「ツール」を選択し、「<> スクリプト エディタ」をクリック

あれ、全然動かなくなちゃったんだけど、って方は、一旦スプレッドシートとそのエディタもタブを消して、もう一度Googleドライブから入りなおして同じ作業を繰り返してみてください。

2-2.myFunction(){ }を削除する

f:id:tabemi:20210523162235p:plain
スクリプトエディタ

それっぽい画面の中央付近に「myFunction(){ (改行) }」とありますね。これをすべて消してください。普通に「BackSpace」キーで削除できます。

f:id:tabemi:20210523162240p:plain
削除した後のエディタ

2-3.コードをコピペする

「myFunction(){ (改行) }」のあった部分に、下のコードをコピーしてペースト(貼り付け)てみましょう。コピーは「Ctrl+c」で、ペーストは「Ctrl+v」でできます。右クリックしてもできますよ。

const ACCESS_TOKEN = "【ここを削除】"// => 【】も削除!
const SPREAD_SHEET = SpreadsheetApp.getActiveSpreadsheet();

//スプレッドシートを所定の書式に変更する
//
function initFunction() {
  //新しいシートを作成する関数
  const createSheets = ({ sheetName, values }) => {
    try {
      const sheet = SPREAD_SHEET.insertSheet(sheetName);//新しいシートを作成する
      console.log(sheetName + "を作成しました。");
      if (!values) {//valuesがなけ(データ取得用のシートであ)れば、非表示にするシート
        sheet.hideSheet();
      } else {//編集用のシートであれば
        const row = values.length;
        const column = values[0].length;
        sheet.getRange(1, 1, row, column).setValues(values);//新しいシートに値を貼り付ける
      }
    } catch (e) {
      console.log(sheetName + "の作成に失敗しました。");
      console.error(e);
    }
  }

  //新しく作成するシートの情報
  ////編集用のシート=>日本語のシート, データ取得用のシート=>ローマ字のシート
  const sheetsInfo = [
    {
      sheetName: "店舗情報",
      values: [["店舗情報"],[ "店舗名:\n店舗住所:\n店舗SNS:"]],
    }, {
      sheetName: "カテゴリ",
      values: [["カテゴリ名"], ["ハンバーガー"]],
    }, {
      sheetName: "メニュー",
      values: [["カテゴリ", "メニュー名", "価格", "説明"], ["ハンバーガー", "バーベキューハンバーガー", 1000, "おいしいよ。"]],
    }, {
      sheetName: "info"
    }, {
      sheetName: "category"
    }, {
      sheetName: "menu"
    }
  ];

  sheetsInfo.forEach(createSheets);//新しいシートを作成する処理を行う
  ScriptApp.newTrigger("onOpen").forSpreadsheet(SPREAD_SHEET).onOpen().create();//起動時にonOpen関数を実行するトリガーを設置する
}


function onOpen() {
  const myMenu = [
    { name: '更新を登録', functionName: 'updateMenu' }
  ];
  SPREAD_SHEET.addMenu('<登録>', myMenu);
}

//編集用のシート=>fromSheet, データ取得用のシート=>toSheet
class Sheet {
  constructor(sheetName) {
    const sheetNumsObj = {
      "category": {
        fromSheetName: "カテゴリ",
        numRows: 13,
        numColumns: 1
      },
      "menu": {
        fromSheetName: "メニュー",
        numRows: 10,
        numColumns: 4
      },
      "info": {
        fromSheetName: "店舗情報",
        numRows: 2,
        numColumns: 1
      }
    };
    const sheetNums = sheetNumsObj[sheetName];
    this.toRange = SPREAD_SHEET.getSheetByName(sheetName)
      .getRange(1, 1, sheetNums["numRows"], sheetNums["numColumns"]);
    this.numRows = sheetNums["numRows"];
    this.numColumns = sheetNums["numColumns"];
    this.fromSheetName = sheetNums["fromSheetName"];
  }
  getValues() {
    return this.toRange.getValues();
  }
  peastValues() {
    const fromValues = SPREAD_SHEET.getSheetByName(this.fromSheetName)
      .getRange(2, 1, this.numRows, this.numColumns)
      .getValues();//編集シートの値を取得
    this.toRange.setValues(fromValues);
  }
}

function updateMenu() {
  const toSheets = ["category", "menu", "info"];
  toSheets.forEach(sheetName => {
    const sheet = new Sheet(sheetName);
    sheet.peastValues();
  });
}



//LINEから呼び出される。
function doPost(e) {
  // WebHookで受信した応答用Token
  const event = JSON.parse(e.postData.contents).events[0];
  const url = 'https://api.line.me/v2/bot/message/reply';
  const payload = getPayload(event);
  const headers = {
    'Content-Type': 'application/json; charset=UTF-8',
    'Authorization': 'Bearer ' + ACCESS_TOKEN
  }
  const options = {
    'headers': headers, 'method': 'post',
    'payload': JSON.stringify(payload),
    // muteHttpExceptions: true, // エラーをデバックする場合、コメントオフ
  };
  try {
    UrlFetchApp.fetch(url, options);//LINEに情報を流す
  } catch (e) {
    console.error(e);
  }
  return ContentService
    .createTextOutput(JSON.stringify({ 'content': 'post ok' }))
    .setMimeType(ContentService.MimeType.JSON);
}

function getPayload(event) {
  const replyToken = event["replyToken"];//LINEに送信するToken
  const type = event["type"];
  console.log("type:" +type)
  //受け取ったメッセージタイプがmessageであれば
  if (type === "message") {
    const text = event["message"]["text"];
    if (text === "メニュー") {//テキスト内容が"メニュー"で
      return {
        'replyToken': replyToken,
        "messages": makeCategoryMessages()
      };
    } else if (text === "店舗情報") {
      return {
        'replyToken': replyToken,
        "messages": makeInfoMessages()
      };
    }

    //受け取ったメッセージタイプがpostboackイベント(カテゴリをタップした場合にpostbackになる)であったら
  } else if (type === "postback") {
    const categoryName = event["postback"]["data"];
    return {
      'replyToken': replyToken,
      "messages": makeMenuMessages(categoryName)
    };
  }
}


//category を返す関数
function makeCategoryMessages() {
  const createContent = (categoryName) => {
    const action = {
      "type": "postback",
      "label": categoryName,
      "data": categoryName
    }
    return {
      "type": "action",
      "action": action
    }
  };

  const categorySheet = new Sheet("category");
  const values = categorySheet.getValues();//[[categoryName0],[categoryName1],...];
  const items = values.reduce((array, x) => {
    const categoryName = x[0];
    if (!categoryName) return array;//categoryNameが空欄であれば追加しない。
    return [...array, createContent(categoryName)];
  }, []);
  console.log(items);
  if (!items.length) return [{ "type": "text", "text": "メニューがありません" }];

  return [{
    "type": "text",
    "text": "メニューカテゴリです",
    "quickReply": { "items": items }
  }];
}


//menuを返す関数
function makeMenuMessages(categoryName) {
  console.log(categoryName);
  const numberWithDelimiter = (number) => {
    return "¥" + String(number).replace(/(\d)(?=(\d\d\d)+(?!\d))/g, '$1,');
  }
  const createContent = ([menuName, price, discription]) => {
    return {
      "type": "bubble",
      "header": {
        "type": "box",
        "layout": "vertical",
        "contents": [],
        "backgroundColor": "#666666"
      },
      "body": {
        "type": "box",
        "layout": "vertical",
        "contents": [
          {
            "type": "text",
            "text": menuName,
            "size": "xl"
          },
          {
            "type": "text",
            "text": numberWithDelimiter(price),
            "style": "italic",
            "align": "end",
            "size": "lg"
          },
          {
            "type": "text",
            "text": discription,
            "wrap": true,
            "color": "#666666",
            "margin": "lg"
          }
        ]
      }
    }

  }

  const menuSheet = new Sheet("menu");
  //[["メニュー名", "価格", "説明"],[],...]
  const values = menuSheet
    .getValues()
    .reduce((array, x) => {
      if (x[0] !== categoryName || !x[1]) return array;//カテゴリ名が同じメニューのみ取り出す。
      return [...array, [x[1], x[2], x[3]]];
    }, []);

  if (values === []) return [{ "type": "text", "text": "メニューがありません" }];
  const contents = values.reduce((array,x) => [ ...array, createContent(x)] ,[]);
  console.log(contents);
  return [{
    type: "flex",
    altText: categoryName + "メニューです",
    contents: {
      type: "carousel",
      contents: contents
    }
  }];
}

function makeInfoMessages() {
  const infoSheet = new Sheet("info");
  const infoText = infoSheet.getValues()[0][0];//B1セルを取得
  console.log(infoText)
  return [{ "type": "text", "text": infoText }];
}

過不足なく、全て貼り付けてください。

そして、「Ctrl+s」で保存します。マックの方は、「Comand+s」です。

f:id:tabemi:20210523162319p:plain
貼り付け後はこんな感じになります

これ以降の作業でエラーが出る場合正確に張り付けられていないことが原因です!

2-4.▶ボタンを押してみる

ペーストして物々しい雰囲気となったエディタ画面の上部に注目してみましょう。再生ボタンのようなアイコンがあるかと思います。押してみましょう。

f:id:tabemi:20210523162409p:plain
実行ボタンをクリック

すると、、ウィンドウが出てきますね。。

f:id:tabemi:20210523162329p:plain
ウィンドウが出てくる。「権限を確認」をクリック。キャンセルは押しません。

2-5.承認画面を承認する

これは、「あなたに代わってプログラムが色々するけど、大丈夫かい?」という許可をGoogleが求めてきています。許可されない場合、このプログラムは動きませんので、承認しましょう

手順は、

  1. Googleアカウントを選択
  2. 左下「詳細」>「 安全でないページに移動」をクリック
  3. 下にスクロール「許可」をクリック

です。

f:id:tabemi:20210523162340p:plain:w200
Googleアカウントを選択
f:id:tabemi:20210523162346p:plain:w200
詳細をクリック
f:id:tabemi:20210523162354p:plain:w200
焦りますが、「安全ではないページに移動」をクリック
f:id:tabemi:20210523162404p:plain:w200
スクロールして「許可」を選択

こちらのプログラムは、、、

以外、行っていません!ご安心を!とくに1年前くらいの私!

そしたら、コードが実行できるようになっているので、もう一度「再生ボタン」を押してみましょう

エディタ画面
もう一度、実行ボタンを押すと、下から画面が出てきて、成功したことが分かる

「実行完了」が出ていればOKです!

最後に

今回は、公式LINEに電子メニューBOTを作る方法の前編として、Googleスプレッドシートの作成、Google Apps Scriptのコピペを解説しました。不明点あれば、ぜひコメントでお知らせください
また、Googleアプリでこんなこと自動化できない?Google Apps Scriptで書いてみたいこと、分からないことなど、お気軽にコメント欄でお知らせください!

それでは、次回は公式LINEの設定とメニュー表を埋めてLINEでの動作を確認してみましょう!

ではでは~