男性カップルでslackハックをやってみる

▼きっかけ

一緒に住むようになって2週間、炊事に洗濯に掃除に買い物、ありとあらゆることを共有し始めた。なんとなく、カップル用のアプリは柄でもない。そんなわけで、slackを使ってみようか、と僕が提案。

▼必要なもの

・スケジュール管理
・Todoリスト
・リマインダー
・欲しいものリスト
・天気予報

特にこれといってなかったのだけど、意外とあるのはこんな感じ。あったらいいな、と思うものを実装してみることにした。

▼利用者スペック

僕(23)
工業高等専門学校の電気電子通信系出身。
GAS(Google Apps Script)の使い手へっぽこエンジニア。
彼(28)
大学の工学部機械工学科出身。
勤務先でslackを使っている。職種は非エンジニア。

お互いにslackの使い方は分かっている前提。

▼チャンネル一覧

スクリーンショット 2020-03-12 17.19.08

▼TimeTree

まずはスケジュール管理。

一緒に住み始めた当初から、使っていたTimeTree。Googleカレンダーはすぐにslackと連携が取れるのだが、如何せんシンプルすぎておしゃれじゃない。仕事の感じが出てしまってあんまり。

そんなときにTimeTreeのAPIを発見。GASと使うことにした。

参考文献

ソースコード

var calendar_name = PropertiesService.getScriptProperties().getProperty("calendar_name");
var weekday = ["日""月""火""水""木""金""土"];

// メインの処理
function notifyTodayEvents({

 var todayEvents = JSON.parse(timetreeGetUpcomingEventsByName(calendar_name)).data;
 var message = "直近の予定\n\n" + createMessage(todayEvents);
 sendMessageToSlack(message);
}

function createMessage(events{
 var message = '';
 var eventsSize = events.length;

 if (eventsSize === 0) {
   return message += "特になし"
 }

 events.forEach(function(event, index{
   var allDay = event.attributes.all_day;
   var title = event.attributes.title;
   var startAt = formatDate(new Date(event.attributes.start_at), allDay);
   message += startAt + "\n"+title;
   if (index < eventsSize - 1) message += "\n";
 });
 //Logger.log(message);
 return message;
}

function formatDate(date, allDay{
 if (allDay) {
   return Utilities.formatDate(date, 'JST''MM/dd');
 }
 else {
   return Utilities.formatDate(date, 'JST','MM/dd HH:mm');
 }
}

// TimeTree用の処理
function timetreeGetUpcomingEventsByName(name{
 var id = timetreeGetCalendarIdByName(name);
 return timetreeGetUpcomingEvents(id);
}

function timetreeGetUpcomingEvents(id{
 var getDays = 3;
 var url = 'https://timetreeapis.com/calendars/' + id + '/upcoming_events?days=' + getDays;
 var method = 'GET'; 
 var payload = '';
 return timetreeAPI(url, method, payload);
}

function timetreeGetCalendars({
 var url = 'https://timetreeapis.com/calendars';
 var method = 'GET';
 var payload = '';
 return timetreeAPI(url, method, payload);
}

function timetreeGetCalendarIdByName(name{
 var response = timetreeGetCalendars();
 var calendars = JSON.parse(response).data;
 var calendar = calendars.filter(function(data){
   return data.attributes.name.toString() === name;
 });
 return calendar[0].id;
 
}

function timetreeAPI(url, method, payload{
 var accessToken = PropertiesService.getScriptProperties().getProperty('timetree_personal_access_token');
 var headers = {
   'Authorization''Bearer '+ accessToken
 };
 var options = {
   'method': method,
   'headers': headers,
   'payload': payload
 };
 var timetreeJsonData = JSON.parse(UrlFetchApp.fetch(url, options).getContentText());
 
 return UrlFetchApp.fetch(url, options).getContentText();
}
function sendMessageToSlack(message){

// Slackへ流す
   var payload = {
     "text" : message,
     "channel" : "#calendar",
     "username" : "TimeTree",
     "icon_emoji" : "date"
   }
   var options = {
     "method" : "POST",
     "payload" : JSON.stringify(payload)
   }
   var url = "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
   var response = UrlFetchApp.fetch(url, options);
   var content = response.getContentText("UTF-8");
}

実行結果

こんな感じに表示される。本当はTimeTreeのアイコンにしたかったのだけど、ひとまずカレンダーのアイコンにしてみた。

スクリーンショット 2020-03-12 17.19.29

アイコンの一覧はこちら。

▼Trello

Todoリストはこちらにしてみた。

個人的にはTodoistを使っているのだけど、プライベートとごちゃまぜにしたくなかったのと、1回slackと共有してみたときに使い勝手が悪かったので、Trelloの導入を決めた。

実は連携自体はそんなに難しくない。

Trelloに追加したり、更新したりすると、自動的にslackに通知がいくというもの。でも欲しかったのはこれではなくて、リマインダー機能。定刻になると、Trelloに登録されているリストのものをリストアップして通知してほしかった。

参考文献

ソースコード

そんなわけで、リストやカードを取得してslackに投稿させるスクリプトを実装した。

function getBoardId({
 var trelloKey   = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//keyを入力してください
 var trelloToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//tokenを入力してください
 var userName = "XXXXXXX";//ユーザ名を入力してください
 var url = 'https://trello.com/1/members/' + userName + '/boards?key=' + trelloKey + '&token=' + trelloToken + '&fields=name';
 res = UrlFetchApp.fetch(url, {'method':'get'});
 Logger.log(res);
}

function getListId({
 var trelloKey   = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//keyを入力してください
 var trelloToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//tokenを入力してください
 var boardId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//ボードIDを入力してください

 var url = "https://trello.com/1/boards/" + boardId + "/lists?key=" + trelloKey + "&token=" + trelloToken + "&fields=name";

 res = UrlFetchApp.fetch(url, {'method':'get'});
 Logger.log(res);
}

function getCardId({
 var trelloKey   = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//keyを入力してください
 var trelloToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//tokenを入力してください
 var listId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//リストIDを入力してください
 var url = "https://trello.com/1/lists/" + listId + "/cards?key=" + trelloKey + "&token=" + trelloToken + "&fields=name";

 res = UrlFetchApp.fetch(url, {'method':'get'});
 Logger.log(res);
}

function getCardName({
 var trelloKey   = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//keyを入力してください
 var trelloToken = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//tokenを入力してください
 var listId = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";//リストIDを入力してください
 var url = "https://trello.com/1/lists/" + listId + "/cards?key=" + trelloKey + "&token=" + trelloToken + "&fields=name";

 res = UrlFetchApp.fetch(url, {'method':'get'});
 var json=JSON.parse(res.getContentText());
 var want = ("・" + json[0].name + "\n");
 for(var i=1; i< json.length; i++){
   want = want + ("・" + json[i].name + "\n");
   Logger.log(want);
 }
 // Slackへ流す
   var payload = {
     "text" : "買うものリスト\n" + want ,
     "channel" : "#want_meal",
     "username" : "reminder",
     "icon_emoji" : ":clipboard:"
   }
   var options = {
     "method" : "POST",
     "payload" : JSON.stringify(payload)
   }
   var url = "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
   var response = UrlFetchApp.fetch(url, options);
   var content = response.getContentText("UTF-8");
 
 
}

買うものリストは食料品と生活雑貨を分けたかったので、ソースコードもチャンネルも分けてみた。ソースコードはないけど、チャンネルとしては行きたい場所を挙げていくリストも作った。

実行結果

スクリーンショット 2020-03-12 17.34.46
スクリーンショット 2020-03-12 17.36.00

リマインダーは帰宅時間の前くらいに実行されるようになっている。

▼天気予報

出勤時には気になる天気予報。本来はYahoo!天気から引っ張ってくるMyThingsを使いたかったのだけど、残念ながらサービスが停止してしまった。IFTTTも使うことを考えたけど、外国を主軸にする天気予報のため、あんまり精度が高くない。ということで、気象庁からの予報を取ってくることにした。加えて、2人とも低気圧の頭痛持ちなので、気圧も分かるといいよね、と。普段からお世話になっている頭痛ーるのTwitterを引っ張ってくることにした。

参考文献

ソースコード

function getWeatherData() {

 //日付取得
 var today = new Date();
 // データ取得
 var url = "http://www.jma.go.jp/jp/yoho/319.html";
 var response = UrlFetchApp.fetch(url);
 var data = response.getContentText();

 // 大雑把に分割&配列化
 var ary = data.split("<th class=\"weather\">");
 
 //もし朝6時なら
 if(Utilities.formatDate(today, 'Asia/Tokyo', 'HH') == 6){
   // 朝6時から24時までの降水確率
   var ary2 = ary[1].split("%");
   ary2.pop();
   var ary3 = [];
   for (var i in ary2) {
     ary3.push(ary2[i].substring(ary2[i].lastIndexOf(">")+1)+"%");
   }
   var str1 = "06-12:"+ary3[1]+"\n12-18:"+ary3[2]+"\n18-24:"+ary3[3];

   // 最低最高気温
   var min = ary[1].split("min")[1].substring(ary[1].split("min")[1].indexOf(">")+1,ary[1].split("min")[1].indexOf("<"));
   // 記録がない場合は「---」に置き換える
   if (min == "</t"min = "---";
   var max = ary[1].split("max")[1].substring(ary[1].split("max")[1].indexOf(">")+1,ary[1].split("max")[1].indexOf("<"));
   if (max == "</t"max = "---";
   var str2 = "最低気温:"+min+"\n最高気温:"+max;
   
   // Slackへ流す
   var payload = {
     "text" : "今日の天気\n" + str1 + "\n" + str2,
     "channel" : "#weather",
     "username" : "weather",
     "icon_emoji" : ":sunny:"
   }
   var options = {
     "method" : "POST",
     "payload" : JSON.stringify(payload)
   }
   var url = "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
   var response = UrlFetchApp.fetch(url, options);
   var content = response.getContentText("UTF-8");
 }
 //もし夜21時なら
 else if(Utilities.formatDate(today, 'Asia/Tokyo', 'HH') == 21){
   // 朝6時から24時までの降水確率
   var ary2 = ary[2].split("%");
   ary2.pop();
   var ary3 = [];
   for (var i in ary2) {
     ary3.push(ary2[i].substring(ary2[i].lastIndexOf(">")+1)+"%");
   }
   var str1 = "06-12:"+ary3[1]+"\n12-18:"+ary3[2]+"\n18-24:"+ary3[3];

   // 最低最高気温
   var min = ary[2].split("min")[1].substring(ary[2].split("min")[1].indexOf(">")+1,ary[2].split("min")[1].indexOf("<"));
   Logger.log(ary[2].split("min")[1]);
   // 記録がない場合は「---」に置き換える
   if (min == "</t"min = "---";
   var max = ary[2].split("max")[1].substring(ary[2].split("max")[1].indexOf(">")+1,ary[2].split("max")[1].indexOf("<"));
   if (max == "</t"max = "---";
   var str2 = "最低気温:"+min+"\n最高気温:"+max;
   
   // Slackへ流す
   var payload = {
     "text" : "明日の天気\n" + str1 + "\n" + str2,
     "channel" : "#weather",
     "username" : "weather",
     "icon_emoji" : ":sunny:"
   }
   var options = {
     "method" : "POST",
     "payload" : JSON.stringify(payload)
   }
   var url = "https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
   var response = UrlFetchApp.fetch(url, options);
   var content = response.getContentText("UTF-8");
 }
 else{
 }  

}

今日の天気だけではなく、明日の天気も表示出来るように改変。ちょっとした文字の不要部分も出ていたので、カット出来るようなソースコードにした。

頭痛ーるは天気予報と同じチャンネルに公式Twitterを流す。

実行結果

スクリーンショット 2020-03-12 17.51.04
スクリーンショット 2020-03-12 17.51.18

▼家事分担

これは、単なる試しでやってみたもの。

スクリーンショット 2020-03-12 18.09.20

ソースコード

function getHouseworkData({
// スプレッドシートの読み込みに必要な変数群
var sheet = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/edit#gid=0');
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
var values = rows.getValues();

// 曜日の取得に必要な変数群
var date = new Date();
var dayOfWeek = date.getDay();
var dayOfWeekStr = ["日曜日""月曜日""火曜日""水曜日""木曜日""金曜日""土曜日"][dayOfWeek];

// Slack通知に必要な変数群
var postUrl = 'https://hooks.slack.com/services/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
var username = 'reminder';
var icon = ':house:';

 for (var i = 1; i <= numRows - 1; i++) {
   var weekdays = values[i][1];
   if (dayOfWeekStr === weekdays) {
     var message = '今日の家事は '+ values[i][2] + "です。";
     var jsonData = {
         "username": username,
         "icon_emoji": icon,
         "text" : message
     };
     var payload = JSON.stringify(jsonData);

     var options = {
         "method"'post',
         "contentType" : "application/json",
         "payload" : payload
     }

     UrlFetchApp.fetch(postUrl, options);
   }
 }
}

実行結果

こんな感じで出力される。多分、便利だと思われる。家事が増えたらスプレッドシートに追加すれば良い。

スクリーンショット 2020-03-12 18.12.57

▼おわりに

「今日何度だっけ?」と朝からslackを見てくれた彼。「良い彼氏かよ」って言いそうになった。作って良かった。
botとかには興味がないのと、出来る限りGASやアプリ連携で完結させたかったので、そこそこ上手くいっているのではないかと。これからどんな働きをしてくれるんでしょうか。

頂いたお金は、アプリ「cotonoha」の運営に使わせて頂きます。