podcast_index 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b65c6fe11a9ccf6a3747e6c3c5cd862686978ee63cfcf7b1fd88ad79dc46367
4
- data.tar.gz: 9695467e8f4bcfe6507ef16d8a91e6745de89fc0601d0cc63899886766b96e88
3
+ metadata.gz: be3811eb9cbde914e7e314e4d02e35a335fcb06250b8e22519ed0b59487d2e4b
4
+ data.tar.gz: 19de21d136dbc55f8cf0adcc65a710d773baceb99398ff22182d7e67b26ebc6b
5
5
  SHA512:
6
- metadata.gz: 6a24dfe1841426f4b8aede0afa6470060e6dcbcbaca97312b249d7918b0c45d19bcf26cb201ffb0f38a40285e71ecea42613b42424d58f67968bc0c91b0e9da0
7
- data.tar.gz: 4bd0f8edc4931a7b8c54a46fa56c80ae12247903ae546bde090051cd7fb61efc44da3b2884675fe211247f90f04ce2e94abdcedf610329a3d029ef32f0338227
6
+ metadata.gz: 353f4074705cd1e93675d8bb8908698a57b07f321f54510d6d8e23af483736d6fd127b94aafcc67cd3d29e84e628c505aac290823e33425e30bc7c4343044cf8
7
+ data.tar.gz: 03075bb9e16b26edaa2b4d865435b0bdf919fd896f7b19e0b5c3f7d204cb0093a2f35e02d1312b2e0248c2a3385dbb54cd240a504b92afd1bbae3bb8d8a22d40
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2023-04-24
4
+
5
+ * BREAKING: Switching to be more consistent with ActiveRecord conventions, using `find_by` and `where` methods for all models
6
+ * Add support for all "Search", "Podcasts", "Episodes", "Recent" and "Value" sections of the API. This introduces the `Soundbite` and `Value` domain models.
7
+ * Update README.md
8
+
3
9
  ## [0.1.0] - 2023-01-06
4
10
 
5
11
  - Initial release
data/Gemfile.lock CHANGED
@@ -1,25 +1,24 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- podcast_index (0.1.0)
4
+ podcast_index (0.2.0)
5
5
  activesupport (>= 6.0, < 8)
6
6
  addressable (~> 2)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- activesupport (6.1.7)
11
+ activesupport (7.0.3.1)
12
12
  concurrent-ruby (~> 1.0, >= 1.0.2)
13
13
  i18n (>= 1.6, < 2)
14
14
  minitest (>= 5.1)
15
15
  tzinfo (~> 2.0)
16
- zeitwerk (~> 2.3)
17
16
  addressable (2.8.1)
18
17
  public_suffix (>= 2.0.2, < 6.0)
19
18
  ast (2.4.2)
20
19
  codecov (0.6.0)
21
20
  simplecov (>= 0.15, < 0.22)
22
- concurrent-ruby (1.1.10)
21
+ concurrent-ruby (1.2.2)
23
22
  crack (0.4.5)
24
23
  rexml
25
24
  debug (1.7.1)
@@ -94,7 +93,6 @@ GEM
94
93
  addressable (>= 2.8.0)
95
94
  crack (>= 0.3.2)
96
95
  hashdiff (>= 0.4.0, < 2.0.0)
97
- zeitwerk (2.6.6)
98
96
 
99
97
  PLATFORMS
100
98
  x86_64-darwin-20
data/README.md CHANGED
@@ -32,6 +32,21 @@ In a Rails app, this configuration would typically be placed in an initializer f
32
32
 
33
33
  ## Usage
34
34
 
35
+ This client currently implements the following sections of the API:
36
+ * [Search](https://podcastindex-org.github.io/docs-api/#tag--Search)
37
+ * [Podcasts](https://podcastindex-org.github.io/docs-api/#tag--Podcasts)
38
+ * [Episodes](https://podcastindex-org.github.io/docs-api/#tag--Episodes)
39
+ * [Recent](https://podcastindex-org.github.io/docs-api/#tag--Recent)
40
+ * [Value](https://podcastindex-org.github.io/docs-api/#tag--Value)
41
+
42
+ These are exposed through the following domain models:
43
+ * [Episode](lib/podcast_index/episode.rb)
44
+ * [Podcast](lib/podcast_index/podcast.rb)
45
+ * [Soundbite](lib/podcast_index/soundbite.rb)
46
+ * [Value](lib/podcast_index/value.rb)
47
+
48
+ The intent is to follow ActiveRecord conventions as reasonably possible. Therefore, most of the requests are accessed through the model's `.find_by` and `.where` methods.
49
+
35
50
  ### Examples
36
51
 
37
52
  Find a podcast by podcastindex id:
@@ -44,23 +59,73 @@ podcast.title # => "Podcasting 2.0"
44
59
  Find an episode by guid:
45
60
 
46
61
  ```ruby
47
- episode = PodcastIndex::Episode.find_by_guid("PC2084", feedurl: "http://mp3s.nashownotes.com/pc20rss.xml")
62
+ episode = PodcastIndex::Episode.find_by(guid: "PC2084", feedurl: "http://mp3s.nashownotes.com/pc20rss.xml")
48
63
  episode.title # => "Episode 84: All Aboard to On-Board!"
49
64
  ```
50
65
 
66
+ Find a Value block by feed_id:
67
+
68
+ ```ruby
69
+ value = PodcastIndex::Value.find_by(feed_id: 920666)
70
+ value.model.type # => "lightning"
71
+ ```
72
+
51
73
  Methods that return multiple results are represented as an array of objects:
52
74
 
53
75
  ```ruby
54
- episodes = PodcastIndex::Episode.find_by_person("Adam Curry")
76
+ episodes = PodcastIndex::Episode.where(person: "Adam Curry")
55
77
  episodes.count # => 57
56
78
  episodes.first.title # => "Episode #2: A conversation with Adam Curry"
57
79
  ```
58
80
 
81
+ ```ruby
82
+ soundbite = PodcastIndex::Soundbite.where(recent: true)
83
+ soundbite.first.episode_id # => 15082076307
84
+ ```
85
+
86
+
87
+
59
88
  ### Supported Methods
60
89
 
61
- This client currently implements most of the API, focusing on searching for Podcasts and Episodes. For the list of supported methods, see the [Podcast](lib/podcast_index/podcast.rb) and [Episode](lib/podcast_index/episode.rb) models.
90
+ ```ruby
91
+ # Episode
92
+ Episode.find(id, fulltext: nil)
93
+ Episode.find_by(guid, feedurl: nil, feedid: nil, fulltext: nil)
94
+ Episode.where(feed_id:, since: nil, max: nil, fulltext: nil)
95
+ Episode.where(feed_url:, since: nil, max: nil, fulltext: nil)
96
+ Episode.where(podcast_guid:, since: nil, max: nil, fulltext: nil)
97
+ Episode.where(live: true, max: nil)
98
+ Episode.where(itunes_id:, since: nil, max: nil, fulltext: nil)
99
+ Episode.where(person:, fulltext: nil)
100
+ Episode.where(recent: true, max: nil, exclude_string: nil, before: nil, fulltext: nil)
101
+ Episode.sample(max: nil, lang: nil, categories: [], exclude_categories: [], fulltext: nil) # Find a random episode
102
+
103
+ # Podcast
104
+ Podcast.find(id)
105
+ Podcast.find_by(feed_url)
106
+ Podcast.find_by(guid)
107
+ Podcast.find_by(itunes_id)
108
+ Podcast.where(tag:)
109
+ Podcast.where(medium:)
110
+ # Additional parameters only for searching with "music" medium
111
+ Podcast.where(medium: "music", term:, val: nil, aponly: nil, clean: nil, fulltext: nil)
112
+ Podcast.where(term:, val: nil, aponly: nil, clean: nil, fulltext: nil)
113
+ Podcast.where(title:, val: nil, clean: nil, fulltext: nil)
114
+ Podcast.where(trending: true, max: nil, since: nil, lang: nil, categories: [], exclude_categories: [])
115
+ Podcast.where(dead: true)
116
+ Podcast.where(recent: true, max: nil, since: nil, lang: nil, categories: [], exclude_categories: [])
117
+ Podcast.where(new: true, max: nil, since: nil, feedid: nil, desc: nil)
118
+ Podcast.where(newly_found: true, max: nil, since: nil)
119
+
120
+ # Soundbite
121
+ Soundbite.where(recent: true, max: nil)
122
+
123
+ # Value
124
+ Value.find_by(feed_id)
125
+ Value.find_by(feed_url)
126
+ ```
62
127
 
63
- The attributes of the response object mirror the names in the API, but have been translated to "underscore" format more closely follow Ruby conventions. For example, the `lastUpdateTime` attribute for a `Podcast` is renamed to `last_update_time`.
128
+ The attributes of the models mirror the names in the API, but have been translated to "underscore" format to more closely follow Ruby conventions. For example, the `lastUpdateTime` attribute for a `Podcast` is exposed as `last_update_time`.
64
129
 
65
130
  ### Exception Handling
66
131
 
@@ -4,30 +4,53 @@ module PodcastIndex
4
4
  extend Request
5
5
 
6
6
  class << self
7
+ # https://podcastindex-org.github.io/docs-api/#get-/episodes/byid
7
8
  def by_id(id:, fulltext: nil)
8
9
  response = get("/episodes/byid", id: id, fulltext: fulltext)
9
10
  JSON.parse(response.body)
10
11
  end
11
12
 
13
+ # https://podcastindex-org.github.io/docs-api/#get-/episodes/byfeedid
12
14
  def by_feed_id(id:, since: nil, max: nil, fulltext: nil)
13
15
  response = get("/episodes/byfeedid", id: id, since: since, max: max, fulltext: fulltext)
14
16
  JSON.parse(response.body)
15
17
  end
16
18
 
19
+ # https://podcastindex-org.github.io/docs-api/#get-/episodes/byfeedurl
17
20
  def by_feed_url(url:, since: nil, max: nil, fulltext: nil)
18
21
  response = get("/episodes/byfeedurl", url: url, since: since, max: max, fulltext: fulltext)
19
22
  JSON.parse(response.body)
20
23
  end
21
24
 
25
+ # https://podcastindex-org.github.io/docs-api/#get-/episodes/bypodcastguid
26
+ def by_podcast_guid(podcast_guid:, since: nil, max: nil, fulltext: nil)
27
+ response = get("/episodes/bypodcastguid", guid: podcast_guid, since: since, max: max, fulltext: fulltext)
28
+ JSON.parse(response.body)
29
+ end
30
+
31
+ # https://podcastindex-org.github.io/docs-api/#get-/episodes/byguid
22
32
  def by_guid(guid:, feedurl: nil, feedid: nil, fulltext: nil)
23
33
  response = get("/episodes/byguid", guid: guid, feedurl: feedurl, feedid: feedid, fulltext: fulltext)
24
34
  JSON.parse(response.body)
25
35
  end
26
36
 
37
+ # https://podcastindex-org.github.io/docs-api/#get-/episodes/byitunesid
27
38
  def by_itunes_id(id:, since: nil, max: nil, fulltext: nil)
28
39
  response = get("/episodes/byitunesid", id: id, since: since, max: max, fulltext: fulltext)
29
40
  JSON.parse(response.body)
30
41
  end
42
+
43
+ # https://podcastindex-org.github.io/docs-api/#get-/episodes/live
44
+ def live(max: nil)
45
+ response = get("/episodes/live", max: max)
46
+ JSON.parse(response.body)
47
+ end
48
+
49
+ # https://podcastindex-org.github.io/docs-api/#get-/episodes/random
50
+ def random(max: nil, lang: nil, cat: nil, notcat: nil, fulltext: nil)
51
+ response = get("/episodes/random", max: max, lang: lang, cat: cat, notcat: notcat, fulltext: fulltext)
52
+ JSON.parse(response.body)
53
+ end
31
54
  end
32
55
  end
33
56
  end
@@ -23,6 +23,27 @@ module PodcastIndex
23
23
  response = get("/podcasts/byguid", guid: guid)
24
24
  JSON.parse(response.body)
25
25
  end
26
+
27
+ def by_tag(tag:)
28
+ params = {}.tap { |p| p[tag] = true }
29
+ response = get("/podcasts/bytag", params)
30
+ JSON.parse(response.body)
31
+ end
32
+
33
+ def by_medium(medium:)
34
+ response = get("/podcasts/bymedium", medium: medium)
35
+ JSON.parse(response.body)
36
+ end
37
+
38
+ def trending(max: nil, since: nil, lang: nil, cat: nil, notcat: nil)
39
+ response = get("/podcasts/trending", max: max, since: since, lang: lang, cat: cat, notcat: notcat)
40
+ JSON.parse(response.body)
41
+ end
42
+
43
+ def dead
44
+ response = get("/podcasts/dead", {})
45
+ JSON.parse(response.body)
46
+ end
26
47
  end
27
48
  end
28
49
  end
@@ -0,0 +1,35 @@
1
+ module PodcastIndex
2
+ module Api
3
+ class Recent
4
+ extend Request
5
+
6
+ class << self
7
+ def episodes(max: nil, exclude_string: nil, before: nil, fulltext: nil)
8
+ response = get("/recent/episodes", max: max, exclude_string: exclude_string, before: before,
9
+ fulltext: fulltext)
10
+ JSON.parse(response.body)
11
+ end
12
+
13
+ def feeds(max: nil, since: nil, lang: nil, cat: nil, notcat: nil)
14
+ response = get("/recent/feeds", max: max, since: since, lang: lang, cat: cat, notcat: notcat)
15
+ JSON.parse(response.body)
16
+ end
17
+
18
+ def new_feeds(max: nil, since: nil, feedid: nil, desc: nil)
19
+ response = get("/recent/newfeeds", max: max, since: since, feedid: feedid, desc: desc)
20
+ JSON.parse(response.body)
21
+ end
22
+
23
+ def data(max: nil, since: nil)
24
+ response = get("/recent/data", max: max, since: since)
25
+ JSON.parse(response.body)
26
+ end
27
+
28
+ def soundbites(max: nil)
29
+ response = get("/recent/soundbites", max: max)
30
+ JSON.parse(response.body)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -19,10 +19,10 @@ module PodcastIndex
19
19
  JSON.parse(response.body)
20
20
  end
21
21
 
22
- # def music_by_term(term, val: nil, aponly: nil, clean: nil, fulltext: nil)
23
- # response = get("/search/music/byterm", q: term, val:, aponly:, clean:, fulltext:)
24
- # JSON.parse(response.body)
25
- # end
22
+ def music_by_term(term:, val: nil, aponly: nil, clean: nil, fulltext: nil)
23
+ response = get("/search/music/byterm", q: term, val: val, aponly: aponly, clean: clean, fulltext: fulltext)
24
+ JSON.parse(response.body)
25
+ end
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,19 @@
1
+ module PodcastIndex
2
+ module Api
3
+ class Value
4
+ extend Request
5
+
6
+ class << self
7
+ def by_feed_id(id:)
8
+ response = get("/value/byfeedid", id: id)
9
+ JSON.parse(response.body)
10
+ end
11
+
12
+ def by_feed_url(url:)
13
+ response = get("/value/byfeedurl", url: url)
14
+ JSON.parse(response.body)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -4,18 +4,50 @@ require "delegate"
4
4
  module PodcastIndex
5
5
  class Episode < SimpleDelegator
6
6
  class << self
7
+ FIND_ONE_ATTRIBUTES = %i[guid].freeze
8
+ FIND_MANY_ATTRIBUTES = %i[feed_id feed_url podcast_guid live itunes_id person recent].freeze
9
+
7
10
  def find(id, fulltext: nil)
8
11
  response = Api::Episodes.by_id(id: id, fulltext: fulltext)
9
12
  from_response(response)
10
13
  end
11
14
 
12
- def find_by_feed_id(feed_id, since: nil, max: nil, fulltext: nil)
15
+ def find_by(attributes)
16
+ match = (attributes.keys & FIND_ONE_ATTRIBUTES)
17
+ raise ArgumentError, "Must supply one of the attributes: #{FIND_ONE_ATTRIBUTES}" unless match.present?
18
+ raise ArgumentError, "Must supply only one of the attributes: #{FIND_ONE_ATTRIBUTES}" if match.length > 1
19
+
20
+ send("find_by_#{match.first}", attributes[match.first])
21
+ end
22
+
23
+ def where(attributes)
24
+ match = (attributes.keys & FIND_MANY_ATTRIBUTES)
25
+ raise ArgumentError, "Must supply one of the attributes: #{FIND_MANY_ATTRIBUTES}" unless match.present?
26
+ raise ArgumentError, "Must supply only one of the attributes: #{FIND_MANY_ATTRIBUTES}" if match.length > 1
27
+
28
+ send("find_all_by_#{match.first}", **attributes)
29
+ end
30
+
31
+ def sample(max: nil, lang: nil, categories: [], exclude_categories: [], fulltext: nil)
32
+ response = Api::Episodes.random(max: max, lang: lang, cat: categories.join(","),
33
+ notcat: exclude_categories.join(","), fulltext: fulltext)
34
+ from_response_collection(response, "episodes")
35
+ end
36
+
37
+ private
38
+
39
+ def find_all_by_feed_id(feed_id:, since: nil, max: nil, fulltext: nil)
13
40
  response = Api::Episodes.by_feed_id(id: feed_id, since: since, max: max, fulltext: fulltext)
14
41
  from_response_collection(response)
15
42
  end
16
43
 
17
- def find_by_feed_url(url, since: nil, max: nil, fulltext: nil)
18
- response = Api::Episodes.by_feed_url(url: url, since: since, max: max, fulltext: fulltext)
44
+ def find_all_by_feed_url(feed_url:, since: nil, max: nil, fulltext: nil)
45
+ response = Api::Episodes.by_feed_url(url: feed_url, since: since, max: max, fulltext: fulltext)
46
+ from_response_collection(response)
47
+ end
48
+
49
+ def find_all_by_podcast_guid(podcast_guid:, since: nil, max: nil, fulltext: nil)
50
+ response = Api::Episodes.by_podcast_guid(podcast_guid: podcast_guid, since: since, max: max, fulltext: fulltext)
19
51
  from_response_collection(response)
20
52
  end
21
53
 
@@ -24,25 +56,33 @@ module PodcastIndex
24
56
  from_response(response)
25
57
  end
26
58
 
27
- def find_by_itunes_id(itunes_id, since: nil, max: nil, fulltext: nil)
59
+ def find_all_by_live(live:, max: nil) # rubocop:disable Lint/UnusedMethodArgument
60
+ response = Api::Episodes.live(max: max)
61
+ from_response_collection(response)
62
+ end
63
+
64
+ def find_all_by_itunes_id(itunes_id:, since: nil, max: nil, fulltext: nil)
28
65
  response = Api::Episodes.by_itunes_id(id: itunes_id, since: since, max: max, fulltext: fulltext)
29
66
  from_response_collection(response)
30
67
  end
31
68
 
32
- def find_by_person(person, fulltext: nil)
69
+ def find_all_by_person(person:, fulltext: nil)
33
70
  response = Api::Search.by_person(person: person, fulltext: fulltext)
34
71
  from_response_collection(response)
35
72
  end
36
73
 
37
- private
74
+ def find_all_by_recent(recent:, max: nil, exclude_string: nil, before: nil, fulltext: nil) # rubocop:disable Lint/UnusedMethodArgument
75
+ response = Api::Recent.episodes(max: max, exclude_string: exclude_string, before: before, fulltext: fulltext)
76
+ from_response_collection(response)
77
+ end
38
78
 
39
79
  def from_response(response)
40
80
  feed = response["episode"].transform_keys(&:underscore)
41
81
  new(JSON.parse(feed.to_json, object_class: OpenStruct)) # rubocop:disable Style/OpenStructUse
42
82
  end
43
83
 
44
- def from_response_collection(response)
45
- response["items"].map do |item|
84
+ def from_response_collection(response, collection_key = "items")
85
+ response[collection_key].map do |item|
46
86
  episode = item.transform_keys(&:underscore)
47
87
  new(JSON.parse(episode.to_json, object_class: OpenStruct)) # rubocop:disable Style/OpenStructUse
48
88
  end
@@ -1,16 +1,40 @@
1
1
  require "ostruct"
2
2
  require "delegate"
3
3
 
4
+ # rubocop:disable Metrics/ParameterLists, Lint/UnusedMethodArgument
4
5
  module PodcastIndex
5
6
  class Podcast < SimpleDelegator
6
7
  class << self
8
+ FIND_ONE_ATTRIBUTES = %i[feed_url guid itunes_id].freeze
9
+ FIND_MANY_ATTRIBUTES = %i[tag medium term title trending dead recent new newly_found].freeze
10
+
7
11
  def find(id)
8
12
  response = Api::Podcasts.by_feed_id(id: id)
9
13
  from_response(response)
10
14
  end
11
15
 
12
- def find_by_feed_url(url)
13
- response = Api::Podcasts.by_feed_url(url: url)
16
+ def find_by(attributes)
17
+ match = (attributes.keys & FIND_ONE_ATTRIBUTES)
18
+ raise ArgumentError, "Must supply one of the attributes: #{FIND_ONE_ATTRIBUTES}" unless match.present?
19
+ raise ArgumentError, "Must supply only one of the attributes: #{FIND_ONE_ATTRIBUTES}" if match.length > 1
20
+
21
+ send("find_by_#{match.first}", attributes[match.first])
22
+ end
23
+
24
+ def where(attributes)
25
+ return find_all_music_by_term(**attributes) if attributes[:medium] == "music" && attributes[:term].present?
26
+
27
+ match = (attributes.keys & FIND_MANY_ATTRIBUTES)
28
+ raise ArgumentError, "Must supply one of the attributes: #{FIND_MANY_ATTRIBUTES}" unless match.present?
29
+ raise ArgumentError, "Must supply only one of the attributes: #{FIND_MANY_ATTRIBUTES}" if match.length > 1
30
+
31
+ send("find_all_by_#{match.first}", **attributes)
32
+ end
33
+
34
+ private
35
+
36
+ def find_by_feed_url(feed_url)
37
+ response = Api::Podcasts.by_feed_url(url: feed_url)
14
38
  from_response(response)
15
39
  end
16
40
 
@@ -19,22 +43,67 @@ module PodcastIndex
19
43
  from_response(response)
20
44
  end
21
45
 
22
- def find_by_itunes_id(id)
23
- response = Api::Podcasts.by_itunes_id(id: id)
46
+ def find_by_itunes_id(itunes_id)
47
+ response = Api::Podcasts.by_itunes_id(id: itunes_id)
24
48
  from_response(response)
25
49
  end
26
50
 
27
- def find_by_term(term, val: nil, aponly: nil, clean: nil, fulltext: nil)
51
+ def find_all_by_tag(tag:)
52
+ response = Api::Podcasts.by_tag(tag: tag)
53
+ from_response_collection(response)
54
+ end
55
+
56
+ def find_all_music_by_term(medium:, term:, val: nil, aponly: nil, clean: nil, fulltext: nil)
57
+ response = Api::Search.music_by_term(term: term, val: val, aponly: aponly, clean: clean, fulltext: fulltext)
58
+ from_response_collection(response)
59
+ end
60
+
61
+ def find_all_by_medium(medium:)
62
+ response = Api::Podcasts.by_medium(medium: medium)
63
+ from_response_collection(response)
64
+ end
65
+
66
+ def find_all_by_trending(trending:, max: nil, since: nil, lang: nil, categories: [], exclude_categories: [])
67
+ response = Api::Podcasts.trending(max: max, since: since, lang: lang, cat: categories.join(","),
68
+ notcat: exclude_categories.join(","))
69
+ from_response_collection(response)
70
+ end
71
+
72
+ def find_all_by_dead(*)
73
+ response = Api::Podcasts.dead
74
+ from_response_collection(response)
75
+ end
76
+
77
+ def find_all_by_term(term:, val: nil, aponly: nil, clean: nil, fulltext: nil)
28
78
  response = Api::Search.by_term(term: term, val: val, aponly: aponly, clean: clean, fulltext: fulltext)
29
79
  from_response_collection(response)
30
80
  end
31
81
 
32
- def find_by_title(title, val: nil, clean: nil, fulltext: nil)
82
+ def find_all_by_title(title:, val: nil, clean: nil, fulltext: nil)
33
83
  response = Api::Search.by_title(title: title, val: val, clean: clean, fulltext: fulltext)
34
84
  from_response_collection(response)
35
85
  end
36
86
 
37
- private
87
+ def find_all_by_recent(recent:, max: nil, since: nil, lang: nil, categories: [], exclude_categories: [])
88
+ response = Api::Recent.feeds(max: max, since: since, lang: lang, cat: categories.join(","),
89
+ notcat: exclude_categories.join(","))
90
+ from_response_collection(response)
91
+ end
92
+
93
+ def find_all_by_new(new:, max: nil, since: nil, feedid: nil, desc: nil)
94
+ response = Api::Recent.new_feeds(max: max, since: since, feedid: feedid, desc: desc)
95
+ from_response_collection(response)
96
+ end
97
+
98
+ def find_all_by_newly_found(newly_found:, max: nil, since: nil)
99
+ response = Api::Recent.data(max: max, since: since)
100
+ # This one has a bit of an oddball response. It's nested in a "data" attribute and all the keys
101
+ # for each feed are prefixed with "feed" (see example fixture)
102
+ response["data"]["feeds"].map do |item|
103
+ feed = item.transform_keys { |key| key.delete_prefix("feed").underscore }
104
+ new(JSON.parse(feed.to_json, object_class: OpenStruct)) # rubocop:disable Style/OpenStructUse
105
+ end
106
+ end
38
107
 
39
108
  def from_response(response)
40
109
  feed = response["feed"].transform_keys(&:underscore)
@@ -43,10 +112,11 @@ module PodcastIndex
43
112
 
44
113
  def from_response_collection(response)
45
114
  response["feeds"].map do |item|
46
- episode = item.transform_keys(&:underscore)
47
- new(JSON.parse(episode.to_json, object_class: OpenStruct)) # rubocop:disable Style/OpenStructUse
115
+ feed = item.transform_keys(&:underscore)
116
+ new(JSON.parse(feed.to_json, object_class: OpenStruct)) # rubocop:disable Style/OpenStructUse
48
117
  end
49
118
  end
50
119
  end
51
120
  end
52
121
  end
122
+ # rubocop:enable Metrics/ParameterLists, Lint/UnusedMethodArgument
@@ -0,0 +1,30 @@
1
+ require "ostruct"
2
+ require "delegate"
3
+
4
+ module PodcastIndex
5
+ class Soundbite < SimpleDelegator
6
+ class << self
7
+ FIND_MANY_ATTRIBUTES = %i[recent].freeze
8
+
9
+ def where(attributes)
10
+ match = (attributes.keys & FIND_MANY_ATTRIBUTES)
11
+ raise ArgumentError, "Must supply one of the attributes: #{FIND_MANY_ATTRIBUTES}" unless match.present?
12
+ raise ArgumentError, "Must supply only one of the attributes: #{FIND_MANY_ATTRIBUTES}" if match.length > 1
13
+
14
+ send("find_all_by_#{match.first}", **attributes)
15
+ end
16
+
17
+ def find_all_by_recent(recent:, max: nil) # rubocop:disable Lint/UnusedMethodArgument
18
+ response = Api::Recent.soundbites(max: max)
19
+ from_response_collection(response)
20
+ end
21
+
22
+ def from_response_collection(response, collection_key = "items")
23
+ response[collection_key].map do |item|
24
+ soundbite = item.transform_keys(&:underscore)
25
+ new(JSON.parse(soundbite.to_json, object_class: OpenStruct)) # rubocop:disable Style/OpenStructUse
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,35 @@
1
+ require "ostruct"
2
+ require "delegate"
3
+
4
+ module PodcastIndex
5
+ class Value < SimpleDelegator
6
+ class << self
7
+ FIND_ONE_ATTRIBUTES = %i[feed_id feed_url].freeze
8
+
9
+ def find_by(attributes)
10
+ match = (attributes.keys & FIND_ONE_ATTRIBUTES)
11
+ raise ArgumentError, "Must supply one of the attributes: #{FIND_ONE_ATTRIBUTES}" unless match.present?
12
+ raise ArgumentError, "Must supply only one of the attributes: #{FIND_ONE_ATTRIBUTES}" if match.length > 1
13
+
14
+ send("find_by_#{match.first}", attributes[match.first])
15
+ end
16
+
17
+ private
18
+
19
+ def find_by_feed_id(feed_id)
20
+ response = Api::Value.by_feed_id(id: feed_id)
21
+ from_response(response)
22
+ end
23
+
24
+ def find_by_feed_url(feed_url)
25
+ response = Api::Value.by_feed_url(url: feed_url)
26
+ from_response(response)
27
+ end
28
+
29
+ def from_response(response)
30
+ value = response["value"].transform_keys(&:underscore)
31
+ new(JSON.parse(value.to_json, object_class: OpenStruct)) # rubocop:disable Style/OpenStructUse
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,3 +1,3 @@
1
1
  module PodcastIndex
2
- VERSION = "0.1.0".freeze
2
+ VERSION = "0.2.0".freeze
3
3
  end
data/lib/podcast_index.rb CHANGED
@@ -7,8 +7,12 @@ require_relative "podcast_index/api/request"
7
7
  require_relative "podcast_index/api/podcasts"
8
8
  require_relative "podcast_index/api/episodes"
9
9
  require_relative "podcast_index/api/search"
10
+ require_relative "podcast_index/api/recent"
11
+ require_relative "podcast_index/api/value"
10
12
  require_relative "podcast_index/podcast"
11
13
  require_relative "podcast_index/episode"
14
+ require_relative "podcast_index/soundbite"
15
+ require_relative "podcast_index/value"
12
16
 
13
17
  module PodcastIndex
14
18
  include ActiveSupport::Configurable
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: podcast_index
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason York
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-01-07 00:00:00.000000000 Z
11
+ date: 2023-04-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -173,12 +173,15 @@ files:
173
173
  - lib/podcast_index.rb
174
174
  - lib/podcast_index/api/episodes.rb
175
175
  - lib/podcast_index/api/podcasts.rb
176
+ - lib/podcast_index/api/recent.rb
176
177
  - lib/podcast_index/api/request.rb
177
178
  - lib/podcast_index/api/search.rb
179
+ - lib/podcast_index/api/value.rb
178
180
  - lib/podcast_index/episode.rb
179
181
  - lib/podcast_index/podcast.rb
182
+ - lib/podcast_index/soundbite.rb
183
+ - lib/podcast_index/value.rb
180
184
  - lib/podcast_index/version.rb
181
- - podcast_index.gemspec
182
185
  homepage: https://github.com/jasonyork/podcast-index
183
186
  licenses:
184
187
  - MIT
@@ -1,41 +0,0 @@
1
- require_relative "lib/podcast_index/version"
2
-
3
- Gem::Specification.new do |spec|
4
- spec.name = "podcast_index"
5
- spec.version = PodcastIndex::VERSION
6
- spec.authors = ["Jason York"]
7
-
8
- spec.summary = "Ruby client for the podcastindex.org API"
9
- spec.description = "Exposes the podcastindex.org API through Ruby domain models."
10
- spec.homepage = "https://github.com/jasonyork/podcast-index"
11
- spec.license = "MIT"
12
- spec.required_ruby_version = ">= 3.0.0"
13
-
14
- spec.metadata["homepage_uri"] = spec.homepage
15
- spec.metadata["source_code_uri"] = "https://github.com/jasonyork/podcast-index"
16
- spec.metadata["changelog_uri"] = "https://github.com/jasonyork/podcast-index/blob/master/CHANGELOG.md"
17
- spec.metadata["rubygems_mfa_required"] = "true"
18
-
19
- # Specify which files should be added to the gem when it is released.
20
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(__dir__) do
22
- `git ls-files -z`.split("\x0").reject do |f|
23
- (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
24
- end
25
- end
26
- spec.bindir = "exe"
27
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
- spec.require_paths = ["lib"]
29
-
30
- spec.add_dependency "activesupport", ">= 6.0", "< 8"
31
- spec.add_dependency "addressable", "~> 2"
32
-
33
- spec.add_development_dependency "codecov", "~> 0.4"
34
- spec.add_development_dependency "debug", "~> 1"
35
- spec.add_development_dependency "rspec-core", "~> 3"
36
- spec.add_development_dependency "rspec-its", "~> 1"
37
- spec.add_development_dependency "rubocop-performance", "~> 1"
38
- spec.add_development_dependency "rubocop-rake", "~> 0.6"
39
- spec.add_development_dependency "rubocop-rspec", "~> 2"
40
- spec.add_development_dependency "webmock", "~> 3"
41
- end