GASからNatureRemoのAPIを叩く!
目覚ましと同時に電気をつけたい
私を朝が弱い&遮光カーテンを使っているせいで、目覚めがとても悪い。
そこで、目覚ましと同時に電気をつければ、少しは体が反応して目覚めがよくなるのではと思った。
Nature Remoとは
Nature Remoはスマートリモコンの1つで、あらゆる家電の赤外線リモコンの代わりを担ってくれる。
私はNatureRemoかなり昔から使っているが、今までは主にアレクサ経由での操作しかしていなかった。
アレクサ、電気つけてー
アレクサ、テレビつけてー
Nature Remoの自動化
Nature Remoアプリ自体にスケジュール機能のようなものは、IFTTTを使用すれば簡単なスケジューリングならすることが可能である。
ただし、IFTTTの「Date&Time」は決まった時間に実行することができない。
私は規則正しい生活をしていないので、毎日決まった時間に電気をつけられても困る。
寝る前に「明日は○時に起きる」と決めたいのだ。
そこで、NatureRemoにAPIがあるので、これをいい感じに叩けば、目覚ましと同時に電気をつけることができるのではないか!?
GASから Nature RemoのAPIを叩いてみる
前置きが長くなったが、ようやく本題に入る。
今回も素人コードなので、ご容赦ください。もっとこうした方がいいよ等あればアドバイスください。
Signal IDの一覧取得
https://api.nature.global/1/appliancesへgetすると、現在自分が登録しているアクションに対応するSignal IDの一覧が取得できる。
[ { "id":"08ba7104-0486-4008-9a9a-0d02a0547d66", "device":{ "name":"Remo1go","id":"ccc22cfc-fd1e-4acf-bb89-94279778345a", "created_at":"2018-07-25T16:12:58Z", "updated_at":"2020-06-09T11:16:26Z", "mac_address":"dc:4f:22:3e:da:10", "serial_number":"2W8602345", "firmware_version":"Remo-mini/1.0.87-g8b06f0e", "temperature_offset":0, "humidity_offset":0 }, "model":null, "type":"IR", "nickname":"電気", "image":"ico_light", "settings":null, "aircon":null, "signals":[ { "id":"6a8ea820-fc34-461c-a27e-0c7989584595", "name":"点灯","image":"ico_on" }, { "id":"1608f92c-2fcc-4e0d-a295-17f6083555c0", "name":"消灯", "image":"ico_off" }, { "id":"e99653cb-1e35-46ab-a6f0-cfde33b92e5a", "name":"常夜灯", "image":"ico_record" } ] } ]
取得した結果の必要そうな情報はスプレッドシートへ書き込んでおく。
アクセストークンとスプレッドシートのIDはスクリプトのプロパティで設定している。
// 最新のsignalidを取得 function getSignalIdList() { var token ="Bearer " + PropertiesService.getScriptProperties().getProperty('REMO_ACCESS_TOKEN'); var sheetid = PropertiesService.getScriptProperties().getProperty('SHEET_ID_SIGNALS'); // 機器情報を取得 var options = { 'method': 'get', 'headers': { 'Authorization': token } }; var response = UrlFetchApp.fetch('https://api.nature.global/1/appliances', options); Logger.log(response) r = JSON.parse(response.getContentText()) //Logger.log(r) // 書込み用スプレッドシートを開いて、全クリア var spreadsheet = SpreadsheetApp.openById(sheetid); var sheet = spreadsheet.getActiveSheet(); sheet.clear() var today = new Date(); sheet.getRange(1, 1).setValue("更新日時"); sheet.getRange(1, 2).setValue(today); // 取得したデータを解析、スプレッドシートに書き込み var rownum = 2 for (let i=0; i<r.length; i++) { var name = r[i]["nickname"] if (r[i]["signals"].length !== 0) { for (let j=0; j<r[i]["signals"].length; j++) { var signal_name = r[i]["signals"][j]["name"] var signal_id = r[i]["signals"][j]["id"] sheet.getRange(rownum, 1).setValue(name); sheet.getRange(rownum, 2).setValue(signal_name); sheet.getRange(rownum, 3).setValue(signal_id); Logger.log(name+signal_name+signal_id) rownum = rownum + 1 } } } }
実行するとスプレッドシートはこんな感じ。
照明を操作してみる
signal_idを引数と渡して、そのsignal_idをRemoへ送る関数を作っておく。
// Remoにsignalidを送信 function doRemo(signal_id) { var token ="Bearer " + PropertiesService.getScriptProperties().getProperty('REMO_ACCESS_TOKEN'); var options = { 'method': 'post', 'headers': { 'Authorization': token } }; var url = "https://api.nature.global/1/signals/" + signal_id + "/send" var response = UrlFetchApp.fetch(url, options); return response; }
照明を消すsignal_idを送ってみる。
function test() { var signal_id = "1608f92c-2fcc-4e0d-a295-17f6083555c0" result = doRemo(signal_id)
おー、消えた!簡単だ。
GASのトリガーを使って、指定の時間に実行
あとはトリガーセットするだけ!と思ったが、GASのトリガーはいろいろとクセがあったので、その話は次回。
素人がherokuを使用するまで
pythonを定期実行したくて、調べるとherokuというのが出てくる。
素人にはハードル高かったので、忘備録。
gitをインストール
herokuを使うにはgitをインストールする必要がありそう。
gitの使い方は以下を参考にさせていただきました。
非常にわかりやすかった。
ただ、configを使ったssh認証はうまくいかなかったので断念。
今日からはじめるGitHub 〜 初心者がGitをインストールして、プルリクできるようになるまでを解説 - エンジニアHub|若手Webエンジニアのキャリアを考える!
herokuを登録・CLIインストール
herokuを使うには、アカウント登録とherokuをコマンドで実行するためのCLIのインストールが必要。
このあたりは参考記事がたくさんあるので、よきものを参考。
使用するpythonファイル等を準備
今回は以下のフォルダを準備
test_heroku ┣ slack_notice.py ┣ requirements.txt ┗ runtime.txt
requirements.txt には必要なパッケージを書く。
ローカルと同じ環境で動かすのであれば、pip freeze > requirements.txt で出力すればOK。
runtime.txt には実行するpythonのバージョンを書く。
今回はpython-3.7.3を使用。使用できるバージョンは公式を参照。
#runtime.txt python-3.7.3
herokuへアップロード
準備ができたら、herokuにアップロードしていく。
$ heroku login $ heroku create
heroku login を実行すると、ブラウザに移行してログインする
heroku createでherokuが自動でつけた名前のアプリがheroku上に作成されるのでブラウザで確認する
上記の場合は3つのアプリが作成されている。
作成されたアプリを選択、「deploy」を見るとアップロードする手順が書いてある。
基本的には手順に従えはOK。
所々、上記のサイトのgit手順に従う。
$ cd test_heroku $ git init $ heroku git:remote -a xxxxxxxxxxx $ git add . $ git commit -a $ git push heroku master
git push heroku master を実行すると、パッケージのインストールなどが開始される
c:\Users\xxxx\git\test_github>git push heroku master Enumerating objects: 12, done. Counting objects: 100% (12/12), done. Delta compression using up to 8 threads Compressing objects: 100% (9/9), done. Writing objects: 100% (12/12), 3.23 KiB | 300.00 KiB/s, done. Total 12 (delta 1), reused 3 (delta 0) remote: Compressing source files... done. remote: Building source: remote: remote: -----> Python app detected remote: -----> Installing python-3.7.3 remote: -----> Installing pip remote: -----> Installing SQLite3 remote: -----> Installing requirements with pip remote: Collecting altgraph==0.16.1 (from -r /tmp/build_87a75a556e8eb4dd0ab66965df5e0b56/requirements.txt (line 1)) remote: Downloading https://files.pythonhosted.org/packages/0a/cc/646187eac4b797069e2e6b736f14cdef85dbe405c9bfc7803ef36e4f62ef/altgraph-0.16.1-py2.py3-none-any.whl ・ ・ ・ remote: Verifying deploy... done. To https://git.heroku.com/xxxxxxxxxxx * [new branch] master -> master
これでherokuへのアップロードが完了。
pythonを実行
あとは、下記コマンドでpythonが実行できる
$ heroku run slack_notice.py
ちなみに、slack_notice.pyは中身は下記である。
import requests import json slack_post_url = "slackアクセストークン" name = "python_taro" text = "testyade" requests.post( slack_post_url, data=json.dumps( {"text": text, "username": name, "icon_emoji": ":python:"}))
見事にslackに通知が来た。
slackにはpythonの絵文字を登録している。
あとは定期実行すればよいが、定期実行にはアドオンの使用、アドオンの使用にはクレジットカードの登録が必要なので、とりあえず、今回はここまで。
Gmailの添付ファイルをDropboxに保存する
前回、Gmailの添付ファイルをgoogle driveに保存したが、今回はなぜかDropboxに保存する。
やろうとする人がいないと思うので、GASからDropbox APIを操作する記事が見当たらなかったので苦労した。
Dropbox APIのアプリを作成する
Dropboxに外部からアクセスするには、 Dropbox APIのアプリを作成する必要がある。
Dropbox APIのアプリを作成するには、Dropbox developersに登録する必要があるがそれは割愛
- https://www.dropbox.com/developersへアクセスして、「app console」をクリック
- アカウントを持っている場合はログイン
- My appsが表示されるので、「Create app」をクリック
- 今回は「Dropbox API」「App folder」を選択(既存フォルダにアクセスした場合は「Full Dropbox」で作成する必要あり)名前をつけて「Create app」をクリック
- Generated access tokenの「Generate」をクリック
これでとりあえず、外部からアクセス可能
コマンドを確認する
https://dropbox.github.io/dropbox-api-v2-explorer/でAPIの動きやコマンドを確認することができる
リストを取得する
まずは試しに、dropboxないのリストを取得してみる
- 左のリストから「list_folder」を探してクリック
- 先ほど取得したAccess Tokenを「Access Token」に貼り付けて、「Submit Call」をクリック。ResponseがJSON形式で帰ってくる。list_folderと言いつつ、ファイルも返ってくる。
- フォルダを指定した場合は「path」に「/testfoler」と指定する
- サブフォルダまで探す場合は「list_folder/continue」を使うみたいだが、今回はスルー
- 「Show Code」をクリックすると、サンプルコマンドが表示される。
HTTP POSTする際のheaderやparameterの書き方の参考にするため、よく見ておく
ファイルをアップロードする
- 次は左のリストから「upload」を探してクリック
- 今度は先ほどと同じAccess Tokenがすでに入っているので、Access Tokenの入力はなし
- 「File to upload」でアップしたファイルを選択。「path」に「/up_testfile.txt」と入力(アップロードは必ずファイル名を入力する必要がある。めんどくさい)
- 「mode」は「overwrite」を選択。(未選択で同名のファイルがある場合はエラーになる。「add」は別名保存、「update」は既存ファイルをリネームっぽい)
- 「Submit Call」をクリックすると、ResponseがJSON形式で帰ってくる。
- こちらも「Show Code」して、中身をよく見る。ふむふむ
list_folderとはいくつか違うので注意が必要。最初気付かず、しばらくはまった。。
GASからHTTPリクエストを送る
動きがなんとなく確認できたら、GASからリクエストを送っていく。GASでHTTPリクエストする際はUrlFetchApp.fetchを使用。
リストを取得する
function get_dbxList(Appkey,path) { /* dropboxのフォルダ・ファイルリストを取得する rootを指定する場合はpathに””を指定 */ return UrlFetchApp.fetch( "https://api.dropboxapi.com/2/files/list_folder", { "method" : "post", "headers" : { "Authorization" :"Bearer " + Appkey, "Content-Type" : "application/json" }, 'payload' : JSON.stringify({ "path" : path }), "muteHttpExceptions" : false } ); }
ファイルをアップロードする
function upload_dbx(Appkey,src,dest) { /* dropboxにファイルをアップロードする rootにアップする場合もファイル名を必ず指定する必要あり */ return UrlFetchApp.fetch( "https://content.dropboxapi.com/2/files/upload", { "method" : "post", "headers" : { "Authorization" :"Bearer " + Appkey, "Content-Type" : "application/octet-stream", "Dropbox-API-Arg" : "{\"path\":\"" + dest + "\",\"mode\":{\".tag\":\"overwrite\"}}" }, // Convert the JavaScript object to a JSON string. 'payload' : src , "muteHttpExceptions" : false } ); }
Gmailの添付ファイルをDropboxに保存する
ここまでくれば、前回の記事と組み合わせるだけ。
dropboxは保存するファイル名を必ず指定する必要があるので
取得した添付ファイルのファイル名を取得して、アップロードする際に使う。
function Attachment_to_dbx() { Appkey = ”dropboxのアクセストークン” //昨日の日付計算(検索条件用) var now = new Date(); var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); yesterday = Utilities.formatDate(yesterday, "JST", "yyyy/MM/dd"); //検索条件設定(Gmail で使用できる検索演算子 参照) var condition; condition = " is:unread" //未読 condition += " has:attachment"; //添付あり condition += " after:" + yesterday; //今日(昨日より後) // condition += " subject:添付やで"; //件名 // condition += " from:hogehoge@じーめーる" //条件でメール検索 var search_mail = GmailApp.search(condition); //検索したメールをスレッドで取得(二次配列) var messeges = GmailApp.getMessagesForThreads(search_mail); //スレッド数ループ for(var i = 0; i < messeges.length; i++) { //スレッドの中身数ループ for(var j = 0; j < messeges[i].length; j++) { //添付ファイル取得(一次配列) var attach = messeges[i][j].getAttachments(); //添付ファイル数ループ for(var k = 0; k < attach.length; k++){ //添付ファイルのファイル名取得 fileName = attach[k].getName() Logger.log(fileName) //添付ファイル保存 src = attach[k] dest = "/" + fileName upload_dbx(Appkey, src, dest) } } //スレッドが終わったら、既読に messeges[i][0].markRead(); } }
あとは、前回同様、定期実行設定すれば自動保存されていく。
以上、なぜかGmailの添付ファイルがgoogledriveではなくDropboxに保存する方法。
Gmailの添付ファイルをgoogle driveに保存する
IFTTTのGmail連携が終了してしまった。
Gmailの添付ファイルを自動でgoogle driveに保存するプログラムをGASで作成して
定期実行すれば代わりになりそう。
GASは参考になるものがたくさんあるので、割と簡単。エラー処理はなし。
以下の点を工夫した。
- 本日分の未読メールだけ検索。
- 処理が終わったら既読にすることで、次の実行時は対象外。
検索条件を指定する
検索条件は後から変えやすいように記述。検索条件設定の先頭にスペースつけるの注意。
条件の書き方はGmail で使用できる検索演算子 を参照)
//昨日の日付計算 var now = new Date(); var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); yesterday = Utilities.formatDate(yesterday, "JST", "yyyy/MM/dd"); var condition; condition = " is:unread" //未読 condition += " has:attachment"; //添付あり condition += " after:" + yesterday; //今日(昨日より後) // condition += " subject:添付やで"; //件名 // condition += " from:hogehoge@めーる" //差出人
メールを検索する
上記の条件でメールを検索する。
GmailApp.getMessagesForThreads()は二次配列で返ってくるので注意。
1つのスレッドが返信を含めた複数のメールの一次配列となる。
var search_mail = GmailApp.search(condition); var messeges = GmailApp.getMessagesForThreads(search_mail); //これが二次配列
ループ処理しつつ、添付ファイルを保存する
今回はgoogle driveに保存するので、保存するフォルダのIDを調べる。
フォルダIDはフォルダURLの~folders/以降となる。
処理が終わったスレッドは既読にする。
var hozon_folder = DriveApp.getFolderById('フォルダID'); //スレッド数ループ for(var i = 0; i < messeges.length; i++) { for(var j = 0; j < messeges[i].length; j++) { var attach = messeges[i][j].getAttachments(); for(var k = 0; k < attach.length; k++){ hozon_folder.createFile(attach[k]); } } messeges[i][0].markRead(); }
定期実行
これを、定期実行すれば自動保存されていく。
GASの定期実行はプロジェクトのトリガーを設定するだけ。
5分に1回なのか、1時間に1回なのかはお好みで。
無料ユーザーは定期実行の制限に注意。(これくらいは大丈夫)
まとめ
function Attachment_to_GDrive() { /* 条件に合うgmailの添付ファイルをgoogledriveに保存する 検索条件は先頭にスペースつけるの注意 */ //昨日の日付計算(検索条件用) var now = new Date(); var yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); yesterday = Utilities.formatDate(yesterday, "JST", "yyyy/MM/dd"); //保存先フォルダ設定(フォルダURLの~folders/以降) var hozon_folder = DriveApp.getFolderById('google drive フォルダID'); //検索条件設定 (Gmail で使用できる検索演算子 参照) var condition; condition = " is:unread" //未読 condition += " has:attachment"; //添付あり condition += " after:" + yesterday; //今日(昨日より後) // condition += " subject:添付やで"; //件名 // condition += " from:hogehoge@じーめーる" //条件でメール検索 var search_mail = GmailApp.search(condition); //検索したメールをスレッドで取得(二次配列) var messeges = GmailApp.getMessagesForThreads(search_mail); //スレッド数ループ for(var i = 0; i < messeges.length; i++) { //スレッドの中身数ループ for(var j = 0; j < messeges[i].length; j++) { //添付ファイル取得(一次配列) var attach = messeges[i][j].getAttachments(); //添付ファイル数ループ for(var k = 0; k < attach.length; k++){ //添付ファイル保存 hozon_folder.createFile(attach[k]); } } //スレッドが終わったら、既読に messeges[i][0].markRead(); } }
今回は保存用に転送したメールの添付ファイルを保存する想定なので未読メールだけを処理。
既読のメールも処理する際は、定期実行の前回実行時間以降を処理(5分に1回なら、5分前以降)するなどの工夫が必要。
ただし、GASの定期実行は負荷分散を考慮して正確ではないので、タイミングによっては逃したり、重複保存されるかも。