the_tv_db 0.0.1 → 0.0.2

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.
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/README.md CHANGED
@@ -16,7 +16,39 @@ And then execute:
16
16
 
17
17
  ## Usage
18
18
 
19
- TODO: Write usage instructions here
19
+ ```ruby
20
+ require "the_tv_db"
21
+
22
+ api = TheTvDB.new
23
+
24
+ # You can search for a series name
25
+ api.series.search("Fringe")
26
+ => [#<TheTvDB::Series::Collection id: "82066", api_id: "82066", banner: "graphical/82066-g38.jpg", first_aired: "2008-08-26", imdb_id: "tt1119644", name: "Fringe", language: "en", overview: "The series follows a Federal Bureau of Investigatio...", zap2it_id: "SH01059103">]
27
+
28
+ # Once you have the series ID you can use it to find its full information
29
+ series = api.series.find("82066")
30
+ => #<TheTvDB::Series id: "82066", actors: "|Anna Torv|Joshua Jackson|John Noble|Jasika Nicole|...", added: nil, added_by: nil, airs_day_of_week: "Friday", airs_time: "9:00 PM", banner: "graphical/82066-g38.jpg", content_rating: "TV-14", fanart: "fanart/original/82066-78.jpg", first_aired: "2008-08-26", genre: "|Drama|Science-Fiction|", imdb_id: "tt1119644", language: "en", last_updated: "1351221356", network: "FOX", network_id: nil, overview: "The series follows a Federal Bureau of Investigatio...", rating: "8.7", rating_count: "571", runtime: "60", poster: "posters/82066-53.jpg", series_id: "75146", series_name: "Fringe", status: "Continuing", zap2it_id: "SH01059103">
31
+
32
+ ## ...Even with episodes
33
+ series.episodes
34
+ => [#<TheTvDB::Episode id: "694851", absolute_number: nil, airs_after_season: nil, airs_before_episode: "1", airs_before_season: "1", combined_episode_number: "1", combined_season: "0", director: nil, dvd_chapter: nil, dvd_disc_id: nil, dvd_episode_number: nil, dvd_season: nil, ep_img_flag: "1", name: "Unaired Pilot", number: "1", filename: "episodes/82066/694851.jpg", first_aired: nil, guest_stars: nil, imdb_id: nil, language: "en", last_updated: "1263338464", overview: nil, production_code: nil, rating: nil, rating_count: "0", season_id: "32605", season_number: "0", series_id: "82066", ... (96 more)
35
+
36
+ # Some methods require an API key,
37
+ # For example: searching for episodes by air_date
38
+ api.episodes.find(series_id="82066", air_date="2012-11-16")
39
+ TheTvDB::TheTvDBError: You must have an Application Key to use this interface
40
+
41
+ api = TheTvDB.new(api_key: your_api_key)
42
+ api.episodes.find(series_id="82066", air_date="2012-11-16")
43
+ => [#<TheTvDB::Episode id: "4393016", absolute_number: nil, airs_after_season: nil, airs_before_episode: nil, airs_before_season: nil, combined_episode_number: "7", combined_season: "5", director: nil, dvd_chapter: nil, dvd_disc_id: nil, dvd_episode_number: nil, dvd_season: nil, ep_img_flag: nil, name: "52010", number: "7", filename: nil, first_aired: "2012-11-16", guest_stars: nil, imdb_id: nil, language: "en", last_updated: "1350927372", overview: "The team orchestrates an event of its own.", production_code: nil, rating: nil, rating_count: nil, season_id: "494897", season_number: "5", series_id: "82066", writer: nil>]
44
+ ```
45
+
46
+ ## Feature & Development Roadmap
47
+
48
+ * Series banners
49
+ * Season posters
50
+ * Fan-art posters
51
+ * List of supported languages
20
52
 
21
53
  ## Contributing
22
54
 
@@ -0,0 +1,21 @@
1
+ require "the_tv_db/connection"
2
+
3
+ module TheTvDB
4
+ class API
5
+ extend Connection
6
+
7
+ def initialize(options={}, &block)
8
+ super()
9
+ set_api_client
10
+ self
11
+
12
+ self.instance_eval(&block) if block_given?
13
+ end
14
+
15
+ # Assigns current api class
16
+ def set_api_client
17
+ TheTvDB.api_client = self
18
+ end
19
+
20
+ end # API
21
+ end # TheTvDB
@@ -0,0 +1,17 @@
1
+ require "the_tv_db/series"
2
+ require "the_tv_db/series/collection"
3
+ require "the_tv_db/episode"
4
+
5
+ module TheTvDB
6
+ class Client < API
7
+
8
+ def series
9
+ Series
10
+ end
11
+
12
+ def episodes
13
+ Episode
14
+ end
15
+
16
+ end # API
17
+ end # TheTvDB
@@ -0,0 +1,25 @@
1
+ require "faraday"
2
+ require "the_tv_db/response"
3
+ require "the_tv_db/response/xmlize"
4
+
5
+ module TheTvDB
6
+ module Connection
7
+ extend self
8
+
9
+ @connection = nil
10
+
11
+ def connection
12
+ @connection ||= Faraday.new(:url => TheTvDB::ENDPOINT) do |faraday|
13
+ faraday.response :logger if ENV['DEBUG'] # log requests to STDOUT
14
+ faraday.adapter :typhoeus # make requests with Typhoeus
15
+ faraday.use TheTvDB::Response::Xmlize
16
+ end
17
+ end
18
+
19
+ def request(path, params={})
20
+ response = connection.get(path, params)
21
+ response.body
22
+ end
23
+
24
+ end # Connection
25
+ end # TheTvDB
@@ -0,0 +1,12 @@
1
+ class String
2
+ # Makes an underscored, lowercase form from the expression in the string.
3
+ def underscore
4
+ word = self.dup
5
+ word.gsub!('::', '/')
6
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
7
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
8
+ word.tr!("-", "_")
9
+ word.downcase!
10
+ word
11
+ end
12
+ end
@@ -0,0 +1,81 @@
1
+ module TheTvDB
2
+ class Episode < API
3
+ include Model
4
+
5
+ class << self
6
+
7
+ # Public: Find an episode by air date.
8
+ #
9
+ # This is useful for shows that don't contain season and episode info,
10
+ # but rather have the date in the title.
11
+ #
12
+ # series_id - The ID of the series.
13
+ # air_date - The air date of the episode.
14
+ # lang - The language of the episode.
15
+ #
16
+ # Examples
17
+ #
18
+ # find("82066", "2012-11-16")
19
+ # find("82066", "11/16/2012", "fr")
20
+ #
21
+ # Returns array of TheTvDB::Episode objects.
22
+ def find(series_id, air_date=nil, lang="en")
23
+ data = request("GetEpisodeByAirDate.php", {
24
+ apikey: TheTvDB.api_key,
25
+ seriesid: series_id,
26
+ airdate: air_date,
27
+ language: lang
28
+ })["Data"]
29
+
30
+ raise TheTvDBError, data["Error"] if data["Error"]
31
+
32
+ episodes = data["Episode"]
33
+
34
+ case episodes
35
+ when Hash
36
+ [ new(episodes) ]
37
+ when Array
38
+ episodes.collect { |episode| new(episode) }
39
+ else
40
+ []
41
+ end
42
+ end
43
+ alias :get :find
44
+ end
45
+
46
+ ATTRS_MAP = {
47
+ :id => "id",
48
+ :absolute_number => "absolute_number",
49
+ :airs_after_season => "airsafter_season",
50
+ :airs_before_episode => "airsbefore_episode",
51
+ :airs_before_season => "airsbefore_season",
52
+ :combined_episode_number => "Combined_episodenumber",
53
+ :combined_season => "Combined_season",
54
+ :director => "Director",
55
+ :dvd_chapter => "DVD_chapter",
56
+ :dvd_disc_id => "DVD_discid",
57
+ :dvd_episode_number => "DVD_episodenumber",
58
+ :dvd_season => "DVD_season",
59
+ :ep_img_flag => "EpImgFlag",
60
+ :name => "EpisodeName",
61
+ :number => "EpisodeNumber",
62
+ :filename => "filename",
63
+ :first_aired => "FirstAired",
64
+ :guest_stars => "GuestStars",
65
+ :imdb_id => "IMDB_ID",
66
+ :language => "Language",
67
+ :last_updated => "lastupdated",
68
+ :overview => "Overview",
69
+ :production_code => "ProductionCode",
70
+ :rating => "Rating",
71
+ :rating_count => "RatingCount",
72
+ :season_id => "seasonid",
73
+ :season_number => "SeasonNumber",
74
+ :series_id => "seriesid",
75
+ :writer => "Writer"
76
+ }.freeze
77
+
78
+ attr_accessor *ATTRS_MAP.keys
79
+
80
+ end # Series
81
+ end # TheTvDB
@@ -0,0 +1,18 @@
1
+ module TheTvDB
2
+
3
+ # Generic TheTvDB exception class.
4
+ class TheTvDBError < StandardError
5
+ end
6
+
7
+ # Raised when a connection to the api can't be established.
8
+ class ConnectionNotEstablished < StandardError
9
+ end
10
+
11
+ # Raised when the record was not found given an id or set of ids.
12
+ class RecordNotFound < StandardError
13
+ end
14
+
15
+ # Raised when unknown attributes are supplied via mass assignment.
16
+ class UnknownAttributeError < NoMethodError
17
+ end
18
+ end
@@ -0,0 +1,53 @@
1
+ module TheTvDB
2
+ module Model
3
+
4
+ attr_accessor :klass
5
+
6
+ def initialize(params=nil)
7
+ @klass = self.class
8
+ self.attributes = params
9
+ end
10
+
11
+ def attributes=(params=nil)
12
+ params.each do |attr, value|
13
+ begin
14
+ self.public_send("#{klass::ATTRS_MAP.key(attr)}=", value)
15
+ rescue NoMethodError
16
+ raise UnknownAttributeError, "unknown attribute: #{attr}"
17
+ end
18
+ end if params
19
+ end
20
+
21
+ def attributes
22
+ attrs = {}
23
+ klass::ATTRS_MAP.keys.each do |name|
24
+ attrs[name] = send(name)
25
+ end
26
+ attrs
27
+ end
28
+
29
+ def inspect
30
+ inspection = unless id.nil?
31
+ klass::ATTRS_MAP.keys.collect { |name|
32
+ "#{name}: #{attribute_for_inspect(name)}"
33
+ }.compact.join(", ")
34
+ else
35
+ "not initialized"
36
+ end
37
+ "#<#{self.class} #{inspection}>"
38
+ end
39
+
40
+ def attribute_for_inspect(attr_name)
41
+ value = send(attr_name)
42
+
43
+ if value.is_a?(String) && value.length > 50
44
+ "#{value[0..50]}...".inspect
45
+ elsif value.is_a?(Date) || value.is_a?(Time)
46
+ %("#{value.to_s(:db)}")
47
+ else
48
+ value.inspect
49
+ end
50
+ end
51
+
52
+ end # Model
53
+ end # TheTvDB
@@ -0,0 +1,26 @@
1
+ require "multi_xml"
2
+
3
+ module TheTvDB
4
+ class Response::Xmlize < Response
5
+ dependency 'multi_xml'
6
+
7
+ define_parser do |body|
8
+ MultiXml.parser = :ox
9
+ MultiXml.parse body
10
+ end
11
+
12
+ def parse(body)
13
+ case body
14
+ when ''
15
+ nil
16
+ when 'true'
17
+ true
18
+ when 'false'
19
+ false
20
+ else
21
+ self.class.parser.call body
22
+ end
23
+ end
24
+
25
+ end # Response::Xmlize
26
+ end # TheTvDB
@@ -0,0 +1,24 @@
1
+ module TheTvDB
2
+ # Contains methods and attributes that act on the response returned from the
3
+ # request
4
+ class Response < Faraday::Response::Middleware
5
+ CONTENT_TYPE = 'Content-Type'.freeze
6
+
7
+ class << self
8
+ attr_accessor :parser
9
+ end
10
+
11
+ def self.define_parser(&block)
12
+ @parser = block
13
+ end
14
+
15
+ def response_type(env)
16
+ env[:response_headers][CONTENT_TYPE].to_s
17
+ end
18
+
19
+ def parse_response?(env)
20
+ env[:body].respond_to? :to_str
21
+ end
22
+
23
+ end # Response
24
+ end # TheTvDB
@@ -0,0 +1,20 @@
1
+ module TheTvDB
2
+ class Series::Collection
3
+ include Model
4
+
5
+ ATTRS_MAP = {
6
+ :id => "seriesid",
7
+ :api_id => "id",
8
+ :banner => "banner",
9
+ :first_aired => "FirstAired",
10
+ :imdb_id => "IMDB_ID",
11
+ :name => "SeriesName",
12
+ :language => "language",
13
+ :overview => "Overview",
14
+ :zap2it_id => "zap2it_id"
15
+ }.freeze
16
+
17
+ attr_accessor *ATTRS_MAP.keys
18
+
19
+ end # Series::Collection
20
+ end # TheTvDB
@@ -0,0 +1,75 @@
1
+ module TheTvDB
2
+ class Series < API
3
+ include Model
4
+
5
+ class << self
6
+ def search(name, lang="en")
7
+ data = request("GetSeries.php", { seriesname: name, language: lang })["Data"]
8
+ return [] if data.nil?
9
+
10
+ series = data["Series"]
11
+
12
+ case series
13
+ when Hash
14
+ [ Series::Collection.new(series) ]
15
+ when Array
16
+ series.collect { |serie| Series::Collection.new(serie) }
17
+ else
18
+ []
19
+ end
20
+ end
21
+
22
+ def find(id)
23
+ data = request("/data/series/#{id}/all/")["Data"]
24
+ record = new(data["Series"])
25
+ record.episodes = data["Episode"]
26
+ return record
27
+ rescue MultiXml::ParseError
28
+ raise RecordNotFound, "Couldn't find series with ID=#{id}"
29
+ end
30
+ alias :get :find
31
+ end
32
+
33
+ ATTRS_MAP = {
34
+ :id => "id",
35
+ :actors => "Actors",
36
+ :added => "added",
37
+ :added_by => "addedBy",
38
+ :airs_day_of_week => "Airs_DayOfWeek",
39
+ :airs_time => "Airs_Time",
40
+ :banner => "banner",
41
+ :content_rating => "ContentRating",
42
+ :fanart => "fanart",
43
+ :first_aired => "FirstAired",
44
+ :genre => "Genre",
45
+ :imdb_id => "IMDB_ID",
46
+ :language => "Language",
47
+ :last_updated => "lastupdated",
48
+ :network => "Network",
49
+ :network_id => "NetworkID",
50
+ :overview => "Overview",
51
+ :rating => "Rating",
52
+ :rating_count => "RatingCount",
53
+ :runtime => "Runtime",
54
+ :poster => "poster",
55
+ :series_id => "SeriesID",
56
+ :series_name => "SeriesName",
57
+ :status => "Status",
58
+ :zap2it_id => "zap2it_id"
59
+ }.freeze
60
+
61
+ attr_accessor *ATTRS_MAP.keys, :episodes
62
+
63
+ def episodes=(episodes)
64
+ @episodes = case episodes
65
+ when Hash
66
+ [ Episode.new(episodes) ]
67
+ when Array
68
+ episodes.collect { |episode| Episode.new(episode) }
69
+ else
70
+ []
71
+ end
72
+ end
73
+
74
+ end # Series
75
+ end # TheTvDB
@@ -1,3 +1,3 @@
1
1
  module TheTvDb
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/the_tv_db.rb CHANGED
@@ -1,4 +1,35 @@
1
+ require "the_tv_db/core_ext/string"
1
2
  require "the_tv_db/version"
3
+ require "the_tv_db/errors"
4
+ require "the_tv_db/api"
5
+ require "the_tv_db/model"
6
+ require "the_tv_db/client"
2
7
 
3
- module TheTvDb
4
- end
8
+ module TheTvDB
9
+
10
+ SITE = "http://thetvdb.com".freeze
11
+ ENDPOINT = "http://thetvdb.com/api".freeze
12
+
13
+ class << self
14
+
15
+ # Handle for the client instance
16
+ attr_accessor :api_client, :api_key
17
+
18
+ def new(options={}, &block)
19
+ @api_key = options.delete(:api_key)
20
+ @api_client = TheTvDB::Client.new(options, &block)
21
+ end
22
+
23
+ # Delegate to TheTvDB::Client
24
+ #
25
+ def method_missing(method, *args, &block)
26
+ return super unless new.respond_to?(method)
27
+ new.send(method, *args, &block)
28
+ end
29
+
30
+ def respond_to?(method, include_private = false)
31
+ new.respond_to?(method, include_private) || super(method, include_private)
32
+ end
33
+
34
+ end # Self
35
+ end # TheTvDB
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <Data>
3
+ <Error>airdate is required for this interface</Error>
4
+ <Episode/>
5
+ </Data>
@@ -0,0 +1,7 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <Data>
3
+ <Error>
4
+ You must have an Application Key to use this interface
5
+ </Error>
6
+ <Episode/>
7
+ </Data>
@@ -0,0 +1,30 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <Data>
3
+ <Episode>
4
+ <id>4393016</id>
5
+ <Combined_episodenumber>7</Combined_episodenumber>
6
+ <Combined_season>5</Combined_season>
7
+ <DVD_chapter/>
8
+ <DVD_discid/>
9
+ <DVD_episodenumber/>
10
+ <DVD_season/>
11
+ <Director/>
12
+ <EpImgFlag/>
13
+ <EpisodeName>52010</EpisodeName>
14
+ <EpisodeNumber>7</EpisodeNumber>
15
+ <FirstAired>2012-11-16</FirstAired>
16
+ <GuestStars/>
17
+ <IMDB_ID/>
18
+ <Language>en</Language>
19
+ <Overview>The team orchestrates an event of its own.</Overview>
20
+ <ProductionCode/>
21
+ <Rating/>
22
+ <SeasonNumber>5</SeasonNumber>
23
+ <Writer/>
24
+ <absolute_number/>
25
+ <filename/>
26
+ <lastupdated>1350927372</lastupdated>
27
+ <seasonid>494897</seasonid>
28
+ <seriesid>82066</seriesid>
29
+ </Episode>
30
+ </Data>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <Data>
3
+ <Error>No Results from SP</Error>
4
+ <Episode/>
5
+ </Data>
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
2
+ <html><head>
3
+ <title>404 Not Found</title>
4
+ <script type="text/javascript">
5
+ //<![CDATA[
6
+ window.__CF=window.__CF||{};window.__CF.AJS={"ga_key":{"ua":"UA-9131861-1","ga_bs":"2"}};
7
+ //]]>
8
+ </script>
9
+ <script type="text/javascript">
10
+ //<![CDATA[
11
+ try{if (!window.CloudFlare) { var CloudFlare=[{verbose:0,p:0,byc:0,owlid:"cf",mirage:{responsive:0,lazy:0},oracle:"9a/82fd4d610b168badf482d8810933fa",paths:{cloudflare:"/cdn-cgi/nexp/aav=1870252173/"},atok:"4fa89040047d9c4e9fe703b2270c89db",zone:"thetvdb.com",rocket:"0",apps:{"ga_key":{"ua":"UA-9131861-1","ga_bs":"2"}}}];var a=document.createElement("script"),b=document.getElementsByTagName("script")[0];a.async=!0;a.src="//ajax.cloudflare.com/cdn-cgi/nexp/aav=4114775854/cloudflare.min.js";b.parentNode.insertBefore(a,b);}}catch(e){};
12
+ //]]>
13
+ </script>
14
+ <script type="text/javascript">
15
+ /* <![CDATA[ */
16
+ var _gaq = _gaq || [];
17
+ _gaq.push(['_setAccount', 'UA-9131861-1']);
18
+ _gaq.push(['_trackPageview']);
19
+
20
+ (function() {
21
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
22
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
23
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
24
+ })();
25
+
26
+ (function(b){(function(a){"__CF"in b&&"DJS"in b.__CF?b.__CF.DJS.push(a):"addEventListener"in b?b.addEventListener("load",a,!1):b.attachEvent("onload",a)})(function(){"FB"in b&&"Event"in FB&&"subscribe"in FB.Event&&(FB.Event.subscribe("edge.create",function(a){_gaq.push(["_trackSocial","facebook","like",a])}),FB.Event.subscribe("edge.remove",function(a){_gaq.push(["_trackSocial","facebook","unlike",a])}),FB.Event.subscribe("message.send",function(a){_gaq.push(["_trackSocial","facebook","send",a])}));"twttr"in b&&"events"in twttr&&"bind"in twttr.events&&twttr.events.bind("tweet",function(a){if(a){var b;if(a.target&&a.target.nodeName=="IFRAME")a:{if(a=a.target.src){a=a.split("#")[0].match(/[^?=&]+=([^&]*)?/g);b=0;for(var c;c=a[b];++b)if(c.indexOf("url")===0){b=unescape(c.split("=")[1]);break a}}b=void 0}_gaq.push(["_trackSocial","twitter","tweet",b])}})})})(window);
27
+ /* ]]> */
28
+ </script>
29
+ </head><body><script type="text/javascript">
30
+ //<![CDATA[
31
+ new Image().src = "/cdn-cgi/ping?cf[location]=404&cf[js]=1";
32
+ //]]>
33
+ </script>
34
+ <noscript>
35
+ <img src="/cdn-cgi/ping?cf[location]=404&cf[js]=0" alt="">
36
+ </noscript>
37
+ <h1>Not Found</h1>
38
+ <p>The requested URL /data/series/-1/all/ was not found on this server.</p>
39
+ </body></html>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <Data></Data>