diff --git a/.gitignore b/.gitignore index e3200e0..da53231 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,6 @@ build-iPhoneSimulator/ # Used by RuboCop. Remote config files pulled in from inherit_from directive. # .rubocop-https?--* +.idea/ +venv +.bundle diff --git a/README.md b/README.md index 103f29f..b1dc573 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ See the [Listen Notes Podcast API docs](https://www.listennotes.com/api/docs/). Install the package with: ```sh -gem install podcast-api +gem install podcast_api ``` @@ -29,14 +29,47 @@ gem install podcast-api ## Usage The library needs to be configured with your account's API key which is -available in your [Listen API Dashboard](https://www.listennotes.com/api/dashboard/#apps). Set `apiKey` to its +available in your [Listen API Dashboard](https://www.listennotes.com/api/dashboard/#apps). Set `api_key` to its value: ```ruby -# TODO +require "podcast_api" + +api_key = ENV["LISTEN_API_KEY"] +client = PodcastApi::Client.new(api_key: api_key) + +begin + response = client.search(q: 'startup', type: 'episode') + puts JSON.parse(response.body) +rescue PodcastApi::AuthenticationError + puts 'Wrong api key' +rescue PodcastApi::InvalidRequestError + puts 'Client side errors, e.g., wrong parameters' +rescue PodcastApi::NotFoundError + puts 'Not found' +rescue PodcastApi::RateLimitError + puts 'Reached quota limit' +rescue PodcastApi::APIConnectionError + puts 'Failed to connect to Listen API servers' +rescue PodcastApi::PodcastApiError + puts 'Server side errors' +else + puts "Free Quota per month: #{response.headers['X-ListenAPI-FreeQuota']}" + puts "Usage this month: #{response.headers['X-ListenAPI-Usage']}" + puts "Next billing date: #{response.headers['X-Listenapi-NextBillingDate']}" +end ``` -If `apiKey` is null, then we'll connect to a [mock server](https://www.listennotes.com/api/tutorials/#faq0) that returns fake data for testing purposes. +If `api_key` is nil, then we'll connect to a [mock server](https://www.listennotes.com/api/tutorials/#faq0) that returns fake data for testing purposes. You can see all available API endpoints and parameters on the API Docs page at [listennotes.com/api/docs/](https://www.listennotes.com/api/docs/). + +### Handling exceptions + +Unsuccessful requests raise exceptions. The class of the exception will reflect +the sort of error that occurred. + +All exception classes can be found in [this file](https://github.com/ListenNotes/podcast-api-ruby/blob/main/lib/errors.rb). + +And you can see some sample code [here](https://github.com/ListenNotes/podcast-api-ruby/blob/main/examples/sample.rb). diff --git a/examples/Gemfile b/examples/Gemfile index a08c364..5fee00e 100644 --- a/examples/Gemfile +++ b/examples/Gemfile @@ -1 +1,3 @@ +source 'https://rubygems.org' + gem "podcast_api", path: "../" diff --git a/examples/Gemfile.lock b/examples/Gemfile.lock index 033ac0c..ac5f3bc 100644 --- a/examples/Gemfile.lock +++ b/examples/Gemfile.lock @@ -2,9 +2,18 @@ PATH remote: .. specs: podcast_api (1.0.1) + httparty GEM + remote: https://rubygems.org/ specs: + httparty (0.18.1) + mime-types (~> 3.0) + multi_xml (>= 0.5.2) + mime-types (3.3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2021.0225) + multi_xml (0.6.0) PLATFORMS ruby diff --git a/examples/sample.rb b/examples/sample.rb index c55d21f..dc7c534 100644 --- a/examples/sample.rb +++ b/examples/sample.rb @@ -1 +1,77 @@ require "podcast_api" + +api_key = ENV["LISTEN_API_KEY"] +client = PodcastApi::Client.new(api_key: api_key) + +begin + response = client.search(q: 'startup') + puts JSON.parse(response.body) +rescue PodcastApi::AuthenticationError + puts 'Wrong api key' +rescue PodcastApi::InvalidRequestError + puts 'Client side errors, e.g., wrong parameters' +rescue PodcastApi::NotFoundError + puts 'Not found' +rescue PodcastApi::RateLimitError + puts 'Reached quota limit' +rescue PodcastApi::APIConnectionError + puts 'Failed to connect to Listen API servers' +rescue PodcastApi::PodcastApiError + puts 'Server side errors' +else + puts "Free Quota per month: #{response.headers['X-ListenAPI-FreeQuota']}" + puts "Usage this month: #{response.headers['X-ListenAPI-Usage']}" + puts "Next billing date: #{response.headers['X-Listenapi-NextBillingDate']}" +end + + +# response = client.typeahead(q: 'startup', show_podcasts: 1) +# puts JSON.parse(response.body) + +# response = client.fetch_best_podcasts() +# puts JSON.parse(response.body) + +# response = client.fetch_podcast_by_id(id: '4d3fe717742d4963a85562e9f84d8c79') +# puts JSON.parse(response.body) + +# response = client.fetch_episode_by_id(id: '6b6d65930c5a4f71b254465871fed370') +# puts JSON.parse(response.body) + +# response = client.batch_fetch_podcasts(ids: '3302bc71139541baa46ecb27dbf6071a,68faf62be97149c280ebcc25178aa731,37589a3e121e40debe4cef3d9638932a,9cf19c590ff0484d97b18b329fed0c6a') +# puts JSON.parse(response.body) + +# response = client.batch_fetch_episodes(ids: 'c577d55b2b2b483c969fae3ceb58e362,0f34a9099579490993eec9e8c8cebb82') +# puts JSON.parse(response.body) + +# response = client.fetch_curated_podcasts_list_by_id(id: 'SDFKduyJ47r') +# puts JSON.parse(response.body) + +# response = client.fetch_podcast_genres() +# puts JSON.parse(response.body) + +# response = client.fetch_podcast_regions() +# puts JSON.parse(response.body) + +# response = client.fetch_podcast_languages() +# puts JSON.parse(response.body) + +# response = client.just_listen() +# puts JSON.parse(response.body) + +# response = client.fetch_recommendations_for_podcast(id: '25212ac3c53240a880dd5032e547047b') +# puts JSON.parse(response.body) + +# response = client.fetch_recommendations_for_episode(id: '914a9deafa5340eeaa2859c77f275799') +# puts JSON.parse(response.body) + +# response = client.fetch_playlist_by_id(id: 'm1pe7z60bsw') +# puts JSON.parse(response.body) + +# response = client.fetch_my_playlists() +# puts JSON.parse(response.body) + +# response = client.submit_podcast(rss: 'https://feeds.megaphone.fm/committed') +# puts JSON.parse(response.body) + +# response = client.delete_podcast(id: '4d3fe717742d4963a85562e9f84d8c79') +# puts JSON.parse(response.body) diff --git a/lib/errors.rb b/lib/errors.rb new file mode 100644 index 0000000..1135ca6 --- /dev/null +++ b/lib/errors.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module PodcastApi + # PodcastApiError is the base error from which all other more specific PodcastApi + # errors derive. + class PodcastApiError < StandardError + end + + class AuthenticationError < PodcastApiError + end + + class APIConnectionError < PodcastApiError + end + + class InvalidRequestError < PodcastApiError + end + + class RateLimitError < PodcastApiError + end + + class NotFoundError < PodcastApiError + end + + class APIConnectionError < PodcastApiError + end +end diff --git a/lib/podcast_api.rb b/lib/podcast_api.rb index 23b90dd..5feaee4 100644 --- a/lib/podcast_api.rb +++ b/lib/podcast_api.rb @@ -1,11 +1,140 @@ # frozen_string_literal: true -require "net/http" +require 'httparty' # Version -require "version" - -puts PodcastApi::VERSION +require 'version' +require 'errors' module PodcastApi + + class Client + include HTTParty + + @@BASE_URL_PROD = 'https://listen-api.listennotes.com/api/v2' + @@BASE_URL_TEST = 'https://listen-api-test.listennotes.com/api/v2' + + def initialize(api_key: nil, user_agent: nil) + @api_key = api_key + @base_url = api_key ? @@BASE_URL_PROD : @@BASE_URL_TEST + @headers = { + 'X-ListenAPI-Key' => @api_key, + 'User-Agent' => user_agent ? user_agent : "podcasts-api-ruby #{VERSION}" + } + puts @headers + end + + protected + def get_response(response:) + if response.code == 200 + return response + elsif response.code == 400 + raise InvalidRequestError.new 'something wrong on your end (client side errors), e.g., missing required parameters' + elsif response.code == 401 + raise AuthenticationError.new 'wrong api key or your account is suspended' + elsif response.code == 404 + raise NotFoundError.new 'endpoint not exist, or podcast / episode not exist' + elsif response.code == 429 + raise RateLimitError.new 'you are using FREE plan and you exceed the quota limit' + else + raise PodcastApiError.new 'something wrong on our end (unexpected server errors)' + end + end + + def send_http_request(http_method, *args) + begin + response = HTTParty.public_send(http_method, *args) + rescue SocketError + raise APIConnectionError.new 'Failed to connect to Listen API servers' + else + return get_response(response: response) + end + end + + public + def search(**kwargs) + return send_http_request('get', "#{@base_url}/search", {query: kwargs, headers: @headers}) + end + + def typeahead(**kwargs) + return send_http_request('get', "#{@base_url}/typeahead", {query: kwargs, headers: @headers}) + end + + def fetch_best_podcasts(**kwargs) + return send_http_request('get', "#{@base_url}/best_podcasts", {query: kwargs, headers: @headers}) + end + + def fetch_podcast_by_id(**kwargs) + id = kwargs.delete(:id) + return send_http_request('get', "#{@base_url}/podcasts/#{id}", {query: kwargs, headers: @headers}) + end + + def fetch_episode_by_id(**kwargs) + id = kwargs.delete(:id) + return send_http_request('get', "#{@base_url}/episodes/#{id}", {query: kwargs, headers: @headers}) + end + + def batch_fetch_podcasts(**kwargs) + return send_http_request('post', "#{@base_url}/podcasts", {body: kwargs, headers: @headers}) + end + + def batch_fetch_episodes(**kwargs) + return send_http_request('post', "#{@base_url}/episodes", {body: kwargs, headers: @headers}) + end + + def fetch_curated_podcasts_list_by_id(**kwargs) + id = kwargs.delete(:id) + return send_http_request('get', "#{@base_url}/curated_podcasts/#{id}", {query: kwargs, headers: @headers}) + end + + def fetch_curated_podcasts_lists(**kwargs) + id = kwargs.delete(:id) + return send_http_request('get', "#{@base_url}/curated_podcasts", {query: kwargs, headers: @headers}) + end + + def fetch_podcast_genres(**kwargs) + return send_http_request('get', "#{@base_url}/genres", {query: kwargs, headers: @headers}) + end + + def fetch_podcast_regions(**kwargs) + return send_http_request('get', "#{@base_url}/regions", {query: kwargs, headers: @headers}) + end + + def fetch_podcast_languages(**kwargs) + return send_http_request('get', "#{@base_url}/languages", {query: kwargs, headers: @headers}) + end + + def just_listen(**kwargs) + return send_http_request('get', "#{@base_url}/just_listen", {query: kwargs, headers: @headers}) + end + + def fetch_recommendations_for_podcast(**kwargs) + id = kwargs.delete(:id) + return send_http_request('get', "#{@base_url}/podcasts/#{id}/recommendations", {query: kwargs, headers: @headers}) + end + + def fetch_recommendations_for_episode(**kwargs) + id = kwargs.delete(:id) + return send_http_request('get', "#{@base_url}/episodes/#{id}/recommendations", {query: kwargs, headers: @headers}) + end + + def fetch_playlist_by_id(**kwargs) + id = kwargs.delete(:id) + return send_http_request('get', "#{@base_url}/playlists/#{id}", {query: kwargs, headers: @headers}) + end + + def fetch_my_playlists(**kwargs) + return send_http_request('get', "#{@base_url}/playlists", {query: kwargs, headers: @headers}) + end + + def submit_podcast(**kwargs) + return send_http_request('post', "#{@base_url}/podcasts/submit", {body: kwargs, headers: @headers}) + end + + def delete_podcast(**kwargs) + id = kwargs.delete(:id) + return send_http_request('delete', "#{@base_url}/podcasts/#{id}", {query: kwargs, headers: @headers}) + end + end + end diff --git a/podcast_api.gemspec b/podcast_api.gemspec index 4f734c8..500b5f4 100644 --- a/podcast_api.gemspec +++ b/podcast_api.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.email = "hello@listennotes.com" s.homepage = "https://www.listennotes.com/api/" s.license = "MIT" - + s.add_runtime_dependency "httparty" s.metadata = { "bug_tracker_uri" => "https://github.com/ListenNotes/podcast-api-ruby/issues", "changelog_uri" =>