Yuki's bnb blog

こんにちは!Yukiといいます。本業のかたわら大阪で2016年夏から民泊運営のお手伝いをしています。民泊業務に関する様々なことを自動化・効率化したいと思い日々活動しています。 お気軽にコメント・お問い合わせください :) TOEICスコア 985

【Beds24のAPI連携シリーズ】チェックアウト&イン一覧を毎朝自動LINE通知するGAS

Beds24 check out in

こんにちは!
Yuki (@yukibnb) です。

今回はBeds24を使用している民泊運営者の方向けに、チェックアウトとチェックインの部屋一覧を毎朝自動でLINE通知する方法を紹介します。

 

memo

チェックアウトの一覧のみLINEしたい方は以下の記事をご覧ください。

【Beds24のAPI連携シリーズ】チェックアウト一覧を毎朝自動LINE通知するGAS

では見ていきましょう!

 

はじめに

実現したいこと

Beds24 LINE

この画像のようにその日のチェックアウトとチェックインの一覧を毎朝自動でLINE通知します。

通知する部屋情報は施設名 - 部屋名(ユニット名)としています。

チェックアウト一覧のうち同日にチェックインがある部屋を★施設名 - 部屋名(ユニット名)《チェックイン予定時刻》と表記して目立たせています。

チェックイン一覧の部屋はすべてチェックイン予定時刻を表記しています。

beds24 arrival time

チェックイン予定時刻はBeds24の予約詳細画面の[Arrival Time]の値を取得しています。もし[Arrival Time]に何も入力されていない場合、チェックイン予定時刻は《未定》と表示されます。

同日にチェックインがある部屋を目立たせ、且つチェックイン予定時刻を表記することで清掃順の優先順位付けに役立ちます。

 

使用するもの

主に以下3つのサービスを使って実現します。

  • Beds24のAPI
  • Google Apps Script (GAS)
  • LINE Notify

当記事で紹介する通知内容を独自にカスタマイズしたい場合Google Apps Scriptの知識が必要ですが、そのまま使うだけでしたら予備知識がなくても設定できるように紹介しますのでご安心ください。

大半は過去に当ブログで紹介した各種方法の組み合わせで実現できます。

 

コピペでOK!チェックアウト&イン一覧を毎朝自動LINE通知するGAS

GASのスクリプトエディタを開く

まずGoogle Apps Script (GAS) のスクリプトエディタを開いてください。

スタンドアロンでも大丈夫ですが、空のスプレッドシートを作成してスクリプトエディタを開く(コンテナバインド)方が後々カスタマイズしやすいのでおすすめします。

memo

スクリプトエディタの開き方がわからない場合、以下記事で方法を解説していますのでご覧ください。
【Beds24のAPI連携シリーズ】Google Apps Scriptを開こう

 

Moment.jsライブラリを追加する

今回紹介するGoogle Apps ScriptではMoment.jsというライブラリを使用します。

以下記事を参考にMoment.jsライブラリを導入してください。記事内の手順に沿って頂くと簡単に導入できます。

www.yukibnb.com

Moment.jsを使うとGoogle Apps Script内で日付や時間を簡単に記述することができます。

今回は「本日チェックアウトがある部屋を抽出」「本日チェックインがある部屋を抽出」「本日チェックアウトがあり、且つチェックインもある部屋を目立たせる」など日付に関する処理を多数実施します。Moment.jsを使うことでそれらの処理をよりシンプルに記述できます。

 

スクリプトエディタにスクリプトをコピペする

スクリプトエディタに以下をまるごとコピペしてください。

//★★★ Beds24のログインIDを記入 ★★★
const USERID = "Beds24のログインID";

//★★★ Beds24のAPIキーを記入 ★★★
const APIKEY = "Beds24のAPIキー";

//★★★ LINE Notifyのアクセストークンを記入 ★★★
const LINETOKEN = "LINE Notifyのアクセストークン";

function checkOutAndIn() {
  //本日の日付を取得する
  let today = Moment.moment();
  let todayFormatted = today.format("YYYY/MM/DD");
  
  //3ヶ月前の日付を"YYYY-MM-DD"で抽出する
  let datefrom = today.clone().subtract(3, "months");
  let datefrom_formatted = datefrom.format("YYYY-MM-DD");
  
  //1ヶ月後の日付を"YYYY-MM-DD"で抽出する
  let dateto = today.clone().add(1, "months");
  let dateto_formatted = dateto.format("YYYY-MM-DD");
  
  //POST先のURL
  let url = "https://www.beds24.com/api/csv/getbookingscsv"
  
  let options = {
    "method": "post",
    "followRedirects": false,
    "payload": {
      "username": USERID,
      "password": APIKEY,
      "datefrom": datefrom_formatted,
      "dateto": dateto_formatted
    }
  }
  
  //CSVをBeds24からダウンロード
  let csv = UrlFetchApp.fetch(url,options);
  
  //ダウンロードしたCSVを二次元配列に変換する
  let csvContents = Utilities.parseCsv(csv);
  
  //必要な情報のインデックスを調べる
  //※今回使用していないものもあります
  //カスタマイズしたい場合参考にしてください
  let property = csvContents[0].indexOf("Property");
  let room = csvContents[0].indexOf("Room");
  let roomId = csvContents[0].indexOf("Roomid");
  let unit = csvContents[0].indexOf("Unit");
  let unitId = csvContents[0].indexOf("Unitid");
  let bookingId = csvContents[0].indexOf("Ref") //Beds24の予約番号
  let status = csvContents[0].indexOf("Status");
  let checkIn = csvContents[0].indexOf("FirstNight"); //チェックイン日
  let checkOut = csvContents[0].indexOf("CheckOut"); //チェックアウト日
  let firstName = csvContents[0].indexOf("First Name"); //名
  let lastName = csvContents[0].indexOf("Name"); //姓
  let balance = csvContents[0].indexOf("Balance"); //バランス (残高)
  let originalReferer = csvContents[0].indexOf("Original Referer"); //予約元
  let adultNum = csvContents[0].indexOf("Adult");
  let childNum = csvContents[0].indexOf("Child"); 
  let arrivalTime = csvContents[0].indexOf("Arrival"); 
  let notes = csvContents[0].indexOf("Notes"); 
  
  //本日チェックインの予約情報を格納するための空配列を宣言する
  let checkInToday = [];
  
  //本日チェックアウトとチェックインの両方がある部屋を判別するための情報確報用の空配列を宣言する
  let checkOutAndInToday = [];
  
  //本日チェックインがある部屋のチェックイン時間を判別するための情報確報用の空配列を宣言する
  let checkInTime = [];
  
  //本日チェックインの予約を抽出する
  for(let i = 1; i < csvContents.length; i++) {
    let checkInDate = Moment.moment(csvContents[i][checkIn], "YYYY-MM-DD");
    
    //ステータスが"Cancelled"の予約は除外する
    if(today.isSame(checkInDate, "day") && csvContents[i][status] !== "Cancelled") {
      checkInToday.push(csvContents[i]);
      checkOutAndInToday.push("" + csvContents[i][roomId] + csvContents[i][unitId]);
      
      if(csvContents[i][arrivalTime] == "") {
        checkInTime.push(["" + csvContents[i][roomId] + csvContents[i][unitId], "未定"]);  
      } else {
        checkInTime.push(["" + csvContents[i][roomId] + csvContents[i][unitId], csvContents[i][arrivalTime]]);
      }
    }
  }
  
  //本日チェックアウトの予約情報を格納するための空配列を宣言する
  let checkOutToday = [];
  
  //本日チェックアウトの予約を抽出する
  for(let i = 1; i < csvContents.length; i++) {
    let checkOutDate = Moment.moment(csvContents[i][checkOut], "YYYY-MM-DD");
    
    //ステータスが"Cancelled"の予約は除外する
    if(today.isSame(checkOutDate, "day") && csvContents[i][status] !== "Cancelled") {
      checkOutToday.push(csvContents[i]);
    }
  }
  
  //通知文章の一部格納するための空配列を宣言する
  let checkOutList = [];
  
  for(let j = 0; j < checkOutToday.length; j++) {
    let checkOutRoomAndUnit = "" + checkOutToday[j][roomId] + checkOutToday[j][unitId];

    //チェックアウトとチェックインの両方ある部屋は頭に★を付与する
    if(checkOutAndInToday.indexOf(checkOutRoomAndUnit) >= 0){
    
      //チェックイン時間を格納するための空配列を宣言する
      let checkInTimeToday = [];
    
      //チェックイン時間をcheckInTimeTodayに格納する
      for(let k = 0; k < checkInTime.length; k++) {
        if(checkInTime[k].indexOf(checkOutRoomAndUnit) >= 0) {
          checkInTimeToday.push(checkInTime[k][1]);
        }
      }

      //当日チェックインがある部屋の文面
      checkOutList.push("★" + checkOutToday[j][property] + " - " +
                        checkOutToday[j][room] + "(" +
                        checkOutToday[j][unit] + ")" +
                        " 《" + checkInTimeToday[0] + "》"
                        );
    } else {
      //当日チェックインがない部屋の文面
      checkOutList.push(checkOutToday[j][property] + " - " +
                        checkOutToday[j][room] + "(" +
                        checkOutToday[j][unit] + ")"
                        );
    }
  }
  
  //通知文章の一部格納するための空配列を宣言する
  let checkInList = [];
  
  for(let l = 0; l < checkInToday.length; l++) {
    //チェックイン時間が空白の場合「未定」を代入する
    if(checkInToday[l][arrivalTime] == "") {
      checkInToday[l][arrivalTime] = "未定"
    }
    
    checkInList.push(checkInToday[l][property] + " - " +
                      checkInToday[l][room] + "(" +
                      checkInToday[l][unit] + ")" +
                      " 《" + checkInToday[l][arrivalTime] + "》"
                      );
  }
  
  //通知文章を格納するための変数を宣言する
  let content;

  //チェックアウトとチェックイン共にある場合
  if(checkOutToday.length > 0 && checkInToday.length > 0) {
    content = "\n日付: " + todayFormatted + "\n\n" +
              "【本日チェックアウト】\n" +
              "合計: " + checkOutList.length + "件" +
              "\n\n" +
              checkOutList.join("\n") +
              "\n\n" +
              "【本日チェックイン】\n" +
              "合計: " + checkInList.length + "件" +
              "\n\n" +
              checkInList.join("\n");

  //チェックアウトのみある場合
  } else if(checkOutToday.length > 0) {
    content = "\n日付: " + todayFormatted + "\n\n" +
              "【本日チェックアウト】\n" +
              "合計: " + checkOutList.length + "件" +
              "\n\n" +
              checkOutList.join("\n") +
              "\n\n" +
              "【本日チェックイン】\n" +
              "合計: " + checkInList.length + "件" +
              "\n本日チェックインはありません。";

  //チェックインのみある場合
  } else if(checkInToday.length > 0){
    content = "\n日付: " + todayFormatted + "\n\n" +
              "【本日チェックアウト】\n" +
              "合計: " + checkOutList.length + "件" +
              "\n本日チェックアウトはありません。" +
              "\n\n" +
              "【本日チェックイン】\n" +
              "合計: " + checkInList.length + "件" +
              "\n\n" +
              checkInList.join("\n");

  //チェックアウトとチェックイン共にない場合
  } else {
    content = "\n日付: " + todayFormatted + "\n\n" +
              "【本日チェックアウト】\n" +
              "合計: " + checkOutList.length + "件" +
              "\n本日チェックアウトはありません。" +
              "\n\n" +
              "【本日チェックイン】\n" +
              "合計: " + checkInList.length + "件" +
              "\n本日チェックインはありません。";
  }                

  sendLine(content);                      
}

// LINE notifyへの通知
function sendLine(content){
  //「&」が文章に含まれると「&」以降の文章が通知されないため、
  //「&」を「%26」で置き換えます
  //実際のライン上では「%26」は「&」と表示されます
  let contentLine = content.replace(/&/g, "%26");
  let options =
   {
     "method"  : "post",
     "payload" : "message=" + contentLine,
     "headers" : {"Authorization" : "Bearer "+ LINETOKEN}
   };
 
   UrlFetchApp.fetch("https://notify-api.line.me/api/notify", options);
}

 
コピペすると以下のようになります。
Beds24 Google Apps Script

 

一部情報を置き換える

先程ペーストしたスクリプトの中の3か所をみなさん自身のものに置き換えてください。

Beds24のログインID
//★★★ Beds24のログインIDを記入 ★★★
const USERID = "Beds24のログインID";

ご自身のBeds24のログインIDに置き換えてください。

ログインIDは以下箇所のことです。
f:id:yukibnb:20200224223915p:plain

 

Beds24のAPIキー
//★★★ Beds24のAPIキーを記入 ★★★
const APIKEY = "Beds24のAPIキー";

ご自身のBeds24のAPIキーに置き換えてください。

APIキーの取得方法は以下記事をご覧ください。
www.yukibnb.com

 

注意

APIキーはBeds24のログインパスワードではありません。

 

LINE Notifyのアクセストークン
//★★★ LINE Notifyのアクセストークンを記入 ★★★
const LINETOKEN = "LINE Notifyのアクセストークン";

ご自身のLINE Notifyのアクセストークンに置き換えてください。

アクセストークンの取得方法は以下記事をご覧ください。
www.yukibnb.com

 

スクリプトを保存する

Beds24 Google Apps Script

スクリプトエディタの画面上部の[保存]アイコンをクリックして、スクリプトを保存してください。

これで本日チェックアウトする部屋一覧をLINE通知するするスクリプトの記述が完了しました。

 

スクリプトを手動実行しよう

スクリプトを選択する

Google Apps Script

スクリプトエディタの画面上部のプルダウンから[checkOutAndIn]を選択してください。

 

スクリプトの初回承認を行う

Google Apps Script

スクリプトエディタの画面上部の[実行]をクリックしてください。

すると以下のようなポップアップが表示されます。

Google Apps Script authorization

これは[checkOut]を実行するための初回承認です。承認しないとスクリプトを実行できないため、[権限を確認]をクリックし承認を行ってください。

なお[権限を確認]をクリックした後の詳しい承認方法については以下記事に画像や動画付きで詳しく紹介していますので、そちらをご覧ください。
www.yukibnb.com

 

[checkOutAndIn]を手動実行する

初回承認が完了すると自動的に[checkOutAndIn]が実行されます。

もし何も動きがなければ、画面上部の[実行]をクリックすると[checkOutAndIn]を実行できます。

数秒でスクリプトの実行が完了し、LINE通知が届きます。

Google Apps Script LINE Notify

 

毎朝自動的にLINE通知されるようにトリガーを設定する

無事スクリプトの初回承認が完了し、手動実行することができました。

ただし毎朝[実行]をクリックして手動実行するのは面倒なので、毎日決まった時間帯に自動でLINE通知されるように設定します。

この設定のことを「トリガーを設定する」と言います。

 

トリガーの設定画面を開く

Google Apps Script Trigger Settings

スクリプトエディタの画面左側の[時計]アイコンをクリックしてください。

するとトリガーの設定画面が開きます。

 

[トリガーを追加]をクリックする

Google Apps Script Trigger Settings

画面右下の[トリガーを追加]をクリックしてください。

するとポップアップが表示されます。

 

トリガーを設定する

Google Apps Script Trigger Settings

上記画像のように各項目の選択肢を選択してください。

  1. 実行する関数を選択: checkOutAndIn
  2. 実行するデプロイを選択: Head
  3. イベントのソースを選択: 時間主導型
  4. 時間ベースのトリガーのタイプを選択: 日付ベースのタイマー
  5. 時刻を選択: 自動LINE通知したい時間帯
  6. [保存]をクリック

(5)は画像内では「午前8時~9時」としていますが、自由に時間帯を選択ください。

ここで選択した時間帯に毎日自動LINE通知されます。

 

トリガーを設定完了!

Google Apps Script Trigger Settings

トリガーを保存すると、このように画面上に設定済みのトリガーが表示されます。

これで毎日決まった時間帯に[checkOut]が自動実行され、自動LINE通知されるようになりました。

 

GASのポイントを解説

「使えたら問題がない」という方は読み飛ばして頂いて問題ありません。

今回紹介したスクリプトをカスタマイズして独自の通知を作成したい方向けに、Google Apps Scriptのポイントを解説します。

 

Beds24からCSVを取得し、二次元配列に変換する

//CSVをBeds24からダウンロード
let csv = UrlFetchApp.fetch(url,options);
  
//ダウンロードしたCSVを二次元配列に変換する
let csvContents = Utilities.parseCsv(csv);

UrlFetchApp.fetch(url,options)を使いBeds24からCSVを取得し、変数csvに格納します。

そして変数csvに対してUtilities.parseCsvを使用することで、取得したCSVを二次元配列に変換し変数csvContentsに格納します。

変数csvContentsの中には様々な予約情報が二次元配列形式で格納されています。

どのような情報が格納されているか確認したい場合、Logger.log(csvContents)を記述すれば確認できます。

 

取り出したい情報のインデックスを調べる

let property = csvContents[0].indexOf("Property");
let room = csvContents[0].indexOf("Room");
let roomId = csvContents[0].indexOf("Roomid");
let unit = csvContents[0].indexOf("Unit");
let unitId = csvContents[0].indexOf("Unitid");
let bookingId = csvContents[0].indexOf("Ref") //Beds24の予約番号
let status = csvContents[0].indexOf("Status");
let checkIn = csvContents[0].indexOf("FirstNight"); //チェックイン日
let checkOut = csvContents[0].indexOf("CheckOut"); //チェックアウト日
let firstName = csvContents[0].indexOf("First Name"); //名
let lastName = csvContents[0].indexOf("Name"); //姓
let balance = csvContents[0].indexOf("Balance"); //バランス (残高)
let originalReferer = csvContents[0].indexOf("Original Referer"); //予約元
let adultNum = csvContents[0].indexOf("Adult");
let childNum = csvContents[0].indexOf("Child"); 
let arrivalTime = csvContents[0].indexOf("Arrival"); 
let notes = csvContents[0].indexOf("Notes"); 

csvContents[0]の要素はProperty、Room、Roomid、Unit、Unitidなど数十個あります。

PropertyはcsvContents[0][0]、RoomはcsvContents[0][1]、RoomidはcsvContents[0][2]...というように指定することができます。

スプレッドシートで例えるとA列にProperty、B列にRoom、C列にRoomid....というイメージです。

Beds24 API CSV

「A列にProperty」のように何列目に何があるかは、Beds24に新しい項目が追加されれば将来変わる可能性があります。例えばSub Statusという項目は昔のBeds24ではありませんでしたが現在はH列に存在しています。

取得したい要素の列(インデックス)が将来変わっても問題なくスクリプトが動くように、indexOfメソッドを使ってcsvContents[0]から必要な要素のインデックスを動的に取得しています。

なお変数bookingIdfirstNamebalanceなどは今回のLINE通知では使用していませんが、みなさんがカスタマイズする際にわかりやすいように変数だけ作成しておきました。

 

キャンセル予約を除外する

//ステータスが"Cancelled"の予約は除外する
if(today.isSame(checkInDate, "day") && csvContents[i][status] !== "Cancelled") {
  checkInToday.push(csvContents[i]);
  checkOutAndInToday.push("" + csvContents[i][roomId] + csvContents[i][unitId]);
//ステータスが"Cancelled"の予約は除外する
if(today.isSame(checkOutDate, "day") && csvContents[i][status] !== "Cancelled") {
  checkOutToday.push(csvContents[i]);

変数csvContentsにはキャンセル予約の情報も含まれているため、ステータスが"Cancelled"になっている予約を除外しました。

 

LINE通知の文章を変数contentに格納する

//通知文章を格納するための変数を宣言する
let content;

//チェックアウトとチェックイン共にある場合
if(checkOutToday.length > 0 && checkInToday.length > 0) {
  content = "\n日付: " + todayFormatted + "\n\n" +
            "【本日チェックアウト】\n" +
            "合計: " + checkOutList.length + "件" +
            "\n\n" +
            checkOutList.join("\n") +
            "\n\n" +
            "【本日チェックイン】\n" +
            "合計: " + checkInList.length + "件" +
            "\n\n" +
            checkInList.join("\n");

//チェックアウトのみある場合
} else if(checkOutToday.length > 0) {
  content = "\n日付: " + todayFormatted + "\n\n" +
            "【本日チェックアウト】\n" +
            "合計: " + checkOutList.length + "件" +
            "\n\n" +
            checkOutList.join("\n") +
            "\n\n" +
            "【本日チェックイン】\n" +
            "合計: " + checkInList.length + "件" +
            "\n本日チェックインはありません。";

//チェックインのみある場合
} else if(checkInToday.length > 0){
  content = "\n日付: " + todayFormatted + "\n\n" +
            "【本日チェックアウト】\n" +
            "合計: " + checkOutList.length + "件" +
            "\n本日チェックアウトはありません。" +
            "\n\n" +
            "【本日チェックイン】\n" +
            "合計: " + checkInList.length + "件" +
            "\n\n" +
            checkInList.join("\n");

//チェックアウトとチェックイン共にない場合
} else {
  content = "\n日付: " + todayFormatted + "\n\n" +
            "【本日チェックアウト】\n" +
            "合計: " + checkOutList.length + "件" +
            "\n本日チェックアウトはありません。" +
            "\n\n" +
            "【本日チェックイン】\n" +
            "合計: " + checkInList.length + "件" +
            "\n本日チェックインはありません。";
}

変数contentに格納した文章が実際にLINE通知される文章です。

すこし長いですが、カスタマイズしやすいように以下4パターンで分けています。

  • チェックアウトとチェックイン共にある場合の文章
  • チェックアウトのみある場合の文章
  • チェックインのみある場合の文章
  • チェックアウトとチェックイン共にない場合の文章

通知内容をカスタマイズしたい場合、変数contentの中身を調整すればOKです。

 

「&」のエラーを回避する

//「&」が文章に含まれると「&」以降の文章が通知されないため、
//「&」を「%26」で置き換えます
//実際のライン上では「%26」は「&」と表示されます
let contentLine = content.replace(/&/g, "%26");

LINE Notifyでは文章内に「&」が含まれると、「&」以降の文章が途切れてしまい全文が通知されないことがあります。

「&」を「%26」に置き換えることでLINE通知内で「&」を表示することができるため、replaceメソッドを使って変数content内の「&」をすべて「%26」に置き換えます。

 

まとめ

今回はBeds24を使用している民泊運営者の方向けに、チェックアウトとチェックインの部屋一覧を毎朝自動でLINE通知する方法を紹介しました。

日々の業務効率化に役立てて頂けるとうれしいです。

次回記事もご期待ください!