【GAS】#10 ウェブアプリのフォーム入力からスプレッドシートへ記載とLineへ通知する

1. 概要

GAS Web App to Line

申込があった場合、下記処理を行う事で申込管理をしやすくする。

  • スプレッドシートへ記載
    • 名簿や集計用
  • Lineへ通知
    • 状況確認用

本記事では「API連携」による通知について解説します。

2. LINE Messaging APIの準備

2-1. 「LINE Developers」にアクセス

  • 「ログイン」をクリック

2-2 LINEアカウントでログイン

2-3. 「LINE Developers Account」を作成

2-4. 「Provider」を作成

2-5. 「Channel」を作成

2-6. 「User ID」を取得

2-7. 「Access Token」を取得

3. ウェブアプリ(HTML)の準備

3-1. New Deploymentを選択

3-2. Web appを選択

3-3. 内容を記載

3-4. Deployment完了

3-5. 申込フォーム画面のHTMLファイルを作成

3-6. 申込完了画面のHTMLを作成

4. サンプルコード

4-1. ファイル構成(GitHubで管理

  • notification/line/app_form_to_spreadsheet_and_line.gs
    • メインコード
  • notification/html/entry_form.html
    • 申込フォーム画面
  • notification/html/thanks.html
    • 申込完了画面
  • notification/slack/gas_properties.gsheet
    • 「WebHook URL」を含め、コード内に書かない方が良いデータ(ID、PASSWORD、KEY等)をプロパティとして保存

4-2. スプレッドシートIDの取得

4-3. GAS Editorの開け方

※「+新規」をクリック

4-4. コード & 解説

※複数のプログラムよりプロパティファイルを共有する為、敢えてスタンドアロン型(*6-2)を採用する

// プロパティ情報が記載されているスプレッドシートのID
const bookId = '1j2z-S●●●●●●●●●●●●●●●●●●●●●●●●●●●●cQk';

// 申込フォーム
const doGet = () => {
  const html = HtmlService.createTemplateFromFile('entry_form');
  html.app_url = getValueOfProperty('H32');
  return html.evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1, user-scalable=no').setTitle('申込フォーム');
}

// 申込フォーム完了
// ・スプレッドシートへ記録
// ・Lineへ通知
// ・完了画面に申込内容を表示
const doPost = (e) => {
  const eventMap = getEventInfo(e);

  recordToSpreadSheet(eventMap);
  sendEvent(eventMap);

  // 完了画面に申込内容を表示
  const html = HtmlService.createTemplateFromFile('thanks');
  html.joinTime = eventMap.get('joinTime');;
  html.name = eventMap.get('name');
  html.tel = eventMap.get('tel');
  html.notes = eventMap.get('notes');
  return html.evaluate().addMetaTag('viewport', 'width=device-width, initial-scale=1, user-scalable=no').setTitle('申込完了');
}

// スプレッドシートへ記録
const recordToSpreadSheet = (eventMap) => {
  const sheet = getTargetSheet(getValueOfProperty('G32'));
  const event = [eventMap.get('now'), eventMap.get('joinTime'), eventMap.get('name'), eventMap.get('tel'), eventMap.get('notes')];
  sheet.appendRow(event);
}

// 申込内容を取得
const getEventInfo = (e) => {
  let eventMap = new Map();
  eventMap.set('joinTime', e.parameter.joinTime + '時');
  eventMap.set('name', e.parameter.name);
  eventMap.set('tel', e.parameter.tel);
  eventMap.set('notes', e.parameter.notes);
  eventMap.set('now', Utilities.formatDate(new Date(), 'JST', 'YYYY-MM-dd HH:mm:ss'));
  return eventMap;
}

// Lineへ通知
const sendEvent = (eventMap) => {
  const joinTime = eventMap.get('joinTime');
  const name = eventMap.get('name');
  const tel = eventMap.get('tel');
  const notes = eventMap.get('notes');
  const now = eventMap.get('now');

  const eventInfo = `【申込日時】${now}\n【参加時間】${joinTime}\n【名前】${name}\n【連絡先】${tel}\n【備考】${notes}`;
  sendToLine(eventInfo);
}

// 申込用スプレッドシート
const getTargetSheet = (sheetName) => {
  const spreadsheetId = getValueOfProperty('D32');
  const spreadsheet = SpreadsheetApp.openById(spreadsheetId);
  return spreadsheet.getSheetByName(sheetName);
}

// 指定のスプレッドシートに記載されているプロパティの値を取得
const getValueOfProperty = (cell) => {
  const sheet = SpreadsheetApp.openById(bookId).getSheetByName('Properties');
  return sheet.getRange(cell).getValue();
}

// メッセージをLineへ通知
const sendToLine = (msg) => {
  // Message Push API Url
  const targetUrl = getValueOfProperty('C32');
  // アクセストークン
  const accessToken = getValueOfProperty('E32');
  // BotユーザーID
  const to = getValueOfProperty('F32');

  const headers = {
    'Content-type': 'application/json; charset=UTF-8',
    'Authorization': 'Bearer ' + accessToken,
  };

  const param = {
    'to': to,
    'messages': [
      {
        'type': 'text',
        'text': msg,
      },
    ],
  };

  const payload = JSON.stringify(param);

  const options = {
    'method': 'post',
    'headers': headers,
    'payload': payload,
  };

  UrlFetchApp.fetch(targetUrl, options);
}
<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
    <title>卓球レッスン申込</title>
  </head>
  <body>
    <div class="container">
      <div class="container-fluid">
        <form action="<?= app_url ?>" method="POST" name="entryForm">
          <div class="card text-white bg-primary mb-3">
            <div class="card-header">申込フォーム</div>
            <div class="card-body">
              <h5 class="card-title">卓球レッスン申込</h5>
              <p class="card-text">9月20日(月)、2時間コース、1000円</p>
            </div>
          </div>
          <div class="card text-dark bg-light mb-3">
            <div class="card-body">
              <h5 class="card-title">参加時間を選択して下さい。</h5>
              <div class="form-check">
                <input class="form-check-input" type="radio" name="joinTime" id="joinTime1" value="10">
                <label class="form-check-label" for="joinTime1">
                  10時
                </label>
              </div>
              <div class="form-check">
                <input class="form-check-input" type="radio" name="joinTime" id="joinTime2" value="14" checked>
                <label class="form-check-label" for="joinTime2">
                  14時
                </label>
              </div>
              <div class="form-check">
                <input class="form-check-input" type="radio" name="joinTime" id="joinTime3" value="18">
                <label class="form-check-label" for="joinTime3">
                  18時
                </label>
              </div>
            </div>
          </div>
          <div class="card text-dark bg-light mb-3">
            <div class="card-body">
              <h5 class="card-title">お名前を教えて下さい。</h5>
              <div class="form-floating mb-3">
                <input type="text" class="form-control" name="name" id="name" placeholder="田中">
                <label for="floatingInput">お名前</label>
              </div>
            </div>
          </div>
          <div class="card text-dark bg-light mb-3">
            <div class="card-body">
              <h5 class="card-title">ご連絡先を教えて下さい。</h5>
              <div class="form-floating mb-3">
                <input type="text" class="form-control" name="tel" id="tel" placeholder="090-1234-5678">
                <label for="floatingInput">ご連絡先</label>
              </div>
            </div>
          </div>
          <div class="card text-dark bg-light mb-3">
            <div class="card-body">
              <h5 class="card-title">ご質問などあれば記載して下さい。</h5>
              <div class="mb-3">
                <textarea class="form-control" name="notes" id="notes" rows="3"></textarea>
              </div>
            </div>
          </div>
          <button type="submit" class="btn btn-primary">Submit</button>
        </form>
      </div>
    </div>
  </body>
</html>

<!DOCTYPE html>
<html>
  <head>
    <base target="_top">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js" integrity="sha384-/bQdsTh/da6pkI1MST/rWKFNjaCP5gBSY4sEBT38Q/9RBh9AH40zEOg7Hlq2THRZ" crossorigin="anonymous"></script>
    <title>申込完了</title>
  </head>
  <body>
    <div class="container">
      <div class="container-fluid">
        <div class="card text-white bg-primary mb-3">
          <div class="card-header">申込完了</div>
          <div class="card-body">
            <h5 class="card-title"><?= name ?>さん、ご申込ありがとうございます!</h5>
            <p class="card-text"><?= name ?>さんの申込内容</p>
          </div>
        </div>
        <ul class="list-group">
          <li class="list-group-item">【参加時間】<?= joinTime ?></li>
          <li class="list-group-item">【ご連絡先】<?= tel ?></li>
          <li class="list-group-item">【ご質問】<?= notes ?></li>
        </ul>
      </div>
    </div>
  </body>
</html>

5. 実行結果例

【申込フォーム画面】

【申込完了画面】

【スプレッドシート】

【Line】

6. 参考

6-1. GAS(Google Apps Script)とは

6-2. 2種類の方式

6-3. Web APIとは

6-4. 「UrlFetchApp.fetch()」について

関連記事

  1. 【GAS】#2 Google Calendarに登録されている予定をS…

  2. 【GAS】#7 GoogleForms入力からスプレッドシートへ記載と…

  3. 【GAS】#11 Twitterの話題を検索し、スプレッドシートに記載…

  4. 【GAS】#8 TwitterのPublic Metricsデータを取…

  5. 【GAS】#12 指定フォルダ(下位フォルダ含む)に更新されたファイル…

  6. 【GAS】#3 NewsAPI.orgからデータを取得し、Slackへ…