瀏覽代碼

Add podcast tags to events emitted by RssAgent

The keys are only added when the feed is a podcast.
Akinori MUSHA 8 年之前
父節點
當前提交
3612ba7333
共有 6 個文件被更改,包括 339 次插入8 次删除
  1. 1 1
      Gemfile
  2. 11 6
      Gemfile.lock
  3. 82 1
      app/models/agents/rss_agent.rb
  4. 17 0
      lib/feedjira_extension.rb
  5. 76 0
      spec/data_fixtures/podcast.rss
  6. 152 0
      spec/models/agents/rss_agent_spec.rb

+ 1 - 1
Gemfile

@@ -92,7 +92,7 @@ gem 'devise','~> 4.2.0'
 gem 'em-http-request', '~> 1.1.2'
 gem 'faraday', '~> 0.9.0'
 gem 'faraday_middleware', github: 'lostisland/faraday_middleware', branch: 'master'  # '>= 0.10.1'
-gem 'feedjira', '~> 2.0'
+gem 'feedjira', github: 'feedjira/feedjira' # ['~> 2.0', '>= 2.0.1']
 gem 'font-awesome-sass', '~> 4.3.2'
 gem 'foreman', '~> 0.63.0'
 gem 'geokit', '~> 1.8.4'

+ 11 - 6
Gemfile.lock

@@ -37,6 +37,16 @@ GIT
       oauth2 (~> 1)
       rest-client (~> 1.8)
 
+GIT
+  remote: git://github.com/feedjira/feedjira.git
+  revision: 849b7809455c084b06f9c4c2d368a752677882b0
+  specs:
+    feedjira (2.0.0)
+      faraday (>= 0.9)
+      faraday_middleware (>= 0.9)
+      loofah (>= 2.0)
+      sax-machine (>= 1.0)
+
 GIT
   remote: git://github.com/lostisland/faraday_middleware.git
   revision: c5836ae55857272732b33eb0e0a98d60e995a376
@@ -214,11 +224,6 @@ GEM
     extlib (0.9.16)
     faraday (0.9.2)
       multipart-post (>= 1.2, < 3)
-    feedjira (2.0.0)
-      faraday (~> 0.9)
-      faraday_middleware (~> 0.9)
-      loofah (~> 2.0)
-      sax-machine (~> 1.0)
     ffi (1.9.10)
     font-awesome-sass (4.3.2.1)
       sass (~> 3.2)
@@ -619,7 +624,7 @@ DEPENDENCIES
   evernote_oauth
   faraday (~> 0.9.0)
   faraday_middleware!
-  feedjira (~> 2.0)
+  feedjira!
   ffi (>= 1.9.4)
   font-awesome-sass (~> 4.3.2)
   forecast_io (~> 2.0.0)

+ 82 - 1
app/models/agents/rss_agent.rb

@@ -66,6 +66,22 @@ module Agents
               "copyright": "...",
               "icon": "http://example.com/icon.png",
               "authors": [ "..." ],
+
+              "itunes_block": "no",
+              "itunes_categories": [
+                "Technology", "Gadgets",
+                "TV & Film",
+                "Arts", "Food"
+              ],
+              "itunes_complete": "yes",
+              "itunes_explicit": "yes",
+              "itunes_image": "http://...",
+              "itunes_new_feed_url": "http://...",
+              "itunes_owners": [ "John Doe <john.doe@example.com>" ],
+              "itunes_subtitle": "...",
+              "itunes_summary": "...",
+              "language": "en-US",
+
               "date_published": "2014-09-11T01:30:00-07:00",
               "last_updated": "2014-09-11T01:30:00-07:00"
             },
@@ -84,6 +100,16 @@ module Agents
             "enclosure": {
               "url" => "http://example.com/file.mp3", "type" => "audio/mpeg", "length" => "123456789"
             },
+
+            "itunes_block": "no",
+            "itunes_closed_captioned": "yes",
+            "itunes_duration": "04:34",
+            "itunes_explicit": "yes",
+            "itunes_image": "http://...",
+            "itunes_order": "1",
+            "itunes_subtitle": "...",
+            "itunes_summary": "...",
+
             "date_published": "2014-09-11T01:30:00-0700",
             "last_updated": "2014-09-11T01:30:00-0700"
           }
@@ -91,7 +117,8 @@ module Agents
       Some notes:
 
       - The `feed` key is present only if `include_feed_info` is set to true.
-      - Each element in `authors` is a string normalized in the format "*name* <*email*> (*url*)", where each space-separated part is optional.
+      - The keys starting with `itunes_`, and `language` are only present when the feed is a podcast.  See [Podcasts Connect Help](https://help.apple.com/itc/podcasts_connect/#/itcb54353390) for details.
+      - Each element in `authors` and `itunes_owners` is a string normalized in the format "*name* <*email*> (*url*)", where each space-separated part is optional.
       - Timestamps are converted to the ISO 8601 format.
     MD
 
@@ -206,9 +233,40 @@ module Agents
         authors: feed.authors,
         date_published: feed.date_published,
         last_updated: feed.last_updated,
+        **itunes_feed_data(feed)
       }
     end
 
+    def itunes_feed_data(feed)
+      data = {}
+      case feed
+      when Feedjira::Parser::ITunesRSS
+        %i[
+          itunes_block
+          itunes_categories
+          itunes_complete
+          itunes_explicit
+          itunes_image
+          itunes_new_feed_url
+          itunes_owners
+          itunes_subtitle
+          itunes_summary
+          language
+        ].each { |attr|
+          if value = feed.try(attr).presence
+            data[attr] =
+              case attr
+              when :itunes_summary
+                clean_fragment(value)
+              else
+                value
+              end
+          end
+        }
+      end
+      data
+    end
+
     def entry_data(entry)
       {
         id: entry.id,
@@ -224,9 +282,32 @@ module Agents
         categories: Array(entry.try(:categories)),
         date_published: entry.date_published,
         last_updated: entry.last_updated,
+        **itunes_entry_data(entry)
       }
     end
 
+    def itunes_entry_data(entry)
+      data = {}
+      case entry
+      when Feedjira::Parser::ITunesRSSItem
+        %i[
+          itunes_block
+          itunes_closed_captioned
+          itunes_duration
+          itunes_explicit
+          itunes_image
+          itunes_order
+          itunes_subtitle
+          itunes_summary
+        ].each { |attr|
+          if value = entry.try(attr).presence
+            data[attr] = value
+          end
+        }
+      end
+      data
+    end
+
     def feed_to_events(feed)
       payload_base = {}
 

+ 17 - 0
lib/feedjira_extension.rb

@@ -57,6 +57,13 @@ module FeedjiraExtension
     value :content
   end
 
+  class ITunesRssOwner < Author
+    include SAXMachine
+
+    element :'itunes:name', as: :name
+    element :'itunes:email', as: :email
+  end
+
   class Enclosure
     include SAXMachine
 
@@ -290,6 +297,16 @@ module FeedjiraExtension
           def copyright
             @copyright || super
           end
+
+          if /ITunes/ === name
+            sax_config.collection_elements['itunes:owner'].clear
+            elements :"itunes:owner", as: :_itunes_owners, class: ITunesRssOwner
+            private :_itunes_owners
+
+            def itunes_owners
+              _itunes_owners.reject(&:empty?)
+            end
+          end
         end
 
         sax_config.collection_elements.each_value do |collection_elements|

+ 76 - 0
spec/data_fixtures/podcast.rss

@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<rss version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd">
+  <channel>
+    <title>All About Everything</title>
+    <link>http://www.example.com/podcasts/everything/index.html</link>
+    <language>en-us</language>
+    <copyright>&#x2117; &amp; &#xA9; 2014 John Doe &amp; Family</copyright>
+    <itunes:subtitle>A show about everything</itunes:subtitle>
+    <itunes:author>John Doe</itunes:author>
+    <itunes:summary>All About Everything is a show about everything. Each week we dive into any subject known to man and talk about it as much as we can. Look for our podcast in the Podcasts app or in the iTunes Store</itunes:summary>
+    <description>All About Everything is a show about everything. Each week we dive into any subject known to man and talk about it as much as we can. Look for our podcast in the Podcasts app or in the iTunes Store</description>
+    <itunes:owner>
+      <itunes:name>John Doe</itunes:name>
+      <itunes:email>john.doe@example.com</itunes:email>
+    </itunes:owner>
+    <itunes:complete>yes</itunes:complete>
+    <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything.jpg"/>
+    <itunes:category text="Technology">
+      <itunes:category text="Gadgets"/>
+    </itunes:category>
+    <itunes:category text="TV &amp; Film"/>
+    <itunes:category text="Arts">
+      <itunes:category text="Food"/>
+    </itunes:category>
+    <itunes:explicit>no</itunes:explicit>
+    <item>
+      <title>Shake Shake Shake Your Spices</title>
+      <itunes:author>John Doe</itunes:author>
+      <itunes:subtitle>A short primer on table spices</itunes:subtitle>
+      <itunes:summary><![CDATA[This week we talk about <a href="https://itunes/apple.com/us/book/antique-trader-salt-pepper/id429691295?mt=11">salt and pepper shakers</a>, comparing and contrasting pour rates, construction materials, and overall aesthetics. Come and join the party!]]></itunes:summary>
+      <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything/Episode1.jpg"/>
+      <enclosure length="8727310" type="audio/x-m4a" url="http://example.com/podcasts/everything/AllAboutEverythingEpisode3.m4a"/>
+      <guid>http://example.com/podcasts/archive/aae20140615.m4a</guid>
+      <pubDate>Tue, 08 Mar 2016 12:00:00 GMT</pubDate>
+      <itunes:duration>07:04</itunes:duration>
+      <itunes:explicit>no</itunes:explicit>
+    </item>
+    <item>
+      <title>Socket Wrench Shootout</title>
+      <itunes:author>Jane Doe</itunes:author>
+      <itunes:subtitle>Comparing socket wrenches is fun!</itunes:subtitle>
+      <itunes:summary>This week we talk about metric vs. Old English socket wrenches. Which one is better? Do you really need both? Get all of your answers here.</itunes:summary>
+      <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything/Episode2.jpg"/>
+      <enclosure length="5650889" type="video/mp4" url="http://example.com/podcasts/everything/AllAboutEverythingEpisode2.mp4"/>
+      <guid>http://example.com/podcasts/archive/aae20140608.mp4</guid>
+      <pubDate>Wed, 09 Mar 2016 13:00:00 EST</pubDate>
+      <itunes:duration>04:34</itunes:duration>
+      <itunes:explicit>no</itunes:explicit>
+    </item>
+    <item>
+      <title>The Best Chili</title>
+      <itunes:author>Jane Doe</itunes:author>
+      <itunes:subtitle>Jane and Eric</itunes:subtitle>
+      <itunes:summary>This week we talk about the best Chili in the world. Which chili is better?</itunes:summary>
+      <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything/Episode3.jpg"/>
+      <enclosure length="5650889" type="video/x-m4v" url="http://example.com/podcasts/everything/AllAboutEverythingEpisode2.m4v"/>
+      <guid>http://example.com/podcasts/archive/aae20140697.m4v</guid>
+      <pubDate>Thu, 10 Mar 2016 02:00:00 -0700</pubDate>
+      <itunes:duration>04:34</itunes:duration>
+      <itunes:explicit>no</itunes:explicit>
+      <itunes:isClosedCaptioned>Yes</itunes:isClosedCaptioned>
+    </item>
+    <item>
+      <title>Red,Whine, &amp; Blue</title>
+      <itunes:author>Various</itunes:author>
+      <itunes:subtitle>Red + Blue != Purple</itunes:subtitle>
+      <itunes:summary>This week we talk about surviving in a Red state if you are a Blue person. Or vice versa.</itunes:summary>
+      <itunes:image href="http://example.com/podcasts/everything/AllAboutEverything/Episode4.jpg"/>
+      <enclosure length="498537" type="audio/mpeg" url="http://example.com/podcasts/everything/AllAboutEverythingEpisode4.mp3"/>
+      <guid>http://example.com/podcasts/archive/aae20140601.mp3</guid>
+      <pubDate>Fri, 11 Mar 2016 01:15:00 +3000</pubDate>
+      <itunes:duration>03:59</itunes:duration>
+      <itunes:explicit>no</itunes:explicit>
+    </item>
+  </channel>
+</rss>

+ 152 - 0
spec/models/agents/rss_agent_spec.rb

@@ -12,7 +12,11 @@ describe Agents::RssAgent do
     stub_request(:any, /SlickdealsnetFP/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/slickdeals.atom")), :status => 200)
     stub_request(:any, /onethingwell.org/).to_return(body: File.read(Rails.root.join("spec/data_fixtures/onethingwell.rss")), status: 200)
     stub_request(:any, /bad.onethingwell.org/).to_return(body: File.read(Rails.root.join("spec/data_fixtures/onethingwell.rss")).gsub(/(?<=<link>)[^<]*/, ''), status: 200)
+<<<<<<< HEAD
     stub_request(:any, /iso-8859-1/).to_return(body: File.binread(Rails.root.join("spec/data_fixtures/iso-8859-1.rss")), headers: { 'Content-Type' => 'application/rss+xml; charset=ISO-8859-1' }, status: 200)
+=======
+    stub_request(:any, /podcast/).to_return(body: File.read(Rails.root.join("spec/data_fixtures/podcast.rss")), status: 200)
+>>>>>>> Add podcast tags to events emitted by RssAgent
   end
 
   let(:agent) do
@@ -303,6 +307,154 @@ describe Agents::RssAgent do
         expect(event.payload['title']).to eq('Mëkanïk Zaïn')
       end
     end
+
+    context 'with podcast elements' do
+      before do
+        @valid_options['url'] = 'http://example.com/podcast.rss'
+        @valid_options['include_feed_info'] = true
+      end
+
+      let :feed_info do
+        {
+          "id" => nil,
+          "type" => "rss",
+          "url" => "http://www.example.com/podcasts/everything/index.html",
+          "links" => [ { "href" => "http://www.example.com/podcasts/everything/index.html" } ],
+          "title" => "All About Everything",
+          "description" => "All About Everything is a show about everything. Each week we dive into any subject known to man and talk about it as much as we can. Look for our podcast in the Podcasts app or in the iTunes Store",
+          "copyright" => "℗ & © 2014 John Doe & Family",
+          "generator" => nil,
+          "icon" => nil,
+          "authors" => [
+            "John Doe"
+          ],
+          "date_published" => nil,
+          "last_updated" => nil,
+          "itunes_categories" => [
+            "Technology", "Gadgets",
+            "TV & Film",
+            "Arts", "Food"
+          ],
+          "itunes_complete" => "yes",
+          "itunes_explicit" => "no",
+          "itunes_image" => "http://example.com/podcasts/everything/AllAboutEverything.jpg",
+          "itunes_owners" => ["John Doe <john.doe@example.com>"],
+          "itunes_subtitle" => "A show about everything",
+          "itunes_summary" => "All About Everything is a show about everything. Each week we dive into any subject known to man and talk about it as much as we can. Look for our podcast in the Podcasts app or in the iTunes Store",
+          "language" => "en-us"
+        }
+      end
+
+      it "is parsed correctly" do
+        expect {
+          agent.check
+        }.to change { agent.events.count }.by(4)
+
+        expect(agent.events.map(&:payload)).to match([
+          {
+            "feed" => feed_info,
+            "id" => "http://example.com/podcasts/archive/aae20140601.mp3",
+            "url" => nil,
+            "urls" => [],
+            "links" => [],
+            "title" => "Red,Whine, & Blue",
+            "description" => nil,
+            "content" => nil,
+            "image" => nil,
+            "enclosure" => {
+              "url" => "http://example.com/podcasts/everything/AllAboutEverythingEpisode4.mp3",
+              "type" => "audio/mpeg",
+              "length" => "498537"
+            },
+            "authors" => ["<Various>"],
+            "categories" => [],
+            "date_published" => "2016-03-11T01:15:00+00:00",
+            "last_updated" => "2016-03-11T01:15:00+00:00",
+            "itunes_duration" => "03:59",
+            "itunes_explicit" => "no",
+            "itunes_image" => "http://example.com/podcasts/everything/AllAboutEverything/Episode4.jpg",
+            "itunes_subtitle" => "Red + Blue != Purple",
+            "itunes_summary" => "This week we talk about surviving in a Red state if you are a Blue person. Or vice versa."
+          },
+          {
+            "feed" => feed_info,
+            "id" => "http://example.com/podcasts/archive/aae20140697.m4v",
+            "url" => nil,
+            "urls" => [],
+            "links" => [],
+            "title" => "The Best Chili",
+            "description" => nil,
+            "content" => nil,
+            "image" => nil,
+            "enclosure" => {
+              "url" => "http://example.com/podcasts/everything/AllAboutEverythingEpisode2.m4v",
+              "type" => "video/x-m4v",
+              "length" => "5650889"
+            },
+            "authors" => ["Jane Doe"],
+            "categories" => [],
+            "date_published" => "2016-03-10T02:00:00-07:00",
+            "last_updated" => "2016-03-10T02:00:00-07:00",
+            "itunes_closed_captioned" => "Yes",
+            "itunes_duration" => "04:34",
+            "itunes_explicit" => "no",
+            "itunes_image" => "http://example.com/podcasts/everything/AllAboutEverything/Episode3.jpg",
+            "itunes_subtitle" => "Jane and Eric",
+            "itunes_summary" => "This week we talk about the best Chili in the world. Which chili is better?"
+          },
+          {
+            "feed" => feed_info,
+            "id" => "http://example.com/podcasts/archive/aae20140608.mp4",
+            "url" => nil,
+            "urls" => [],
+            "links" => [],
+            "title" => "Socket Wrench Shootout",
+            "description" => nil,
+            "content" => nil,
+            "image" => nil,
+            "enclosure" => {
+              "url" => "http://example.com/podcasts/everything/AllAboutEverythingEpisode2.mp4",
+              "type" => "video/mp4",
+              "length" => "5650889"
+            },
+            "authors" => ["Jane Doe"],
+            "categories" => [],
+            "date_published" => "2016-03-09T13:00:00-05:00",
+            "last_updated" => "2016-03-09T13:00:00-05:00",
+            "itunes_duration" => "04:34",
+            "itunes_explicit" => "no",
+            "itunes_image" => "http://example.com/podcasts/everything/AllAboutEverything/Episode2.jpg",
+            "itunes_subtitle" => "Comparing socket wrenches is fun!",
+            "itunes_summary" => "This week we talk about metric vs. Old English socket wrenches. Which one is better? Do you really need both? Get all of your answers here."
+          },
+          {
+            "feed" => feed_info,
+            "id" => "http://example.com/podcasts/archive/aae20140615.m4a",
+            "url" => nil,
+            "urls" => [],
+            "links" => [],
+            "title" => "Shake Shake Shake Your Spices",
+            "description" => nil,
+            "content" => nil,
+            "image" => nil,
+            "enclosure" => {
+              "url" => "http://example.com/podcasts/everything/AllAboutEverythingEpisode3.m4a",
+              "type" => "audio/x-m4a",
+              "length" => "8727310"
+            },
+            "authors" => ["John Doe"],
+            "categories" => [],
+            "date_published" => "2016-03-08T12:00:00+00:00",
+            "last_updated" => "2016-03-08T12:00:00+00:00",
+            "itunes_duration" => "07:04",
+            "itunes_explicit" => "no",
+            "itunes_image" => "http://example.com/podcasts/everything/AllAboutEverything/Episode1.jpg",
+            "itunes_subtitle" => "A short primer on table spices",
+            "itunes_summary" => "This week we talk about <a href=\"https://itunes/apple.com/us/book/antique-trader-salt-pepper/id429691295?mt=11\">salt and pepper shakers</a>, comparing and contrasting pour rates, construction materials, and overall aesthetics. Come and join the party!"
+          }
+        ])
+      end
+    end
   end
 
   describe 'logging errors with the feed url' do