この投稿は 「Google Apps Script Advent Calendar 2016」12日目の投稿です。
今日は
GoogleAppsScriptでQiitaのRSSフィードを複数読み込んで、Slackに投稿する
話を書きます。
きっかけ
12月はadventカレンダーで色々なエントリを沢山読めて楽しいですよね。
でも今年は、去年に比べてカレンダーの数が多くなった気がします。Advent Calendarのトップを見ると
年度 | カレンダー数 |
---|---|
2016 | 509 |
2015 | 377 |
2014 | 214 |
2013 | 82 |
2012 | 29 |
年々、カレンダーの数が増えているんですね。
読みたいものが増えるとその分だけ、労力が必要になるので、意識の低い自分は「追いかける」のが辛くなってきました。チェックするのを忘れてたり、何が読みたかったのか自体を忘れてたり。。。
普通に考えれば、RSS購読したり何か工夫すれば良いのですが、
- 興味があるカレンダーをまとめて読めたい
- 読みたいのは昨日の投稿だけ
- まとめた結果を自分用のslackチャンネルに投稿させたい
- 新聞の朝刊のように翌朝に結果をPush通知させて通勤電車で見たい
と思い、GASで前日分の投稿をまとめるスクリプトを作りました。
コード
処理の流れは
指定カレンダーのRSSフィードを読み込み前日分の投稿だけを集める
自分用のslackチャンネルに毎朝9時に投稿する
です。
実際にslack投稿されるメッセージはこんな感じにしました。
コードはこうなりました。
// -------------------------------------------------------------
// Slackに前日分の「Qiita Advent Calendar」エントリをまとめて投稿する
// -------------------------------------------------------------
function postSlackChannel_qiitaAdventCalendar(){
// slackのINCOMINGURL
var slackIncomingUrl = "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX";
// slackのチャンネルを指定
var slackChannelId = "XXXXXXXXX";
// 対象カレンダーの指定
var calendars = new Array("vscode","csharp","communication","productmanager","hodgepodge","muscle","se","kosodate-engineer","getwild","ouch-hack","slack","docker","git","machine-learning","electron","free_zankoku");
// 対象の日付(昨日)
var now = new Date();
var targetDateD = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1).getDate();
// QiitaのRSSから対象エントリを取得
var result = getQiitaAdventCalendarEntries(targetDateD, calendars);
// エントリがあればSlackに投稿
if (result.entryCount == 0){
Logger.log("Really!?");
}else{
Logger.log("result.entryCount:" + result.entryCount);
// slack投稿用の配列に入れる
var atts = [];
for (var idx=0; idx<=result.entryData.length-1; idx++) {
var entry = result.entryData[idx];
var ptext = "";
var fback = "";
if (idx==0){
ptext = "<" + result.calendarTopUrl + "|" + result.calendarTopName + "> Day " + targetDateD;
fback = result.calendarTopName + " Day " + targetDateD;
}
atts.push({title : "<" + entry.href + "|" + entry.title + ">",
color : "#64c914",
pretext : ptext,
fallback : fback,
thumb_url : entry.authorImage,
fields : [ { title:"Calendar", value:"<" + entry.calendarUrl + "|" + entry.calendarTitle +">", short:true},
{ title:"Author", value:entry.author, short:true}]
})
}
Logger.log("atts.length:" + atts.length);
if (atts.length > 0){
// slackにメッセージを投稿する
postSlackMessages(slackIncomingUrl, slackChannelId, "AdventCalendar", "", ":page_facing_up:", "", atts);
}
}
}
// ---------------------------------------------------------------------------
// Qiita Advent Calendar2016から指定日&指定カレンダーのエントリを取得する
// ---------------------------------------------------------------------------
var getQiitaAdventCalendarEntries = function(targetDay, targetCalendars){
var result = {};
var datas = [];
var dataidx = 0;
// QiitaAdventCalendarの情報
var calendarTopUrl = "http://qiita.com/advent-calendar/2016/";
var calendarUrlBase = "http://qiita.com/advent-calendar/2016/{calandar}";
var calendarName = "Advent Calendar 2016";
result['calendarTopUrl'] = calendarTopUrl;
result['calendarTopName'] = calendarName;
// カレンダーループ
for(var i=0; i < targetCalendars.length; i++){
// リクエスト
var calendarUrl = calendarUrlBase.replace("{calandar}", targetCalendars[i]);
var xml = UrlFetchApp.fetch(calendarUrl + '/feed').getContentText();
// パース
var atom = XmlService.getNamespace('http://www.w3.org/2005/Atom');
var re = XmlService.getNamespace('http://purl.org/atompub/rank/1.0');
var doc = XmlService.parse(xml);
var root = doc.getRootElement();
var calendarTitle = root.getChild('title', atom).getText().replace(" Advent Calendarの投稿 - Qiita","");
var calendarEntries = root.getChildren('entry', atom);
// エントリループ
for(var idx=0; idx < calendarEntries.length; idx++){
// adventカレンダー日付を取得
var rank = calendarEntries[idx].getChild('rank', re).getText();;
// adventカレンダー日付が対象日ならデータを取得
if (rank == targetDay){
dataidx++;
var published = calendarEntries[idx].getChild('published', atom).getText().replace("T"," ").replace("+09:00","");
var author = calendarEntries[idx].getChild('author' , atom).getChild('name', atom).getText();
var href = calendarEntries[idx].getChild('link' , atom).getAttribute('href').getValue();
var etitle = calendarEntries[idx].getChild('title' , atom).getText();
var econtent = calendarEntries[idx].getChild('content' , atom);
// qiita以外の投稿は「content」タグの中にタイトルが入っているケースあり
if (econtent){
if (econtent.getAttribute('type').getValue() == "text"){
etitle = econtent.getText();
}
}
// プロフィール画像を取得
var authorImage = getSlackProfileImage(author);
// データを配列に退避
datas.push({author:author, authorImage:authorImage, title:etitle, href:href,
calendarDate:rank, calendarUrl:calendarUrl, calendarTitle:calendarTitle});
}
}
}
result['entryData'] = datas;
result['entryCount'] = dataidx;
return result;
}
// -------------------------------------------------------------
// Qiita画像を取得する
// -------------------------------------------------------------
var getSlackProfileImage = function(profileId){
var imageurl = "";
try {
var html = UrlFetchApp.fetch("http://qiita.com/" + profileId,{muteHttpExceptions:true}).getContentText();
var matchs = html.match(/<div class="newUserPageProfile_avatar"><img alt="" src="(.*)" \/><\/div><div class="newUserPageProfile_header">/);
if (matchs){
imageurl = matchs[1];
}
} catch (e) {
}
return imageurl;
}
// -------------------------------------------------------------
// slackにメッセージを投稿する
// -------------------------------------------------------------
var postSlackMessages = function(incurl, channel, username, text, icon_emoji, icon_url, attachments){
var payload = {
"channel" : channel,
"username" : username,
"text" : text,
"icon_emoji" : icon_emoji,
"icon_url" : icon_url,
"attachments": attachments,
"link_names" : 0};
var options = { "method":"post", "contentType":"application/json", "payload":JSON.stringify(payload) };
var result = UrlFetchApp.fetch(incurl, options);
}
コードの説明
- Slackへの投稿はIncomingWebHookを使用
- 見たいカレンダーのリストを内部で指定
- 画像が欲しかったので、RSSフィードには載っていないプロフィール画像を別途取得
- slackのattachmentsパラメータで、配列形式で投稿。
- カレンダーの指定ミスとか、想定外の事態に弱いと思います。
スケジュール登録はgoogleappsscriptメニューの「リソース-> 現在のプロジェクトのトリガ」から「日タイマー/午前8-9時」にセットしました。
終わり
GASは簡単に作れるので「情報収集ツール」として使うと便利です。
今回のスクリプト以外でも、普段の生活でGAS&SLACKを使っているので、
機会があればどこかで紹介したいと思います。
最後までご覧になっていただき、ありがとうございました。