the_tv_db 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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>