2018年1月21日日曜日

[Google Assistant, IFTTT] Google Homeに話しかけてhttp://diet.dyndns.orgの体重グラフを自動Update


Google Home/Assistantに話しかけて体重グラフを自動で更新する方法

[2018/1/24] 体重にいわゆる半角全角が混ざっている場合にうまく半角に出来ない問題があります。近日修正予定です。
[2018/1/25] 半角全角が混ざるのではなく、61.2kgといったときに "6[半角スペース]1.2kg"という文字送られてきているのが問題でした。スペースを削除するようにしました。
[2018/2/18] IFTTTの返事文言を変更しました。
[2018/2/21]体重グラフのWebページがhttps化されたため。Google App Scriptを変更しました(http→https)。
[2018/3/16] Amazon Alexa用のskillを作成し、現在Amazonの審査中です。公開でき次第こちらにも記載します。また時間がとれればGoogle Assistant用のアプリも作成できればと思います(機能は本IFTTT版とほぼ同じです)
[2018/3/23] Amazon Alexa用skillとして、「体重グラフ」を公開しました。ご要望があれば、weight-graph at sanpei.org にメールください。Google Assistant用のアクション(アプリ)も作成予定です。
[2018/3/25] Google Assistant(Google Home)用のアクションとして「体重グラフ」をGoogleに公開申請しました。公開できましたらこちらに記載します。
[2018/3/27] Google Assistant(Google Home)用のアクションとして「体重グラフ」をGoogleに公開しました。そのため以下の内容は技術的な参考として引き続き掲載します。

各スキル・アクションのコードは以下のgithubで公開中です。

Amazon Alexa用skill code

Google Assistant用Action code



以下の設定の説明します。

1) IFTTTの設定
2) Google App Scriptの説明

1) IFTTTの設定


1-1) IFTTTの設定のポイント
1-1-1) IFの"Say a phrase with a text ingredient"の利用
当初は体重は数字なので"Say a phrase with a number"を使っていましたが、Google Assistantが体重部分をいわゆる全角で認識する場合があり、そうすると数字ではなく文字列になりました。そのため、現在は"Say a phrase with a text ingredient"を利用しています。
1-1-2) Languageは日本語(Japanese)に変更しています。

2) Google App Script

2-1) Google App ScriptはIFTTTと連携するGoogle DriveのGoogle Sheetで[Tools]->[Script editor...]で開いて入力ください(本例では、"IFTTT/sanpeiweight"の"Google Assistant Commands")

2-2) Google App Scriptの操作方法はこちらのYouTubeが参考になりました。
[Resources] -> [All your trigger]か以下のボタンからTrigger追加してください。

2-3) diet.dyndns.orgのユーザー名とパスワードは、Google Sheetのシート名[Auth]のB1セルにユーザー名、B2セルにパスワードを入力してください。

2-4) 簡単な各関数の説明
関数addDate:
A) IFTTTでThen "Add row to spreadsheet"でなぜか"Add row to spreadsheet"をしても正しくCreatedAtが入らないためGAS側でA列に日付を入れています。
B) Google Assistantが時々いわゆる全角文字で体重を認識するため全角を半角に変換しています。-->[2018/1/25] 全角ではなく、数字の途中に不要なスペースが入る場合がありその削除対応をしています。
C) updateDietの呼び出し

関数updateDiet:
A) Diet.dyndns.orgと通信しながら、現在の時刻で体重を記録します。


function addDate(e) { 
  var authSheetName = "Auth";
  var activeSheet = SpreadsheetApp.getActiveSheet();
  if (activeSheet.getName() != authSheetName) {
    var lr = activeSheet.getLastRow(); 
    var weight = activeSheet.getRange(lr,2).getValue().toString();
    weight = zenToHan(weight);
    activeSheet.getRange(lr, 2).setValue(weight);
    var CurrentDateString = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "MMM d yyyy HH:mm:ss");
    activeSheet.getRange(lr, 1).setValue(CurrentDateString);
    
    updateDiet(weight, authSheetName);
  }
}

function updateDiet(weight, authSheetName) {
  var bk = SpreadsheetApp.getActiveSpreadsheet();
  var authSheet = bk.getSheetByName(authSheetName);
  var userId = authSheet.getRange(1,2).getValue().toString();
  var password = authSheet.getRange(2,2).getValue().toString();
  
  // 1. open login page
  var LOGIN_URL = "https://diet.dyndns.org/?cmd=login&user="+userId+"&password="+password
  var response = UrlFetchApp.fetch(LOGIN_URL, {'followRedirects': false, 'muteHttpExceptions': false});
  Utilities.sleep(1000);

  var headers = response.getAllHeaders();
  var cookies = [];
  if ( typeof headers['Set-Cookie'] !== 'undefined' ) {
    // Set-Cookieヘッダーが2つ以上の場合はheaders['Set-Cookie']の中身は配列
    cookies = typeof headers['Set-Cookie'] == 'string' ? [ headers['Set-Cookie'] ] : headers['Set-Cookie'];
    for (var i = 0; i < cookies.length; i++) {
      // Set-Cookieヘッダーからname=valueだけ取り出し、セミコロン以降の属性は除外する
      cookies[i] = cookies[i].split( ';' )[0];
    };
  }
  
  // 2. open redirect page
  REDIRECT_URL = "https://diet.dyndns.org/?cmd=user&";
  var options = {
    method : "POST",
    contentType: "application/x-www-form-urlencoded",
    headers: {
      Cookie: cookies.join(';')
    },
  };
  response = UrlFetchApp.fetch(REDIRECT_URL, options);
  Utilities.sleep(1000);
  
  // 3. set weight
  WRITE_URL = "https://diet.dyndns.org/";
  var date = new Date();
  var timeZone = Session.getScriptTimeZone();
  var year = Utilities.formatDate(date, timeZone, "y");
  var month = Utilities.formatDate(date, timeZone, "M");
  var day = Utilities.formatDate(date, timeZone, "d");
  var hour = Utilities.formatDate(date, timeZone, "H");
  weight = weight.toString();
  var options = {
    method : "POST",
    contentType: "application/x-www-form-urlencoded",
    headers: {
      Cookie: cookies.join(';')
    },
    payload : {
      year: year,
      month:month,
      day:day,
      hour:hour,
      weight:weight,
      comment:"",
      cmd:"user",
      mode:"input"
    }
  };
  response = UrlFetchApp.fetch(WRITE_URL, options);
}

function zenToHan(s) {
  s = s.replace(/[0-9.]/g, function(s) {
      return String.fromCharCode(s.charCodeAt(0) - 65248);
  });
  return s.replace(/ /g, "");
}


ちなみに、バックアップも兼ねて以下のようにGoogle SheetにもA列に日時、B列に体重が入ります。