ニフクラ mobile backend(mBaaS)お役立ちブログ

スマホアプリ開発にニフクラ mobile backend(mBaaS)。アプリ開発に役立つ情報をおとどけ!

RSSフィードをmBaaSに登録するデモスクリプトを作ってみました

アプリにコンテンツを配信したり、メディア系アプリを作る際にはブログとアプリを連携させたいと考えるのが一般的です。主なやり方として、アプリから直接RSSフィードを読み込んで表示する方法があります。

これの欠点としては、アプリ側でXMLを解釈しなければいけなかったり、最新の何件かのみを配信するものなので過去記事を参照する際にはアプリ内部にデータを蓄積しなければなりません。検索などの実装も独自になるでしょう。

そこで使ってみたいのがmBaaSを経由してアプリへ配信する方法です。この場合、アプリ側ではSDKを使って簡単にデータを取得できたり、絞り込みや検索を行うこともできます。

今回はJavaScript SDKを使って任意のフィードをmBaaSに登録するスクリプトを作ってみました。すべての場合に対応できる訳ではありませんが、スクリプトを参考にカスタマイズしてみてください。

全体の処理

まず全体の処理について紹介します。

var request = require('request');
var argv = require('argv');
var url = argv.run().targets[0];

// 全体の処理
request(url, function (error, response, body) {
  if (!error && response.statusCode == 200) {
    var json = xml2json(body);
    
    // フィードクラスを探します
    // なければ作成します
    find_or_create_feed(json)
      .then(function(feed) {
        // 処理がうまくいったら
        // Feedオブジェクトがくるので、記事の登録をします
        items_register(feed, json.items)
          .then(function() {
            console.log("Registered.")
          })
      })
  }
});
  1. コマンドの引数で指定されたURLをrequestを使って取得します。
  2. 取得できたらxml2jsonという関数(後述)で解析し、JSON化します。
  3. JSONが取得できたら、mBaaSのFeedクラスを探します。もしなければ作成します。
  4. Feedクラスを取得したら、記事を登録します


フィードをJSONに変換する

フィードのXMLコンテンツを取得したら、xml2jsonでJSONに変換します。基本的にはxml2jsonというライブラリを使います。

ただしフィードのフォーマット(RSS 1.0/2.0、Atomなど)が異なりますので、それを合わせるための処理が追加されています。現在はAtomフィードのみに対応しています。

function xml2json(body) {
   json = JSON.parse(require('xml2json').toJson(body));
   if (json.feed && json.feed.entry) {
     // Atom feedの場合はここです
     // メイン情報の設定
     var obj = {
       title: json.feed.title,
       subtitle: json.feed.subtitle,
       url: json.feed.link.href,
       items: []
     };
     
     // 記事情報の設定
     for (var i in json.feed.entry) {
       item = json.feed.entry[i];
       new_item = {
         id: sha256(item.id), created: item.published,
         updated: item.updated, url: item.link.href,
         title: item.title, content: item.content['$t'],
         author: item.author.name, category: []
       };
       
       // カテゴリが単数の場合と複数とで処理分け
       if (item.category.term) {
         new_item.category.push(item.category.term);
       }else{
         for (var j in item.category) {
           var c = item.category[j];
           new_item.category.push(c.term || c);
         }
       }
       
       // 返却するデータに追加
       obj.items.push(new_item);
     }
     return obj;
   }
}


mBaaSのFeedクラスを探す/もしなければ作成

次にフィードのURLを検索して、なければmBaaSに新しいFeedクラスを作る処理になります。非同期処理になりますので、Promiseを使います。

そして、Feedクラスを検索して、データがなければ新しいFeedクラスを作成した上でresolveを実行します。途中でエラーがあればrejectを実行します。

function find_or_create_feed(json) {
  // 非同期処理なのでPromiseで囲みます
  var p = new Promise(function(resolve, reject) {
    // フィードをURLで検索します
    Feed.equalTo("url", json.url)
      .fetch()
      .then(function(feed) {
        :
      })
      .catch(function(err) {
        // 検索が失敗したらPromiseのエラーとして
        // 返します
        return reject(err);
      })
  });
  return p;
}

Feedクラスが存在しない場合は空のオブジェクト(Object.keys(feed).length === 0)が返ってきますので、それを判定に利用します。何らかのエラーが起きた場合はPromiseのrejectを実行します。

// データがある場合はFeedオブジェクトを返します
if (Object.keys(feed).length > 0) {
  return resolve(feed);
}

// なければ新しく作ります
var new_feed = new Feed();
new_feed
  .set("title", json.title)
  .set("subtitle", json.subtitle)
  .set("url", json.url)
  .save()
  .then(function(ncmb_feed) {
    // 保存結果がうまくいった場合は
    // Feedオブジェクトを返します
    return resolve(ncmb_feed);
  }).catch(function(err) {
    // エラーがあった場合はPromise
    // の失敗として返します
    return reject(err);
  });


記事を登録する

Feedのクラスが作成できたら、後は記事を登録するだけです。ここでは非同期処理のループを行っているので多少複雑な処理となっています。

まず概略として次のようになります。全体をPromiseで囲みつつ、その中でもPromise処理を使って非同期処理を制御します。

function items_register(feed, items) {
  // 処理全体のPromiseです
  var p = new Promise(function(res, rej) {
    // 個別の記事を登録する処理です
    function item_register(feed, item, item_id) {
      :
    }
    // 最初の処理を実行します
    item_register(feed, items[0], 0);
  });
  return p;
}

この中の item_register では次のように処理を行います。インデックスになる item_id を更新しつつ、記事データを検索します。mBaaS上に記事データがなければ、新しく記事を作成します。

// 記事がない場合は全記事処理し終わったと見なしてPromiseのresolveを実行します
if (typeof item === 'undefined') {
  return res("");
}

// 非同期処理なのでPromiseで囲みます
return new Promise(function(resolve, reject) {
  // 記事を探します
  // フィードの中で、かつIDが一致するものを検索します
  Item.equalTo("feed", {"__type": "Pointer", "className": "Feed", "objectId": feed.objectId})
    .equalTo("id", item.id)
    .fetch()
    .then(function(ncmb_item) {
      
      // すでに登録されている場合は次の記事を処理します
      if (Object.keys(ncmb_item).length > 0) {
        return item_register(feed, items[item_id], item_id+1);
      }
      
      // データがない場合は新しい記事オブジェクトを作成します
      new_item = new Item();
      
      // フィードをポインターとして保存します
      new_item.set("feed", feed);
      
      // データを順番に登録していきます
      for (var j in item) {
        new_item.set(j, item[j]);
      }
      
      // 保存処理を実行します
      new_item.save()
        .then(function(item){
          // 保存処理がうまくいった場合は次の記事を処理する
          return item_register(feed, items[item_id], item_id+1);
        })
        .catch(function(err) {
          // エラーがあった場合は全体のPromiseにおいてエラーにします
          return rej(err);
        });
    })
    .catch(function(err) {
      // データが取れないなどのエラーがあった場合はPromise全体のエラーにします
      return rej(err);
    });
});

これでフィードデータをmBaaSに反映できます。すでにあるデータについてはスキップしますので、Cronを使って定期的に実行することでFeed/Itemクラスに最新データが反映されるようになります。


発展系としては、Atomフォーマット以外への対応であったり、更新があった場合にはPush通知を送ると言った処理が考えられます。ブログに限らずフィードは更新通知によく使われるので、それらのデータをmBaaSに登録すれば様々な利用法が考えられるでしょう。

今回のコードはmoongift/feed2ncmbにアップしてあります。これを参考にしつつ、自分たちのサービスにあった形にカスタマイズしてください。