podcast_api 1.1.5 → 3.0.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.
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+ # Generated by listennotes/devtools/api-sdks/sync.py ruby. Do not edit.
3
+
4
+ module PodcastApi
5
+ module ApiMethods
6
+ def search(**kwargs)
7
+ request_api('GET', '/search',
8
+ ['q', 'sort_by_date', 'type', 'offset', 'len_min', 'len_max', 'episode_count_min', 'episode_count_max', 'update_freq_min', 'update_freq_max', 'genre_ids', 'published_before', 'published_after', 'only_in', 'language', 'region', 'ocid', 'ncid', 'safe_mode', 'unique_podcasts', 'interviews_only', 'sponsored_only', 'page_size'], kwargs)
9
+ end
10
+
11
+ def typeahead(**kwargs)
12
+ request_api('GET', '/typeahead',
13
+ ['q', 'show_podcasts', 'show_genres', 'safe_mode'], kwargs)
14
+ end
15
+
16
+ def search_episode_titles(**kwargs)
17
+ request_api('GET', '/search_episode_titles',
18
+ ['q', 'podcast_id', 'podcast_id_type'], kwargs)
19
+ end
20
+
21
+ def spellcheck(**kwargs)
22
+ request_api('GET', '/spellcheck',
23
+ ['q'], kwargs)
24
+ end
25
+
26
+ def fetch_related_searches(**kwargs)
27
+ request_api('GET', '/related_searches',
28
+ ['q'], kwargs)
29
+ end
30
+
31
+ def fetch_trending_searches(**kwargs)
32
+ request_api('GET', '/trending_searches',
33
+ [], kwargs)
34
+ end
35
+
36
+ def fetch_best_podcasts(**kwargs)
37
+ request_api('GET', '/best_podcasts',
38
+ ['genre_id', 'page', 'region', 'publisher_region', 'language', 'sort', 'safe_mode'], kwargs)
39
+ end
40
+
41
+ def fetch_podcast_by_id(**kwargs)
42
+ request_api('GET', '/podcasts/{id}',
43
+ ['next_episode_pub_date', 'sort'], kwargs)
44
+ end
45
+
46
+ def delete_podcast(**kwargs)
47
+ request_api('DELETE', '/podcasts/{id}',
48
+ ['reason'], kwargs)
49
+ end
50
+
51
+ def fetch_episode_by_id(**kwargs)
52
+ request_api('GET', '/episodes/{id}',
53
+ ['show_transcript'], kwargs)
54
+ end
55
+
56
+ def batch_fetch_episodes(**kwargs)
57
+ request_api('POST', '/episodes',
58
+ [], kwargs)
59
+ end
60
+
61
+ def batch_fetch_podcasts(**kwargs)
62
+ request_api('POST', '/podcasts',
63
+ [], kwargs)
64
+ end
65
+
66
+ def fetch_curated_podcasts_list_by_id(**kwargs)
67
+ request_api('GET', '/curated_podcasts/{id}',
68
+ [], kwargs)
69
+ end
70
+
71
+ def fetch_podcast_genres(**kwargs)
72
+ request_api('GET', '/genres',
73
+ ['top_level_only'], kwargs)
74
+ end
75
+
76
+ def fetch_podcast_regions(**kwargs)
77
+ request_api('GET', '/regions',
78
+ [], kwargs)
79
+ end
80
+
81
+ def fetch_podcast_languages(**kwargs)
82
+ request_api('GET', '/languages',
83
+ [], kwargs)
84
+ end
85
+
86
+ def just_listen(**kwargs)
87
+ request_api('GET', '/just_listen',
88
+ [], kwargs)
89
+ end
90
+
91
+ def fetch_curated_podcasts_lists(**kwargs)
92
+ request_api('GET', '/curated_podcasts',
93
+ ['page'], kwargs)
94
+ end
95
+
96
+ def fetch_recommendations_for_podcast(**kwargs)
97
+ request_api('GET', '/podcasts/{id}/recommendations',
98
+ ['safe_mode'], kwargs)
99
+ end
100
+
101
+ def fetch_recommendations_for_episode(**kwargs)
102
+ request_api('GET', '/episodes/{id}/recommendations',
103
+ ['safe_mode'], kwargs)
104
+ end
105
+
106
+ def submit_podcast(**kwargs)
107
+ request_api('POST', '/podcasts/submit',
108
+ [], kwargs)
109
+ end
110
+
111
+ def fetch_playlist_by_id(**kwargs)
112
+ request_api('GET', '/playlists/{id}',
113
+ ['type', 'last_timestamp_ms', 'sort'], kwargs)
114
+ end
115
+
116
+ def fetch_my_playlists(**kwargs)
117
+ request_api('GET', '/playlists',
118
+ ['sort', 'page'], kwargs)
119
+ end
120
+
121
+ def fetch_audience_for_podcast(**kwargs)
122
+ request_api('GET', '/podcasts/{id}/audience',
123
+ [], kwargs)
124
+ end
125
+
126
+ def fetch_podcasts_by_domain(**kwargs)
127
+ request_api('GET', '/podcasts/domains/{domain_name}',
128
+ ['page'], kwargs)
129
+ end
130
+
131
+ def create_playlist(**kwargs)
132
+ request_api('POST', '/playlists',
133
+ [], kwargs)
134
+ end
135
+
136
+ def update_playlist(**kwargs)
137
+ request_api('PUT', '/playlists/{id}',
138
+ [], kwargs)
139
+ end
140
+
141
+ def add_playlist_item(**kwargs)
142
+ request_api('POST', '/playlists/{id}/items',
143
+ [], kwargs)
144
+ end
145
+
146
+ def delete_playlist_item(**kwargs)
147
+ request_api('DELETE', '/playlists/{id}/items/{item_id}',
148
+ [], kwargs)
149
+ end
150
+
151
+ def update_playlist_item_notes(**kwargs)
152
+ request_api('PUT', '/playlists/{id}/items/{item_id}',
153
+ [], kwargs)
154
+ end
155
+
156
+ end
157
+ end
data/lib/errors.rb CHANGED
@@ -1,23 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PodcastApi
4
- # PodcastApiError is the base error from which all other more specific PodcastApi
5
- # errors derive.
6
- class PodcastApiError < StandardError
7
- end
8
-
9
- class AuthenticationError < PodcastApiError
10
- end
11
-
12
- class APIConnectionError < PodcastApiError
13
- end
14
-
15
- class InvalidRequestError < PodcastApiError
16
- end
17
-
18
- class RateLimitError < PodcastApiError
4
+ class PodcastApiError < StandardError
5
+ attr_reader :response
6
+
7
+ def initialize(message = nil, response: nil)
8
+ @response = response
9
+ super(message)
19
10
  end
20
-
21
- class NotFoundError < PodcastApiError
22
- end
11
+ end
12
+
13
+ class AuthenticationError < PodcastApiError; end
14
+ class APIConnectionError < PodcastApiError; end
15
+ class InvalidRequestError < PodcastApiError; end
16
+ class PermissionDeniedError < PodcastApiError; end
17
+ class RateLimitError < PodcastApiError; end
18
+ class NotFoundError < PodcastApiError; end
23
19
  end
data/lib/podcast_api.rb CHANGED
@@ -1,165 +1,85 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'httparty'
4
-
5
- # Version
6
- require 'version'
7
- require 'errors'
4
+ require 'json'
5
+ require 'uri'
6
+ require_relative 'version'
7
+ require_relative 'errors'
8
+ require_relative 'api_methods'
8
9
 
9
10
  module PodcastApi
11
+ class Client
12
+ include HTTParty
13
+ include ApiMethods
14
+
15
+ BASE_URL_PROD = 'https://listen-api.listennotes.com/api/v2'
16
+ BASE_URL_TEST = 'https://listen-api-test.listennotes.com/api/v2'
17
+ attr_reader :base_url
18
+
19
+ def initialize(api_key: nil, user_agent: nil, timeout: 30)
20
+ unless (timeout.is_a?(Integer) || timeout.is_a?(Float)) && timeout.finite? && timeout.positive?
21
+ raise ArgumentError, 'timeout must be a positive, finite number of seconds'
22
+ end
23
+ @base_url = (api_key.nil? || api_key.empty?) ? BASE_URL_TEST : BASE_URL_PROD
24
+ @headers = {
25
+ 'X-ListenAPI-Key' => api_key.to_s.dup.freeze,
26
+ 'User-Agent' => (user_agent || "podcast-api-ruby #{VERSION}").dup.freeze
27
+ }.freeze
28
+ @timeout = timeout
29
+ end
10
30
 
11
- class Client
12
- include HTTParty
13
-
14
- @@BASE_URL_PROD = 'https://listen-api.listennotes.com/api/v2'
15
- @@BASE_URL_TEST = 'https://listen-api-test.listennotes.com/api/v2'
16
-
17
- attr_reader :base_url
18
-
19
- def initialize(api_key: nil, user_agent: nil)
20
- @api_key = api_key
21
- @base_url = api_key ? @@BASE_URL_PROD : @@BASE_URL_TEST
22
- @headers = {
23
- 'X-ListenAPI-Key' => @api_key ? @api_key : '',
24
- 'User-Agent' => user_agent ? user_agent : "podcasts-api-ruby #{VERSION}"
25
- }
26
- end
27
-
28
- protected
29
- def get_response(response:)
30
- if response.code == 200
31
- return response
32
- elsif response.code == 400
33
- raise InvalidRequestError.new 'something wrong on your end (client side errors), e.g., missing required parameters'
34
- elsif response.code == 401
35
- raise AuthenticationError.new 'wrong api key or your account is suspended'
36
- elsif response.code == 404
37
- raise NotFoundError.new 'endpoint not exist, or podcast / episode not exist'
38
- elsif response.code == 429
39
- raise RateLimitError.new 'for FREE plan, exceeding the quota limit; or for all plans, sending too many requests too fast and exceeding the rate limit - https://www.listennotes.com/api/faq/#faq17'
40
- else
41
- raise PodcastApiError.new 'something wrong on our end (unexpected server errors)'
42
- end
43
- end
44
-
45
- def send_http_request(http_method, *args)
46
- begin
47
- response = HTTParty.public_send(http_method, *args)
48
- rescue SocketError
49
- raise APIConnectionError.new 'Failed to connect to Listen API servers'
50
- else
51
- return get_response(response: response)
52
- end
53
- end
54
-
55
- public
56
- def search(**kwargs)
57
- return send_http_request('get', "#{@base_url}/search", {query: kwargs, headers: @headers})
58
- end
59
-
60
- def typeahead(**kwargs)
61
- return send_http_request('get', "#{@base_url}/typeahead", {query: kwargs, headers: @headers})
62
- end
63
-
64
- def spellcheck(**kwargs)
65
- return send_http_request('get', "#{@base_url}/spellcheck", {query: kwargs, headers: @headers})
66
- end
67
-
68
- def fetch_related_searches(**kwargs)
69
- return send_http_request('get', "#{@base_url}/related_searches", {query: kwargs, headers: @headers})
70
- end
71
-
72
- def fetch_trending_searches(**kwargs)
73
- return send_http_request('get', "#{@base_url}/trending_searches", {query: kwargs, headers: @headers})
74
- end
75
-
76
- def fetch_best_podcasts(**kwargs)
77
- return send_http_request('get', "#{@base_url}/best_podcasts", {query: kwargs, headers: @headers})
78
- end
79
-
80
- def fetch_podcast_by_id(**kwargs)
81
- id = kwargs.delete(:id)
82
- return send_http_request('get', "#{@base_url}/podcasts/#{id}", {query: kwargs, headers: @headers})
83
- end
84
-
85
- def fetch_episode_by_id(**kwargs)
86
- id = kwargs.delete(:id)
87
- return send_http_request('get', "#{@base_url}/episodes/#{id}", {query: kwargs, headers: @headers})
88
- end
89
-
90
- def batch_fetch_podcasts(**kwargs)
91
- @headers['Content-Type'] = 'application/x-www-form-urlencoded'
92
- return send_http_request('post', "#{@base_url}/podcasts", {body: kwargs, headers: @headers})
93
- end
94
-
95
- def batch_fetch_episodes(**kwargs)
96
- @headers['Content-Type'] = 'application/x-www-form-urlencoded'
97
- return send_http_request('post', "#{@base_url}/episodes", {body: kwargs, headers: @headers})
98
- end
99
-
100
- def fetch_curated_podcasts_list_by_id(**kwargs)
101
- id = kwargs.delete(:id)
102
- return send_http_request('get', "#{@base_url}/curated_podcasts/#{id}", {query: kwargs, headers: @headers})
103
- end
104
-
105
- def fetch_curated_podcasts_lists(**kwargs)
106
- return send_http_request('get', "#{@base_url}/curated_podcasts", {query: kwargs, headers: @headers})
107
- end
108
-
109
- def fetch_podcast_genres(**kwargs)
110
- return send_http_request('get', "#{@base_url}/genres", {query: kwargs, headers: @headers})
111
- end
112
-
113
- def fetch_podcast_regions(**kwargs)
114
- return send_http_request('get', "#{@base_url}/regions", {query: kwargs, headers: @headers})
115
- end
116
-
117
- def fetch_podcast_languages(**kwargs)
118
- return send_http_request('get', "#{@base_url}/languages", {query: kwargs, headers: @headers})
119
- end
120
-
121
- def just_listen(**kwargs)
122
- return send_http_request('get', "#{@base_url}/just_listen", {query: kwargs, headers: @headers})
123
- end
31
+ protected
124
32
 
125
- def fetch_recommendations_for_podcast(**kwargs)
126
- id = kwargs.delete(:id)
127
- return send_http_request('get', "#{@base_url}/podcasts/#{id}/recommendations", {query: kwargs, headers: @headers})
128
- end
129
-
130
- def fetch_recommendations_for_episode(**kwargs)
131
- id = kwargs.delete(:id)
132
- return send_http_request('get', "#{@base_url}/episodes/#{id}/recommendations", {query: kwargs, headers: @headers})
133
- end
134
-
135
- def fetch_playlist_by_id(**kwargs)
136
- id = kwargs.delete(:id)
137
- return send_http_request('get', "#{@base_url}/playlists/#{id}", {query: kwargs, headers: @headers})
138
- end
139
-
140
- def fetch_my_playlists(**kwargs)
141
- return send_http_request('get', "#{@base_url}/playlists", {query: kwargs, headers: @headers})
142
- end
143
-
144
- def submit_podcast(**kwargs)
145
- @headers['Content-Type'] = 'application/x-www-form-urlencoded'
146
- return send_http_request('post', "#{@base_url}/podcasts/submit", {body: kwargs, headers: @headers})
147
- end
148
-
149
- def delete_podcast(**kwargs)
150
- id = kwargs.delete(:id)
151
- return send_http_request('delete', "#{@base_url}/podcasts/#{id}", {query: kwargs, headers: @headers})
33
+ def request_api(method, path, query_names, kwargs)
34
+ params = kwargs.transform_keys(&:to_s)
35
+ path = path.gsub(/\{([^}]+)\}/) do
36
+ name = Regexp.last_match(1)
37
+ value = params.delete(name)
38
+ if value.nil? || value.to_s.empty?
39
+ raise InvalidRequestError, "Missing required path parameter: #{name}"
152
40
  end
41
+ URI.encode_www_form_component(value.to_s).gsub('+', '%20')
42
+ end
43
+ params.reject! { |_, value| value.nil? }
44
+ query, body = params.partition { |name, _| %w[GET DELETE].include?(method) || query_names.include?(name) }.map(&:to_h)
45
+ options = {
46
+ headers: @headers.dup, query: query, timeout: @timeout,
47
+ # Net::HTTP otherwise retries some write methods on broken connections.
48
+ follow_redirects: false, max_retries: 0
49
+ }
50
+ if %w[POST PUT].include?(method)
51
+ options[:headers]['Content-Type'] = 'application/x-www-form-urlencoded'
52
+ options[:body] = URI.encode_www_form(body)
53
+ end
54
+ send_http_request(method.downcase, "#{@base_url}#{path}", options)
55
+ end
153
56
 
154
- def fetch_audience_for_podcast(**kwargs)
155
- id = kwargs.delete(:id)
156
- return send_http_request('get', "#{@base_url}/podcasts/#{id}/audience", {query: kwargs, headers: @headers})
157
- end
158
-
159
- def fetch_podcasts_by_domain(**kwargs)
160
- domain_name = kwargs.delete(:domain_name)
161
- return send_http_request('get', "#{@base_url}/podcasts/domains/#{domain_name}", {query: kwargs, headers: @headers})
162
- end
57
+ def send_http_request(http_method, *args)
58
+ response = HTTParty.public_send(http_method, *args)
59
+ get_response(response: response)
60
+ rescue SocketError, SystemCallError, Timeout::Error, IOError, EOFError, OpenSSL::SSL::SSLError => error
61
+ raise APIConnectionError, "Failed to connect to Listen API servers (#{error.class})"
163
62
  end
164
63
 
64
+ def get_response(response:)
65
+ return response if (200..299).cover?(response.code)
66
+
67
+ error_class = {
68
+ 400 => InvalidRequestError,
69
+ 401 => AuthenticationError,
70
+ 403 => PermissionDeniedError,
71
+ 404 => NotFoundError,
72
+ 429 => RateLimitError
73
+ }.fetch(response.code, PodcastApiError)
74
+ message = "Listen API returned HTTP #{response.code}"
75
+ begin
76
+ payload = JSON.parse(response.body.to_s)
77
+ detail = payload['error'] || payload['message'] if payload.is_a?(Hash)
78
+ message += ": #{detail}" if detail.is_a?(String) && !detail.empty?
79
+ rescue JSON::ParserError
80
+ # Preserve a useful status message for HTML, empty, or malformed bodies.
81
+ end
82
+ raise error_class.new(message, response: response)
83
+ end
84
+ end
165
85
  end
data/lib/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PodcastApi
4
- VERSION = "1.1.5"
5
- end
4
+ VERSION = '3.0.0'
5
+ end
metadata CHANGED
@@ -1,29 +1,34 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: podcast_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Listen Notes, Inc.
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-04-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: httparty
15
14
  requirement: !ruby/object:Gem::Requirement
16
15
  requirements:
17
- - - "~>"
16
+ - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: 0.20.0
18
+ version: 0.24.1
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '0.25'
20
22
  type: :runtime
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
23
25
  requirements:
24
- - - "~>"
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: 0.24.1
29
+ - - "<"
25
30
  - !ruby/object:Gem::Version
26
- version: 0.20.0
31
+ version: '0.25'
27
32
  description: Listen Notes is the best podcast search engine and api. This is the
28
33
  official Ruby Gem for the Listen Notes Podcast API. See https://www.PodcastAPI.com/
29
34
  for details.
@@ -32,30 +37,23 @@ executables: []
32
37
  extensions: []
33
38
  extra_rdoc_files: []
34
39
  files:
35
- - Gemfile
36
- - Gemfile.lock
37
40
  - LICENSE
38
41
  - README.md
39
- - RakeFile
40
- - examples/Gemfile
41
- - examples/Gemfile.lock
42
- - examples/sample.rb
42
+ - lib/api-contract.json
43
+ - lib/api_methods.rb
43
44
  - lib/errors.rb
44
45
  - lib/podcast_api.rb
45
46
  - lib/version.rb
46
- - podcast_api.gemspec
47
- - rakefile.rb
48
47
  homepage: https://www.PodcastAPI.com/
49
48
  licenses:
50
49
  - MIT
51
50
  metadata:
52
51
  bug_tracker_uri: https://github.com/ListenNotes/podcast-api-ruby/issues
53
52
  changelog_uri: https://github.com/ListenNotes/podcast-api-ruby/releases
54
- documentation_uri: https://www.listennotes.com/podcast-api/docs/
53
+ documentation_uri: https://www.listennotes.com/api/docs/
55
54
  github_repo: https://github.com/ListenNotes/podcast-api-ruby
56
55
  homepage_uri: https://www.PodcastAPI.com/
57
56
  source_code_uri: https://github.com/ListenNotes/podcast-api-ruby
58
- post_install_message:
59
57
  rdoc_options: []
60
58
  require_paths:
61
59
  - lib
@@ -63,15 +61,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
63
61
  requirements:
64
62
  - - ">="
65
63
  - !ruby/object:Gem::Version
66
- version: 2.3.0
64
+ version: '3.3'
67
65
  required_rubygems_version: !ruby/object:Gem::Requirement
68
66
  requirements:
69
67
  - - ">="
70
68
  - !ruby/object:Gem::Version
71
69
  version: '0'
72
70
  requirements: []
73
- rubygems_version: 3.0.3.1
74
- signing_key:
71
+ rubygems_version: 4.0.20
75
72
  specification_version: 4
76
73
  summary: Ruby bindings for the Listen Notes Podcast API
77
74
  test_files: []
data/Gemfile DELETED
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- gemspec
6
-
7
- group :development do
8
- gem "mocha"
9
- gem "rake"
10
- gem "test-unit"
11
- gem "bundler"
12
- end
data/Gemfile.lock DELETED
@@ -1,34 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- podcast_api (1.1.5)
5
- httparty (~> 0.20.0)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- httparty (0.20.0)
11
- mime-types (~> 3.0)
12
- multi_xml (>= 0.5.2)
13
- mime-types (3.4.1)
14
- mime-types-data (~> 3.2015)
15
- mime-types-data (3.2023.0218.1)
16
- mocha (1.12.0)
17
- multi_xml (0.6.0)
18
- power_assert (2.0.0)
19
- rake (13.0.3)
20
- test-unit (3.4.1)
21
- power_assert
22
-
23
- PLATFORMS
24
- ruby
25
-
26
- DEPENDENCIES
27
- bundler
28
- mocha
29
- podcast_api!
30
- rake
31
- test-unit
32
-
33
- BUNDLED WITH
34
- 1.17.2
data/RakeFile DELETED
@@ -1,9 +0,0 @@
1
- require 'rake/testtask'
2
-
3
- Rake::TestTask.new(:test) do |test|
4
- test.libs << 'test'
5
- test.test_files = FileList['tests/*_test.rb']
6
- end
7
-
8
- desc 'Run Tests'
9
- task :default => :test
data/examples/Gemfile DELETED
@@ -1,3 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- gem "podcast_api", path: "../"
@@ -1,25 +0,0 @@
1
- PATH
2
- remote: ..
3
- specs:
4
- podcast_api (1.1.5)
5
- httparty (~> 0.20.0)
6
-
7
- GEM
8
- remote: https://rubygems.org/
9
- specs:
10
- httparty (0.20.0)
11
- mime-types (~> 3.0)
12
- multi_xml (>= 0.5.2)
13
- mime-types (3.4.1)
14
- mime-types-data (~> 3.2015)
15
- mime-types-data (3.2023.0218.1)
16
- multi_xml (0.6.0)
17
-
18
- PLATFORMS
19
- ruby
20
-
21
- DEPENDENCIES
22
- podcast_api!
23
-
24
- BUNDLED WITH
25
- 1.17.2