hirokiiiii
@hirokiiiii

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

【GAS】Flex message simulatorで作成したデザインが反映されない。

解決したいこと

Googleスプレッドシートに簡単な予約表を作成し、『予約確認』と人間がメッセージを送ると、自動的に予約表の中で空き時間をFlex message simulatorで作成したデザインを送りたい。

下記の画像のイメージで、Flex message simulatorからテスト送信すると、問題なく送れました。

image.png

Googleスプレッドシートで作成している簡単な予約表は下記になります。
空きだけをFlex messageで送信したいと思っています。

image.png

発生している問題・エラー

実際に送られてくるメッセージが下記になります。

image.png

該当するソースコード

実際のソースコードが下記になります。
実行する際には、下記の3点は設定した上で、テストを行っています。
・LINEチャネルのアクセストークン
・スプレッドシートのID
・シート名

const LINE_ACCESS_TOKEN = 'YOUR_LINE_ACCESS_TOKEN'; // LINEチャネルのアクセストークンを設定
const SPREADSHEET_ID = 'YOUR_SPREADSHEET_ID';      // スプレッドシートのIDを設定
const SHEET_NAME = '予約テスト用';                  // シート名を設定

function doPost(e) {
  const json = JSON.parse(e.postData.contents);
  const userMessage = json.events[0].message.text;

  if (userMessage === '予約確認') {
    const replyToken = json.events[0].replyToken;
    const sheetData = getReservationData();
    const replyMessage = createFlexMessage(sheetData);
    sendReplyMessage(replyToken, replyMessage);
  }
  
  return ContentService.createTextOutput(JSON.stringify({ content: 'post ok' })).setMimeType(ContentService.MimeType.JSON);
}

function getReservationData() {
  const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
  const data = sheet.getDataRange().getValues();
  
  let availableSlots = [];
  data.forEach(row => {
    if (row[2] === '空き') { // C列が「空き」の場合を抽出
      availableSlots.push({ date: row[0], time: row[1] });
    }
  });
  
  return availableSlots;
}

function createFlexMessage(slots) {
  if (slots.length === 0) {
    return { type: 'text', text: '現在、予約可能な枠はありません。' };
  }

  const bubbles = slots.map(slot => {
    return {
      type: 'bubble',
      header: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'text',
            text: '予約したい日付を選択ください',
            size: 'md',
            align: 'center',
            weight: 'bold',
            margin: 'lg'
          },
          {
            type: 'text',
            text: '(1つのみ選択可能です)',
            size: 'sm',
            align: 'center',
            gravity: 'top',
            margin: 'lg'
          }
        ]
      },
      body: {
        type: 'box',
        layout: 'vertical',
        contents: [
          {
            type: 'box',
            layout: 'horizontal',
            contents: [
              {
                type: 'button',
                action: {
                  type: 'postback',
                  data: `date=${slot.date}&time=${slot.time}`,
                  label: `${slot.date} ${slot.time}`
                },
                style: 'primary',
                gravity: 'center'
              }
            ],
            justifyContent: 'center',
            alignItems: 'center',
            spacing: 'md'
          }
        ]
      },
      footer: {
        type: 'box',
        layout: 'horizontal',
        contents: [
          {
            type: 'button',
            action: {
              type: 'postback',
              label: 'キャンセル',
              data: 'cancel'
            },
            height: 'sm'
          }
        ],
        spacing: 'xs',
        margin: 'xs'
      }
    };
  });

  return {
    type: 'flex',
    altText: '予約可能な枠があります',
    contents: {
      type: 'carousel',
      contents: bubbles
    }
  };
}

function sendReplyMessage(replyToken, message) {
  const url = 'https://api.line.me/v2/bot/message/reply';
  const headers = {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${LINE_ACCESS_TOKEN}`,
  };
  
  const payload = JSON.stringify({
    replyToken: replyToken,
    messages: [message],
  });

  const response = UrlFetchApp.fetch(url, {
    method: 'post',
    headers: headers,
    payload: payload,
  });

  Logger.log(response.getContentText());
}

自分で試したこと

ChatGPTに何度も質問しながら、『予約確認』と送付すると、Googleスプレッドシート上でC列が『空き』の日付と時間を自動的にメッセージするところまでできましたが、Flex messageを活用できません。

よろしくお願いいたします。

0

2Answer

getValuesを使用してSpreadsheetから値を取得すると、仕様上、日付をDate objectとして取得します。これが原因と思われますので、この場合、別のアプローチとして、関数 getReservationData を下記のように修正するのはいかがでしょうか。

From:

const data = sheet.getDataRange().getValues();

To:

const data = sheet.getDataRange().getDisplayValues();

Class RangeのgetDisplayValues methodは、Spreadsheetに表示されているそのままの値を文字として取得します。Ref Spreadsheetの表示値がそのまま使用できますので使いやすいのではないかと思いました。

2Like

Comments

  1. @hirokiiiii

    Questioner

    ありがとうございます!
    ご指摘いただいた内容で修正を加えてみましたが、うまくいかず。。。
    初歩的なところからつまずいている可能性があるので、まさかこんなところはないだろうと思うところも含め、ご指摘いただけますと幸いです。
    再度一通りお送りいたします。

    実際のコード

    シンプルにするために、まずは1つだけ枠を空いている状態にしています。

    const LINE_ACCESS_TOKEN = PropertiesService.getScriptProperties().getProperty('LINE_ACCESS_TOKEN'); // アクセストークンをプロパティストアから取得
    const SPREADSHEET_ID = 'SPREADSHEET_ID';// 実際のスプレッドシートのIDを入れてます
    const SHEET_NAME = 'SHEET_NAME'; // 実際のシート名を入れてます
    
    function doPost(e) {
      try {
        const json = JSON.parse(e.postData.contents);
        const event = json.events && json.events[0];
        
        if (event && event.type === 'message' && event.message.text === '予約確認') {
          const replyToken = event.replyToken;
          const sheetData = getReservationData();
          const replyMessage = createFlexMessage(sheetData);
          sendReplyMessage(replyToken, replyMessage);
        }
      } catch (error) {
        Logger.log('Error in doPost: ' + error.toString());
      }
      
      return ContentService.createTextOutput(JSON.stringify({ content: 'post ok' })).setMimeType(ContentService.MimeType.JSON);
    }
    
    function getReservationData() {
      const sheet = SpreadsheetApp.openById(SPREADSHEET_ID).getSheetByName(SHEET_NAME);
      const data = sheet.getDataRange().getDisplayValue();
      
      let availableSlots = [];
      data.forEach(row => {
        if (row[2] === '空き') { // C列が「空き」の場合を抽出
          const date = new Date(row[0]);
          const time = new Date(row[1]);
    
          const formattedDate = Utilities.formatDate(date, 'JST', 'MM/dd');
          const formattedTime = Utilities.formatDate(time, 'JST', 'HH:mm');
          availableSlots.push({ date: formattedDate, time: formattedTime });
        }
      });
      return availableSlots;
    }
    
    function createFlexMessage(slots) {
      if (slots.length === 0) {
        return { type: 'text', text: '現在、予約可能な枠はありません。' };
      }
    
      const bubbles = slots.map(slot => ({
        type: 'bubble',
        header: {
          type: 'box',
          layout: 'vertical',
          contents: [
            {
              type: 'text',
              text: '予約したい日付を選択ください',
              size: 'md',
              align: 'center',
              weight: 'bold',
              margin: 'lg'
            },
            {
              type: 'text',
              text: '(1つのみ選択可能です)',
              size: 'sm',
              align: 'center',
              gravity: 'top',
              margin: 'lg'
            }
          ]
        },
        body: {
          type: 'box',
          layout: 'vertical',
          contents: [
            {
              type: 'box',
              layout: 'vertical',
              contents: [
                {
                  type: 'box',
                  layout: 'horizontal',
                  contents: [
                    {
                      type: 'button',
                      action: {
                        type: 'postback',
                        data: `date=${slot.date}&time=${slot.time}`,
                        label: `${slot.date} ${slot.time}`
                      },
                      gravity: 'center',
                      style: 'primary'
                    }
                  ],
                  justifyContent: 'center',
                  alignItems: 'center',
                  spacing: 'md'
                }
              ],
              spacing: 'md'
            }
          ]
        },
        footer: {
          type: 'box',
          layout: 'horizontal',
          contents: [
            {
              type: 'button',
              action: {
                type: 'postback',
                label: 'キャンセル',
                data: 'cancel'
              },
              height: 'sm',
              style: 'secondary'
            }
          ]
        },
        styles: {
          hero: {
            separator: true
          }
        }
      }));
    
      return {
        type: 'flex',
        altText: '予約可能な枠があります',
        contents: {
          type: 'carousel',
          contents: bubbles
        }
      };
    }
    
    function sendReplyMessage(replyToken, message) {
      const url = 'https://api.line.me/v2/bot/message/reply';
      const headers = {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${LINE_ACCESS_TOKEN}`,
      };
      
      const payload = JSON.stringify({
        replyToken: replyToken,
        messages: [message],
      });
    
      try {
        const response = UrlFetchApp.fetch(url, {
          method: 'post',
          headers: headers,
          payload: payload,
        });
        Logger.log('Reply sent: ' + response.getContentText());
      } catch (error) {
        Logger.log('Error in sendReplyMessage: ' + error.toString());
      }
    }
    

    スプレッドシートのスクショ

    image.png

    届くメッセージのスクショ

    image.png

  2. スプレッドシートからのデータ取得は恐らくちゃんとできているのですが、JSONデータへの整形、もしくはJSONデータの受け取り側で問題が発生しているのではないかと思います。
    JSONを送信する時に、JSONがどのようになっているかログを見るのがいいのではないかと思います。
    sendReplyMessageで、下記のようにすれば、送信しているjsonの内容が確認できるのではないかと思います。(たぶん)

    
        messages: [message],
      });
    
     Logger.log(payload); // <---
    
      try {
        const response = UrlFetchApp.fetch(url, {
    
    

    ロガーは下記の記事を参考にしました。
    https://qiita.com/chihiro/items/09c996d41d80f0d30e17

  3. @hirokiiiii

    Questioner

    初歩的な質問ですいません。
    添付の部分でしょうか?

    スクリーンショット 2024-11-17 113520.png

  4. あ、そうですね。全部貼るのは大変だったので手を抜きました。すみません。

    try以降で、送信していると思うので、146行目のところでロガーと設定し、送信前のJSONがどのようか確認すれば、修正箇所がわかるのではないかと思います。
    一度送信してみて、ロガーにどのような値が表示されるか、その値をまたコピペで貼り付けてもらうのがいいかと思います。

以前の質問からかなり進んでいて、驚いています!すごいですね!
LINEのFlex messageはよくわからないので、外しているかもしれませんが、回答してみます。

function getReservationData()
略
      availableSlots.push({ date: row[0], time: row[1] });

ここで日付と時間を取得し、その結果をcreateFlexMessage(sheetData);に返していると思います。
その時に、

日付:Fri Nov 01 2024 00:00:00 GMT+0900(日本標準時),時間:Sat Dec 30 1899 09:00:00
GMT+0900(日本標準時)

と返しており、目的のセルの値を取得し返しているようですが、日付だけ、時間だけとして返せていないのが問題ではないか?と予想しています。

下記の記事が参考になるかもしれません。
ChatGPTなどに聞いても教えてくれそうな気もします。

恐らく型は文字型なんだろうと思うので、
現在のセルの値(日付時間)から、必要な日付のみ、時間のみにして、文字型に変換するようなイメージだと思います。

合っているかわかりませんが、

function getReservationData()
略
      date= Utilities.formatDate(row[0],"JST", "MM/dd");
      time= Utilities.formatDate(row[1],"JST", "hh:mm");
      availableSlots.push({ date: date, time: time });

というような感じにしてはどうでしょうか

1Like

Comments

  1. @hirokiiiii

    Questioner

    またしても、ご回答ありがとうございます!
    週末に頑張ってます!
    ギブアップと同時に質問を投げかけている(次は早めに質問します)ので、また来週末になるかもしれませんが、質問投げかけさせてください!

  2. 修正は、@tanaike さんの案の方が直感的にわかりやすく修正範囲も少なくてスマートだと思いますので、そちらを参考にしていただいた方がいいかなと思います。
    (私のもそんなに間違ってはいないと思いますが、スマートさに欠けるため)

  3. @hirokiiiii

    Questioner

    承知いたしました!
    わざわざありがとうございます!
    またご指摘ください!

Your answer might help someone💌