8
8

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 5 years have passed since last update.

RubyMotionAdvent Calendar 2013

Day 20

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

Last updated at Posted at 2013-12-07

PivixからTINAMIへ

35029629_m.jpg

絵師 カスミ様リンク

マナりつリーダー

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

それで、作りました。

TINAMIリーダー

下準備

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

APIキー申請フォーム(要会員登録)

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

XML

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

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

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

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

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

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

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

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
                       }
                      );
        }
    });
};

XMLをJSONに変換する

GitHubとAPIキー

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

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

APIキーなどを設定ファイルにぶっこむ

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
config.yaml
testflight:
  api_token: API TOKEN
  team_token: TEAM TOKEN

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

tinami:
  api: TINAMI API

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

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でパースして…
って感じで、欲しい情報を格納していっています。

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で使えるようなデータに落とし込む

です。

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

8
8
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
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?