TINAMIのRSSリーダーを作ってみた

### PivixからTINAMIへ

![35029629_m.jpg](https://qiita-image-store.s3.amazonaws.com/0/9578/5633ac1b-cf1c-e714-6c46-4d83a22d8989.jpeg)

[絵師 カスミ様リンク](http://www.pixiv.net/member_illust.php?mode=medium&illust_id=35029629)

[マナりつリーダー](https://github.com/shigemk2/PixivManaRitsu)

以前PixivのRSSリーダーを作りました。
このままiOSアプリに申請しようかと思ったのですが、
PixivのAPIは非公開で、「非公開APIを使ったアプリはリジェクトされる」という
情報をキャッチしましたので、APIが公開されているTINAMIで同じものを作ろうと
思いました。

それで、作りました。

[TINAMIリーダー](https://github.com/shigemk2/TinamiReader)

### 下準備

TINAMIのAPIを使うためには申請する必要です。

[APIキー申請フォーム(要会員登録)](http://www.tinami.com/api/key/form)

### 実装にあたり直面した問題点
#### XML

ただ、Pixivのソレと比べて問題なのは、APIを叩いて出力されるのが
XMLだということです。JSONでもRSSでもなく。

JSONやRSSならBubbleWrapで簡単にパースできるのですが、
XMLを直にパースしようとしてもなかなかうまくいかないので、発想を転換してXMLを
JSONに変換することにしました。

で、参考にしたのがこちらの記事。

http://d.hatena.ne.jp/naoya/20120831/1346409758

サーバーを立ち上げて、XMLをJSONに変換しております。

今回はnode.jsのフレームワークexpressを利用しました。
なぜexpressを利用したかというと、検索結果を利用したいので、
引数を自由に使えるようにするためにはやっぱりフレームワークが必要かなって。

そんなこんなでnode.jsで実装したスクリプトでサーバーを起動して

http://localhost:3000/server?p1=マナりつ

にアクセスすると、そのURLがエンドポイントになるって寸法です。

```js:routes/server.js
exports.server = function(req, res){
    var parser = require('xml2json');
    var request = require('request');

    var param = req.query.p1;
    var xml = 'http://api.tinami.com/content/search?api_key=TINAMIのAPI&text=' + param;
    var json = '';

    request(xml, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            var options = {
                object: false,
                reversible: false,
                coerce: true,
                sanitize: true,
                trim: true,
                arrayNotation: false
            };
            json = parser.toJson(body, options);
            console.log(json);
            res.render('server',
                       {
                           msg: json
                       }
                      );
        }
    });
};
```

https://github.com/shigemk2/ExpressJSON

[XMLをJSONに変換する](http://shigemk2.hatenablog.com/entry/2013/12/04/XML%E3%82%92JSON%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%97%E3%80%81%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%A7%E8%A1%A8%E7%A4%BA%E3%81%95%E3%81%99)

#### GitHubとAPIキー

また、基本的に僕はGitHubにソースコードを上げたいので
APIキーが見えるところにあるのも地味に問題でした。

それも一応は解決しました。

[APIキーなどを設定ファイルにぶっこむ](http://shigemk2.hatenablog.com/entry/2013/12/08/API%E3%82%AD%E3%83%BC%E3%81%AA%E3%82%93%E3%81%8B%E3%82%92%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AB%E3%81%B6%E3%81%A3%E3%81%93%E3%82%80)

```rb:Rakefile
Motion::Project::App.setup do |app|
  # Use `rake config' to see complete project settings.
  app.name = 'TinamiReader'
  require 'yaml'
  conf_file = './config.yml'
  if File.exists?(conf_file)
    config = YAML::load_file(conf_file)
    app.testflight.sdk        = 'vendor/TestFlightSDK'
    app.testflight.api_token  = config['testflight']['api_token']
    app.testflight.team_token = config['testflight']['team_token']

    app.info_plist['TINAMI_API'] = config['tinami']['api']

    env = ENV['ENV'] || 'development'
    app.provisioning_profile = config[env]['provisioning']
  end
end
```

```yaml:config.yaml
testflight:
  api_token: API TOKEN
  team_token: TEAM TOKEN

development:
  provisioning: '/path/to/file'

tinami:
  api: TINAMI API
```

コードでAPIキーを参照したいときは、このように使いました。

```rb:web_view_controller.rb
      api_key = NSBundle.mainBundle.objectForInfoDictionaryKey('TINAMI_API')
      url = "http://api.tinami.com/image?api_key=#{api_key}&cont_id=#{self.item['id']}&no=1"
```

### アプリコード

それで本丸です。
get_itemsのところがキモ。
さっきのURLから得られたJSONをBW::JSONでパースして…
って感じで、欲しい情報を格納していっています。

```rb:tinami_view_controller.rb
# -*- coding: utf-8 -*-

class TinamiViewController < UITableViewController
  def viewDidLoad
    super

    @feed = nil
    @items = []
    self.view.backgroundColor = UIColor.whiteColor

    @refreshControl = UIRefreshControl.alloc.init
    # 更新アクションを設定
    @refreshControl.addTarget(self,
                             action:"onRefresh",
                             forControlEvents:UIControlEventValueChanged)
    self.refreshControl = @refreshControl

    @searchBar = UISearchBar.alloc.initWithFrame(CGRectMake(0, 0, 0, 0))
    @searchBar.delegate = self
    @searchBar.showsCancelButton = true
    @searchBar.sizeToFit
    self.view.dataSource = view.delegate = self
    self.navigationItem.titleView = @searchBar
    # @searchBar.text = 'マナりつ'
    @searchBar.text = ''

    self.getItems(@feed, @searchBar)
    self.buildRefreshBtn
    searchBarCancelButtonClicked(@searchBar)
  end

  def getItems(feed, searchBar)
    query = searchBar.text.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
    url = "http://localhost:3000/server?p1=#{query}"
    @items.clear

    BW::HTTP.get(url) do |response|
      if response.ok?
        @feed = BW::JSON.parse(response.body.to_str)
        for row in @feed['rsp']['contents']['content']
          if row.nil?
            break
          end
          @items << row
        end
        view.reloadData
      else
        App.alert(response.error_message)
      end
    end

    return @items
  end

  def tableView(tableView, numberOfRowsInSection:section)
    if @items.nil?
      return 0
    else
      # @items.size
      15
    end
  end

  def tableView(tableView, heightForRowAtIndexPath:indexPath)
    40
  end

  def tableView(tableView, cellForRowAtIndexPath:indexPath)
    cell = tableView.dequeueReusableCellWithIdentifier('cell') || UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:'cell')
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator

    if @items == []
      return cell
    end

    # title
    cell.textLabel.frame = CGRectMake(200, 200, 20, 30)
    cell.textLabel.text = @items[indexPath.row]['title']
    cell.textLabel.font = UIFont.boldSystemFontOfSize(14)
    cell.textLabel.textAlignment = UITextAlignmentRight

    # thumbnail
    image_path = @items[indexPath.row]['thumbnails']['thumbnail_150x150']['url']
    image_src = NSData.dataWithContentsOfURL(NSURL.URLWithString(image_path))
    image = UIImage.imageWithData(image_src)

    image_view = UIImageView.alloc.initWithImage(image)
    image_view.frame = CGRectMake(5, 5, 30, 30)
    cell.addSubview(image_view)
    return cell
  end

  def tableView(tableView, didSelectRowAtIndexPath:indexPath)
    WebViewController.new.tap do |c|
      c.item = @items[indexPath.row]
      self.navigationController.pushViewController(c, animated:true)
    end
  end

  # 更新ボタンを生成
  def buildRefreshBtn
    btn = UIBarButtonItem.alloc.initWithBarButtonSystemItem(UIBarButtonSystemItemRefresh,
                                                            target:self,
                                                            action:"eventRefreshBtn:")
    btn.tintColor = UIColor.redColor
    self.setToolbarItems(arrayWithObjects:"btn", animated:true)
    self.navigationItem.leftBarButtonItem = btn
  end

  # 処理中のイベント
  def eventActivityIndicator
    self.getItems(@feed, @searchBar)

    # 処理中を、更新ボタンに切り替える
    self.buildRefreshBtn
  end

  # 更新ボタンのイベント
  def eventRefreshBtn(sender)
    # 更新ボタンを、処理中に切り替える
    self.buildActivityIndicator
  end

  # 処理中を生成
  def buildActivityIndicator
    activityIndicator = UIActivityIndicatorView.alloc.initWithFrame(CGRectMake(0, 0, 30, 20))
    activityIndicator.startAnimating

    btn = UIBarButtonItem.alloc.initWithCustomView(activityIndicator)
    self.setToolbarItems(arrayWithObjects:"btn", animated:true)
    self.navigationItem.leftBarButtonItem = btn
    self.performSelector("eventActivityIndicator", withObject:nil, afterDelay:0.1)
  end

  def searchBarSearchButtonClicked(searchBar)
    @searchBar.resignFirstResponder
    self.getItems(@feed, @searchBar)
  end

  def searchBarCancelButtonClicked(searchBar)
    searchBar.resignFirstResponder
  end

  def onRefresh
    # 更新開始
    self.refreshControl.beginRefreshing

    view.reloadData
    @searchBar.resignFirstResponder

    self.getItems(@feed, @searchBar)

    # 更新終了
    self.refreshControl.endRefreshing
  end
end
```

### 結論

そんなたいしたことはやっていませんが、一応の流れだけ。

1.  TINAMIのAPIを叩いて出力されるXMLをJSONにパースする
2.  得られたJSONをBubbleWrapを利用してRubyMotionで使えるようなデータに落とし込む

です。

んーこの程度ではまだまだ届かぬ!!的な。