複数枚写真が添付されたツイートの写真URL等を取得するTwitter gemモンキーパッチ

Twitterで写真が4枚まで添付できるようになりました、アクセスできるAPIがなかなか公開されませんでした。
そして、ようやく（日本時間の）今日から使えるようになりました。
Twitter gemは優秀なのでそのままアクセスできますが、ツイート添付写真に関する情報を取得する`Twitter::Entities#media`メソッドは、当然ながら写真1枚の情報しか取得できません。
これを拡張するモンキーパッチを作成しましたので公開します。
おそらくTwitter gemも同様の実装で対応する（追記：v5.10.0 で対応済）でしょうが、それまでご利用ください。
あと、おそらく対応してくれないであろうTwitter gem バージョン4でも動くパッチも書くつもりです。
#パッチ
とりあえずTwitter gem 5.9.0で動作確認しましたが、おそらくバージョン5全てで動作すると思います。

```ruby:extended_entities_patch.rb
module Twitter
  module Entities
    # @return [Array<Twitter::Media>]
    def media
      extended_entities(MediaFactory, :media)
    end
  
    private

    # @param klass [Class]
    # @param key [Symbol]
    def extended_entities(klass, key)
      if @attrs[:extended_entities].nil?
        []
      else
        @attrs[:extended_entities].fetch(key.to_sym, []).collect do |entity|
          klass.new(entity)
        end
      end
    end
  end
end
```

#動作確認
##Twitter gemで`attrs`を確認
このツイートをAPIで取得します。写真は3枚添付されています。
<blockquote class="twitter-tweet" lang="en"><p>タグ付けされた側は通知が来る。そしてタグ付けを含むツイートには（ツイートした垢が鍵付きじゃなければ、タグ付けに関係しない垢からも）タグ付けされた垢名を確認できる。&#10;新機能の写真タグ付けに加え、写真は1ツイート4枚まで追加可能になった。 <a href="http://t.co/Mljw3xOY3B">pic.twitter.com/Mljw3xOY3B</a></p>&mdash; riocampos（りおかんぽす） (@riocampos) <a href="https://twitter.com/riocampos/statuses/449746301857447936">March 29, 2014</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

Twitter gemでの取得結果はこうなります。

```ruby
client.status(449746301857447936).attrs
=> {:created_at=>"Sat Mar 29 03:14:08 +0000 2014",
 :id=>449746301857447936,
 :id_str=>"449746301857447936",
 :text=>
  "タグ付けされた側は通知が来る。そしてタグ付けを含むツイートには（ツイートした垢が鍵付きじゃなければ、タグ付けに関係しない垢からも）タグ付けされた垢名を確認できる。\n新機能の写真タグ付けに加え、写真は1ツイート4枚まで追加可能になった。 http://t.co/Mljw3xOY3B",
 :source=>
  "<a href=\"http://twitter.com/download/iphone\" rel=\"nofollow\">Twitter for iPhone</a>",
 :truncated=>false,
 :in_reply_to_status_id=>nil,
 :in_reply_to_status_id_str=>nil,
 :in_reply_to_user_id=>nil,
 :in_reply_to_user_id_str=>nil,
 :in_reply_to_screen_name=>nil,
 :user=>
  {:id=>56997313,
   :id_str=>"56997313",
   :name=>"riocampos（りおかんぽす）",
   :screen_name=>"riocampos",
   :location=>"Osaka, Japan",
   :description=>
    "plz call me rio☆ favorite: cycle road race & figure skate, Mac!, iPhone!, Ruby, Fine-Art, Science, Black/Green Tea. | Gender:♂ (FB: http://t.co/71QgiSwca0 )",
   :url=>"http://t.co/cRXTvrEpoN",
   :entities=>
    {:url=>
      {:urls=>
        [{:url=>"http://t.co/cRXTvrEpoN",
          :expanded_url=>"http://about.me/riocampos",
          :display_url=>"about.me/riocampos",
          :indices=>[0, 22]}]},
     :description=>
      {:urls=>
        [{:url=>"http://t.co/71QgiSwca0",
          :expanded_url=>"http://on.fb.me/MvEV7P",
          :display_url=>"on.fb.me/MvEV7P",
          :indices=>[132, 154]}]}},
   :protected=>false,
   :followers_count=>2121,
   :friends_count=>2326,
   :listed_count=>191,
   :created_at=>"Wed Jul 15 12:02:35 +0000 2009",
   :favourites_count=>2947,
   :utc_offset=>32400,
   :time_zone=>"Osaka",
   :geo_enabled=>true,
   :verified=>false,
   :statuses_count=>182220,
   :lang=>"en",
   :contributors_enabled=>false,
   :is_translator=>false,
   :is_translation_enabled=>false,
   :profile_background_color=>"48E0B7",
   :profile_background_image_url=>
    "http://pbs.twimg.com/profile_background_images/378800000158576090/WjPzbAXY.jpeg",
   :profile_background_image_url_https=>
    "https://pbs.twimg.com/profile_background_images/378800000158576090/WjPzbAXY.jpeg",
   :profile_background_tile=>true,
   :profile_image_url=>
    "http://pbs.twimg.com/profile_images/3462273402/f331afb1b57c7d39688229e86de95881_normal.jpeg",
   :profile_image_url_https=>
    "https://pbs.twimg.com/profile_images/3462273402/f331afb1b57c7d39688229e86de95881_normal.jpeg",
   :profile_banner_url=>
    "https://pbs.twimg.com/profile_banners/56997313/1365179721",
   :profile_link_color=>"009999",
   :profile_sidebar_border_color=>"FFFFFF",
   :profile_sidebar_fill_color=>"EFEFEF",
   :profile_text_color=>"333333",
   :profile_use_background_image=>true,
   :default_profile=>false,
   :default_profile_image=>false,
   :following=>false,
   :follow_request_sent=>false,
   :notifications=>false},
 :geo=>nil,
 :coordinates=>nil,
 :place=>nil,
 :contributors=>nil,
 :retweet_count=>0,
 :favorite_count=>1,
 :entities=>
  {:hashtags=>[],
   :symbols=>[],
   :urls=>[],
   :user_mentions=>[],
   :media=>
    [{:id=>449746301068926976,
      :id_str=>"449746301068926976",
      :indices=>[118, 140],
      :media_url=>"http://pbs.twimg.com/media/Bj3R4MJCYAAqnMg.jpg",
      :media_url_https=>"https://pbs.twimg.com/media/Bj3R4MJCYAAqnMg.jpg",
      :url=>"http://t.co/Mljw3xOY3B",
      :display_url=>"pic.twitter.com/Mljw3xOY3B",
      :expanded_url=>
       "http://twitter.com/riocampos/status/449746301857447936/photo/1",
      :type=>"photo",
      :sizes=>
       {:medium=>{:w=>600, :h=>537, :resize=>"fit"},
        :small=>{:w=>340, :h=>304, :resize=>"fit"},
        :thumb=>{:w=>150, :h=>150, :resize=>"crop"},
        :large=>{:w=>640, :h=>573, :resize=>"fit"}}}]},
 :extended_entities=>
  {:media=>
    [{:id=>449746301068926976,
      :id_str=>"449746301068926976",
      :indices=>[118, 140],
      :media_url=>"http://pbs.twimg.com/media/Bj3R4MJCYAAqnMg.jpg",
      :media_url_https=>"https://pbs.twimg.com/media/Bj3R4MJCYAAqnMg.jpg",
      :url=>"http://t.co/Mljw3xOY3B",
      :display_url=>"pic.twitter.com/Mljw3xOY3B",
      :expanded_url=>
       "http://twitter.com/riocampos/status/449746301857447936/photo/1",
      :type=>"photo",
      :sizes=>
       {:medium=>{:w=>600, :h=>537, :resize=>"fit"},
        :small=>{:w=>340, :h=>304, :resize=>"fit"},
        :thumb=>{:w=>150, :h=>150, :resize=>"crop"},
        :large=>{:w=>640, :h=>573, :resize=>"fit"}}},
     {:id=>449746298007076864,
      :id_str=>"449746298007076864",
      :indices=>[118, 140],
      :media_url=>"http://pbs.twimg.com/media/Bj3R4AvCQAA9-Le.jpg",
      :media_url_https=>"https://pbs.twimg.com/media/Bj3R4AvCQAA9-Le.jpg",
      :url=>"http://t.co/Mljw3xOY3B",
      :display_url=>"pic.twitter.com/Mljw3xOY3B",
      :expanded_url=>
       "http://twitter.com/riocampos/status/449746301857447936/photo/1",
      :type=>"photo",
      :sizes=>
       {:medium=>{:w=>599, :h=>1024, :resize=>"fit"},
        :large=>{:w=>599, :h=>1024, :resize=>"fit"},
        :thumb=>{:w=>150, :h=>150, :resize=>"crop"},
        :small=>{:w=>340, :h=>581, :resize=>"fit"}}},
     {:id=>449746298887868416,
      :id_str=>"449746298887868416",
      :indices=>[118, 140],
      :media_url=>"http://pbs.twimg.com/media/Bj3R4EBCEAAUIgg.jpg",
      :media_url_https=>"https://pbs.twimg.com/media/Bj3R4EBCEAAUIgg.jpg",
      :url=>"http://t.co/Mljw3xOY3B",
      :display_url=>"pic.twitter.com/Mljw3xOY3B",
      :expanded_url=>
       "http://twitter.com/riocampos/status/449746301857447936/photo/1",
      :type=>"photo",
      :sizes=>
       {:small=>{:w=>340, :h=>604, :resize=>"fit"},
        :large=>{:w=>576, :h=>1024, :resize=>"fit"},
        :thumb=>{:w=>150, :h=>150, :resize=>"crop"},
        :medium=>{:w=>576, :h=>1024, :resize=>"fit"}}}]},
 :favorited=>false,
 :retweeted=>false,
 :possibly_sensitive=>false,
 :lang=>"ja"}
```
##`media`メソッドの返値
パッチを当てる前の返値は`Twitter::Media::Photo`オブジェクト1要素の配列です。

```ruby
tw = client.status(449746301857447936)
=> #<Twitter::Tweet id=449746301857447936>
m_ary = tw.media
=> [#<Twitter::Media::Photo id=449746301068926976>]
```
パッチを当てると`Twitter::Media::Photo`オブジェクトの配列（1〜4要素）が返ります。

```ruby
load 'extended_entities_patch.rb'
=> true
tw = client.status(449746301857447936)
=> #<Twitter::Tweet id=449746301857447936>
m_ary = tw.media
=> [#<Twitter::Media::Photo id=449746301068926976>,
 #<Twitter::Media::Photo id=449746298007076864>,
 #<Twitter::Media::Photo id=449746298887868416>]
```
`Twitter::Media::Photo`のメソッドは`attrs`, `id`, `media_url`, `media_url_https`, `sizes`などがあります。
`attrs`は`Twitter::Media::Photo`の中身を見れます。ハッシュとして扱えます。
その他のメソッドはそれぞれの情報を取得します。一番使うメソッドは、画像ファイルへのURLを取得出来る`media_url`でしょう。ちなみに`media_url`の返値である`Addressable::URI`オブジェクトは`URI`オブジェクトみたいなものです。`to_s`でURL文字列になります。

```ruby
m_ary.map{ |m| m.attrs }
=> [{:id=>449746301068926976,
  :id_str=>"449746301068926976",
  :indices=>[118, 140],
  :media_url=>"http://pbs.twimg.com/media/Bj3R4MJCYAAqnMg.jpg",
  :media_url_https=>"https://pbs.twimg.com/media/Bj3R4MJCYAAqnMg.jpg",
  :url=>"http://t.co/Mljw3xOY3B",
  :display_url=>"pic.twitter.com/Mljw3xOY3B",
  :expanded_url=>
   "http://twitter.com/riocampos/status/449746301857447936/photo/1",
  :type=>"photo",
  :sizes=>
   {:medium=>{:w=>600, :h=>537, :resize=>"fit"},
    :small=>{:w=>340, :h=>304, :resize=>"fit"},
    :thumb=>{:w=>150, :h=>150, :resize=>"crop"},
    :large=>{:w=>640, :h=>573, :resize=>"fit"}}},
 {:id=>449746298007076864,
  :id_str=>"449746298007076864",
  :indices=>[118, 140],
  :media_url=>"http://pbs.twimg.com/media/Bj3R4AvCQAA9-Le.jpg",
  :media_url_https=>"https://pbs.twimg.com/media/Bj3R4AvCQAA9-Le.jpg",
  :url=>"http://t.co/Mljw3xOY3B",
  :display_url=>"pic.twitter.com/Mljw3xOY3B",
  :expanded_url=>
   "http://twitter.com/riocampos/status/449746301857447936/photo/1",
  :type=>"photo",
  :sizes=>
   {:medium=>{:w=>599, :h=>1024, :resize=>"fit"},
    :large=>{:w=>599, :h=>1024, :resize=>"fit"},
    :thumb=>{:w=>150, :h=>150, :resize=>"crop"},
    :small=>{:w=>340, :h=>581, :resize=>"fit"}}},
 {:id=>449746298887868416,
  :id_str=>"449746298887868416",
  :indices=>[118, 140],
  :media_url=>"http://pbs.twimg.com/media/Bj3R4EBCEAAUIgg.jpg",
  :media_url_https=>"https://pbs.twimg.com/media/Bj3R4EBCEAAUIgg.jpg",
  :url=>"http://t.co/Mljw3xOY3B",
  :display_url=>"pic.twitter.com/Mljw3xOY3B",
  :expanded_url=>
   "http://twitter.com/riocampos/status/449746301857447936/photo/1",
  :type=>"photo",
  :sizes=>
   {:small=>{:w=>340, :h=>604, :resize=>"fit"},
    :large=>{:w=>576, :h=>1024, :resize=>"fit"},
    :thumb=>{:w=>150, :h=>150, :resize=>"crop"},
    :medium=>{:w=>576, :h=>1024, :resize=>"fit"}}}]

m_ary.map{ |m| m.id }
=> [449746301068926976, 449746298007076864, 449746298887868416]

m_ary.map{ |m| m.media_url }
=> [#<Addressable::URI:0x3fdd9a81c9ac URI:http://pbs.twimg.com/media/Bj3R4MJCYAAqnMg.jpg>,
 #<Addressable::URI:0x3fdd9a81e9c8 URI:http://pbs.twimg.com/media/Bj3R4AvCQAA9-Le.jpg>,
 #<Addressable::URI:0x3fdd9a820ad4 URI:http://pbs.twimg.com/media/Bj3R4EBCEAAUIgg.jpg>]
 
m_ary.map{ |m| m.media_url_https }
=> [#<Addressable::URI:0x3fdd9a82e008 URI:https://pbs.twimg.com/media/Bj3R4MJCYAAqnMg.jpg>,
 #<Addressable::URI:0x3fdd9a8caf70 URI:https://pbs.twimg.com/media/Bj3R4AvCQAA9-Le.jpg>,
 #<Addressable::URI:0x3fdd9a8ccfdc URI:https://pbs.twimg.com/media/Bj3R4EBCEAAUIgg.jpg>]
 
m_ary.map{ |m| m.sizes }
=> [{:medium=>#<Twitter::Size h=537 w=600>,
  :small=>#<Twitter::Size h=304 w=340>,
  :thumb=>#<Twitter::Size h=150 w=150>,
  :large=>#<Twitter::Size h=573 w=640>},
 {:medium=>#<Twitter::Size h=1024 w=599>,
  :large=>#<Twitter::Size h=1024 w=599>,
  :thumb=>#<Twitter::Size h=150 w=150>,
  :small=>#<Twitter::Size h=581 w=340>},
 {:small=>#<Twitter::Size h=604 w=340>,
  :large=>#<Twitter::Size h=1024 w=576>,
  :thumb=>#<Twitter::Size h=150 w=150>,
  :medium=>#<Twitter::Size h=1024 w=576>}]
```
なお、`indices`, `url`(`uri`), `display_url`, `expanded_url`などのメソッドは、ツイート本文に埋め込むURLに関連しているので、複数の返値が同じ値になります。

```ruby
m_ary.map{ |m| m.indices }
=> [[118, 140], [118, 140], [118, 140]]

m_ary.map{ |m| m.url }
=> [#<Addressable::URI:0x3fdd9a8da650 URI:http://t.co/Mljw3xOY3B>,
 #<Addressable::URI:0x3fdd9a8dcedc URI:http://t.co/Mljw3xOY3B>,
 #<Addressable::URI:0x3fdd9a8df768 URI:http://t.co/Mljw3xOY3B>]
 
m_ary.map{ |m| m.display_url }
=> ["pic.twitter.com/Mljw3xOY3B",
 "pic.twitter.com/Mljw3xOY3B",
 "pic.twitter.com/Mljw3xOY3B"]
 
m_ary.map{ |m| m.expanded_url }
=> [#<Addressable::URI:0x3fdd9a8f8ba0 URI:http://twitter.com/riocampos/status/449746301857447936/photo/1>,
 #<Addressable::URI:0x3fdd9a8f9dd4 URI:http://twitter.com/riocampos/status/449746301857447936/photo/1>,
 #<Addressable::URI:0x3fdd9a8fefdc URI:http://twitter.com/riocampos/status/449746301857447936/photo/1>]
```