diff --git a/.travis.yml b/.travis.yml index dc2e8c8..cec036a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,4 @@ rvm: - - 1.9.2 - 1.9.3 + - 2.0.0 + - 2.1.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 42db3f5..1e477e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.5.3 +* The player name is now escaped correctly in the URI (@jodyalbritton) + +## 1.5.2 +* Added support for authentication token via `Plex.configure` + ## 1.5.1 * `Plex::Video` now supports multiple media entries (`@media` renamed to `@medias`) diff --git a/lib/plex-ruby.rb b/lib/plex-ruby.rb index 793cb20..aea65f1 100644 --- a/lib/plex-ruby.rb +++ b/lib/plex-ruby.rb @@ -22,10 +22,10 @@ def self.configure # Custom open func which adds the required headers configured by # Plex.configure def self.open(url) - headers = {} + headers = {:ssl_verify_mode => 0} headers["X-Plex-Token"] = config.auth_token if config.auth_token - super(url, headers) + URI.open(url, headers) end # Converts camel case names that are commonly found in the Plex APIs into @@ -62,10 +62,15 @@ def self.camelize(string, first_letter_uppercase = false) require 'plex-ruby/video' require 'plex-ruby/media' require 'plex-ruby/part' +require 'plex-ruby/player' require 'plex-ruby/stream' +require 'plex-ruby/status' require 'plex-ruby/tags' require 'plex-ruby/show' require 'plex-ruby/season' require 'plex-ruby/episode' require 'plex-ruby/movie' +require 'plex-ruby/artist' +require 'plex-ruby/album' +require 'plex-ruby/track' diff --git a/lib/plex-ruby/album.rb b/lib/plex-ruby/album.rb new file mode 100644 index 0000000..3d25e9c --- /dev/null +++ b/lib/plex-ruby/album.rb @@ -0,0 +1,85 @@ +module Plex + class Album + + ATTRIBUTES = %w(ratingKey guid title summary index thumb year addedAt updatedAt) + + attr_reader :section, :key, :attribute_hash + + # @param [Artist] Artist this album belongs to + # @param [String] key to use to later grab this Album + def initialize(section, key) + @section = section + @key = key + @attribute_hash = {} + + directory.attributes.each do |method, val| + @attribute_hash[Plex.underscore(method)] = val.value + define_singleton_method(Plex.underscore(method).to_sym) do + val.value + end + end + + @attribute_hash.merge({'key' => @key}) + end + + # The list of Tracks in the library that belong to this Album + # + # @return [Array] list of Tracks that appear on this Album + def tracks + search_children children + end + + # @private + def url #:nodoc: + section.url + end + + # @private + def ==(other) #:nodoc: + if other.is_a? Plex::Album + key == other.key + else + super + end + end + + # @private + def inspect #:nodoc: + "#" + end + + private + + def base_doc + Nokogiri::XML( open(url+key) ) + end + + def children_base + Nokogiri::XML( open(url+key+'/children') ) + end + + def xml_doc + @xml_doc ||= base_doc + end + + def children + @children ||= children_base + end + + def directory + @directory ||= xml_doc.search('Directory').first + end + + def search_children(node) + node.search('Track').map do |track| + plex_track.new(self, track.attr('key'), track.search('Media')) + end + end + + def plex_track + @plex_track ||= Plex::Track + end + + end + +end diff --git a/lib/plex-ruby/artist.rb b/lib/plex-ruby/artist.rb new file mode 100644 index 0000000..a635ab9 --- /dev/null +++ b/lib/plex-ruby/artist.rb @@ -0,0 +1,85 @@ +module Plex + class Artist + + ATTRIBUTES = %w(ratingKey guid title summary index thumb addedAt updatedAt) + + attr_reader :section, :key, :attribute_hash + + # @param [Section] section this artist belongs to + # @param [String] key to use to later grab this Artist + def initialize(section, key) + @section = section + @key = key + @attribute_hash = {} + + directory.attributes.each do |method, val| + @attribute_hash[Plex.underscore(method)] = val.value + define_singleton_method(Plex.underscore(method).to_sym) do + val.value + end + end + + @attribute_hash.merge({'key' => @key}) + end + + # The list of Albums in the library that belong to this Artist + # + # @return [Array] list of Albums that are credited to this Artist + def albums + @albums ||= search_children children + end + + # @private + def url #:nodoc: + section.url + end + + # @private + def ==(other) #:nodoc: + if other.is_a? Plex::Artist + key == other.key + else + super + end + end + + # @private + def inspect #:nodoc: + "#" + end + + private + + def base_doc + Nokogiri::XML( open(url+key) ) + end + + def children_base + Nokogiri::XML( open(url+key+'/children') ) + end + + def xml_doc + @xml_doc ||= base_doc + end + + def children + @children ||= children_base + end + + def directory + @directory ||= xml_doc.search('Directory').first + end + + def search_children(node) + node.search('Directory[type="album"]').map do |album| + plex_album.new(self, album.attr('key')[0..-10]) # Remove /children + end + end + + def plex_album + @plex_album ||= Plex::Album + end + + end + +end diff --git a/lib/plex-ruby/client.rb b/lib/plex-ruby/client.rb index 023c443..e8e19e2 100644 --- a/lib/plex-ruby/client.rb +++ b/lib/plex-ruby/client.rb @@ -133,7 +133,7 @@ def inspect #:nodoc: private def player_url - url+"/system/players/#{name}" + URI.escape(url+"/system/players/#{name}") end def ping(url) diff --git a/lib/plex-ruby/library.rb b/lib/plex-ruby/library.rb index e21d33c..e6a52c5 100644 --- a/lib/plex-ruby/library.rb +++ b/lib/plex-ruby/library.rb @@ -3,6 +3,9 @@ class Library attr_reader :server + WATCHED_LINK = "/:/scrobble?identifier=com.plexapp.plugins.library&key=" + UNWATCHED_LINK = "/:/unscrobble?identifier=com.plexapp.plugins.library&key=" + # @param [Server] server this libary belongs to def initialize(server) @server = server @@ -33,6 +36,20 @@ def sections! @sections = search_sections(xml_doc!) end + # Set the video as watched + # + # @param [Video] video to be set as watched + def watched(video) + open(url+WATCHED_LINK+video.rating_key) + end + + # Set the video as unwatched + # + # @param [Video] video to be set as unwatched + def unwatched(video) + open(url+UNWATCHED_LINK+video.rating_key) + end + # @private def key #:nodoc: "/library/sections" diff --git a/lib/plex-ruby/parser.rb b/lib/plex-ruby/parser.rb index 21dd61d..eea0244 100644 --- a/lib/plex-ruby/parser.rb +++ b/lib/plex-ruby/parser.rb @@ -63,6 +63,10 @@ def parse_directory case node.attr('type') when 'show' Plex::Show.new( parent, node.attr('key')[0..-10] ) # Remove /children + when 'artist' + Plex::Artist.new( parent, node.attr('key')[0..-10] ) + when 'album' + Plex::Album.new( parent, node.attr('key')[0..-10] ) else raise "Unsupported Directory type #{node.attr('type')}" end diff --git a/lib/plex-ruby/player.rb b/lib/plex-ruby/player.rb new file mode 100644 index 0000000..aa68f1a --- /dev/null +++ b/lib/plex-ruby/player.rb @@ -0,0 +1,27 @@ +module Plex + class Player + + ATTRIBUTES = %w(machineIdentifier platform product state title) + + attr_reader :parts + + # @param [Nokogiri::XML::Element] nokogiri element that represents this + # Media + def initialize(node) + node.attributes.each do |method, val| + define_singleton_method(Plex.underscore(method).to_sym) do + val.value + end + end + end + + def ==(other) + if other.is_a? Media + id == other.id + else + super + end + end + + end +end diff --git a/lib/plex-ruby/section.rb b/lib/plex-ruby/section.rb index 772d1eb..b97bf17 100644 --- a/lib/plex-ruby/section.rb +++ b/lib/plex-ruby/section.rb @@ -20,8 +20,8 @@ def initialize(library, node) } end - # NOT IMPLEMENTED - def refresh(deep = false, force = false) + def refresh + Plex.open(url + key + '/refresh') end @@ -35,13 +35,14 @@ def refresh(deep = false, force = false) # on_deck - videos that are "on deck" in this Section # # @return [Array] list of Shows or Movies in that group - GROUPS.each { |method| - class_eval %( - def #{Plex.underscore(method)} - Plex::Parser.new( self, Nokogiri::XML(Plex.open(url+key+'/#{method}')) ).parse - end - ) - } + GROUPS.each do |method| + define_method(Plex.underscore(method).to_sym) do |options = {}| + path = '/' + method + '?' + path += "title=#{CGI::escape(options[:title])}" if options[:title] + path += "type=4" if options[:episodes] + Plex::Parser.new( self, Nokogiri::XML(Plex.open(url+key+path )) ).parse + end + end # Find TV Shows / Episodes by categories # diff --git a/lib/plex-ruby/server.rb b/lib/plex-ruby/server.rb index 29c2243..cea0b90 100644 --- a/lib/plex-ruby/server.rb +++ b/lib/plex-ruby/server.rb @@ -14,6 +14,13 @@ def initialize(host, port) def library @library ||= Plex::Library.new(self) end + + # The current status of this server + # + # @return [Status] this Servers status + def status + @status ||= Plex::Status.new(self) + end # The Plex clients that are connected to this Server # @@ -29,7 +36,7 @@ def clients! # @private def url #:nodoc: - "http://#{host}:#{port}" + "https://#{host}:#{port}" end # @private diff --git a/lib/plex-ruby/status.rb b/lib/plex-ruby/status.rb new file mode 100644 index 0000000..7f7a408 --- /dev/null +++ b/lib/plex-ruby/status.rb @@ -0,0 +1,81 @@ +module Plex + class Status + + attr_reader :server + + # @param [Server] server this libary belongs to + def initialize(server) + @server = server + end + + # Grab a specific session + # + # @param [String, Fixnum] key of the session we want + # @return [Video] session with that key + def session(id) + search_sessions(xml_doc, id).first + end + + # Cache busting version of #session + def session!(id) + search_sessions(xml_doc!, id).first + end + + # A list of sessions that are located in this library + # + # @return [Array] list of videos + def sessions + @sessions ||= search_sessions(xml_doc) + end + + # Cache busting version of #sessions + def sessions! + @sessions = search_sessions(xml_doc!) + end + + # @private + def key #:nodoc: + "/status/sessions" + end + + # @private + def url #:nodoc: + server.url + end + + # @private + def ==(other) #:nodoc: + if other.is_a? Library + server == other.server + else + super + end + end + + # @private + def inspect #:nodoc: + "#" + end + + private + + def search_sessions(doc, key = nil) + term = key ? "Video[@sessionKey='#{key}']" : 'Video' + doc.search(term).map { |m| Plex::Video.new(m) } + end + + def xml_doc + @xml_doc ||= base_doc + end + + def xml_doc! + @xml_doc = base_doc + end + + def base_doc + Nokogiri::XML( Plex.open(url+key) ) + end + + + end +end diff --git a/lib/plex-ruby/track.rb b/lib/plex-ruby/track.rb new file mode 100644 index 0000000..b03263b --- /dev/null +++ b/lib/plex-ruby/track.rb @@ -0,0 +1,72 @@ +module Plex + class Track + + ATTRIBUTES = %w(ratingKey title originalTitle summary index duration thumb year addedAt updatedAt) + + attr_reader :section, :key, :attribute_hash + attr_reader :medias + + # @param [Album] Album this Track belongs to + # @param [String] key to use to later grab this Track + # @param [Nokogiri::XML::Element] Media node(s) + def initialize(section, key, medias = nil) + @section = section + @key = key + @attribute_hash = {} + + track.attributes.each do |method, val| + @attribute_hash[Plex.underscore(method)] = val.value + define_singleton_method(Plex.underscore(method).to_sym) do + val.value + end + end + + @attribute_hash.merge({'key' => @key}) + + @medias = medias.map { |m| Plex::Media.new(m) } + end + + # @private + def url #:nodoc: + section.url + end + + # @private + def ==(other) #:nodoc: + if other.is_a? Plex::Track + key == other.key + else + super + end + end + + # @private + def inspect #:nodoc: + "#" + end + + private + + def base_doc + Nokogiri::XML( open(url+key) ) + end + + def children_base + Nokogiri::XML( open(url+key+'/children') ) + end + + def xml_doc + @xml_doc ||= base_doc + end + + def children + @children ||= children_base + end + + def track + @track ||= xml_doc.search('Track').first + end + + end + +end diff --git a/lib/plex-ruby/version.rb b/lib/plex-ruby/version.rb index a95ac9e..9d95496 100644 --- a/lib/plex-ruby/version.rb +++ b/lib/plex-ruby/version.rb @@ -1,3 +1,3 @@ module Plex - VERSION = "1.5.1" + VERSION = "1.5.3" end diff --git a/lib/plex-ruby/video.rb b/lib/plex-ruby/video.rb index 79d896e..528a1e2 100644 --- a/lib/plex-ruby/video.rb +++ b/lib/plex-ruby/video.rb @@ -5,7 +5,7 @@ class Video rating viewCount year tagline thumb art duration originallyAvailableAt updatedAt) - attr_reader :medias, :genres, :writers, :directors, :roles, :attribute_hash + attr_reader :medias, :genres, :writers, :directors, :roles, :players, :attribute_hash # @param [Nokogiri::XML::Element] nokogiri element that represents this # Video @@ -23,6 +23,7 @@ def initialize(node) @writers = node.search('Writer').map { |m| Plex::Writer.new(m) } @directors = node.search('Director').map { |m| Plex::Director.new(m) } @roles = node.search('Role').map { |m| Plex::Role.new(m) } + @players = node.search('Player').map { |m| Plex::Player.new(m) } end diff --git a/test/test_client.rb b/test/test_client.rb index 41ccf95..659d3be 100644 --- a/test/test_client.rb +++ b/test/test_client.rb @@ -5,7 +5,7 @@ before do @server = FakeParent.new @client = Plex::Client.new(@server, FakeNode.new(FAKE_CLIENT_NODE_HASH)) - FakeWeb.register_uri(:get, %r|http://localhost:32400|, :body => "") + FakeWeb.register_uri(:get, %r|https://localhost:32400|, :body => "") end after do diff --git a/test/test_helper.rb b/test/test_helper.rb index dcb7a62..bca534f 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -39,7 +39,7 @@ def search(value) class FakeParent def url - 'http://localhost:32400' + 'https://localhost:32400' end end diff --git a/test/test_plex.rb b/test/test_plex.rb index 9c8a38f..fac008e 100644 --- a/test/test_plex.rb +++ b/test/test_plex.rb @@ -9,7 +9,7 @@ end before do - FakeWeb.register_uri(:get, "http://localhost:32400", :body => "") + FakeWeb.register_uri(:get, "https://localhost:32400", :body => "") end after do @@ -20,7 +20,7 @@ it "has an open function which respects the configuration" do Plex.configure {|config| config.auth_token = "ABCD" } - Plex.open("http://localhost:32400").read + Plex.open("https://localhost:32400").read FakeWeb.last_request["X-Plex-Token"].must_equal "ABCD" end end diff --git a/test/test_server.rb b/test/test_server.rb index 4a643ae..3ce3994 100644 --- a/test/test_server.rb +++ b/test/test_server.rb @@ -14,7 +14,7 @@ end it "properly formats its url" do - @server.url.must_equal "http://localhost:3000" + @server.url.must_equal "https://localhost:3000" end it "has a libary" do