4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Google Apps ScriptAdvent Calendar 2016

Day 12

QiitaAdventCalendarのRSSを集めてSlackに投稿する

Last updated at Posted at 2016-12-11

この投稿は 「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投稿されるメッセージはこんな感じにしました。

slackimage3.GIF

コードはこうなりました。

 .js
// -------------------------------------------------------------
// 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を使っているので、
機会があればどこかで紹介したいと思います。

最後までご覧になっていただき、ありがとうございました。

4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?