From d2cb07cb4e4a2a550bbc7ff87b79ba655e564512 Mon Sep 17 00:00:00 2001 From: Gregory Eremin Date: Sun, 26 Aug 2012 08:09:00 +0400 Subject: [PATCH] Robbie comes in --- .gitignore | 1 + Gemfile | 3 ++ Gemfile.lock | 22 ++++++++++ LICENSE | 22 ++++++++++ README.md | 71 +++++++++++++++++++++++++++++++ Rakefile | 2 + lib/robbie.rb | 39 +++++++++++++++++ lib/robbie/autocomplete.rb | 13 ++++++ lib/robbie/models/album.rb | 15 +++++++ lib/robbie/models/artist.rb | 23 ++++++++++ lib/robbie/models/base_model.rb | 9 ++++ lib/robbie/models/genre.rb | 5 +++ lib/robbie/models/track.rb | 5 +++ lib/robbie/parsers/album.rb | 48 +++++++++++++++++++++ lib/robbie/parsers/artist.rb | 40 +++++++++++++++++ lib/robbie/parsers/base_parser.rb | 41 ++++++++++++++++++ lib/robbie/parsers/search.rb | 45 ++++++++++++++++++++ lib/robbie/parsers/track.rb | 34 +++++++++++++++ lib/robbie/version.rb | 3 ++ robbie.gemspec | 21 +++++++++ 20 files changed, 462 insertions(+) create mode 100644 .gitignore create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE create mode 100644 README.md create mode 100644 Rakefile create mode 100644 lib/robbie.rb create mode 100644 lib/robbie/autocomplete.rb create mode 100644 lib/robbie/models/album.rb create mode 100644 lib/robbie/models/artist.rb create mode 100644 lib/robbie/models/base_model.rb create mode 100644 lib/robbie/models/genre.rb create mode 100644 lib/robbie/models/track.rb create mode 100644 lib/robbie/parsers/album.rb create mode 100644 lib/robbie/parsers/artist.rb create mode 100644 lib/robbie/parsers/base_parser.rb create mode 100644 lib/robbie/parsers/search.rb create mode 100644 lib/robbie/parsers/track.rb create mode 100644 lib/robbie/version.rb create mode 100644 robbie.gemspec diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a9a5aec --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +tmp diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..a1b93f3 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source :rubygems + +gemspec diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..d8b3d67 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,22 @@ +PATH + remote: . + specs: + robbie (0.0.1) + httparty + oj + +GEM + remote: http://rubygems.org/ + specs: + httparty (0.8.3) + multi_json (~> 1.0) + multi_xml + multi_json (1.3.6) + multi_xml (0.5.1) + oj (1.3.4) + +PLATFORMS + ruby + +DEPENDENCIES + robbie! diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bdb0e55 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2012 Gregory Eremin + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f0a8c0e --- /dev/null +++ b/README.md @@ -0,0 +1,71 @@ +# Robbie + +Rovi API wrapper + +## Installation + +Add this line to your application's Gemfile: +```ruby +gem "robbie" +``` + +And then execute: +```bash +$ bundle +``` + +Or install it yourself as: +```bash +$ gem install robbie +``` + +## Usage + +*Working with models* +```ruby +require "robbie" +Robbie.setup(api_key: "your api key", api_secret: "your api secret") + +foo = Robbie::Artist.find_by_name("foo fighters") +# ]> + +# ...or directly by id +foo = Robbie::Artist.find_by_name("MA0000002613") + +foo.albums.last +# + +# ...or directly by id +Robbie::Album.find("MW0002115022") + +foo.albums.last.tracks.first +# ], @title="Bridge Burning", @duration=286> + +# ...or directly by id +Robbie::Track.find("MT0041016087") +``` + +Autocomplete and extended autocomplete +```ruby +Robbie::Autocomplete.complete("b") +# ["Beyoncé", "Bruno Mars", "Bad Meets Evil", "Bon Iver", "Bob Marley", "Big Sean", "The Band Perry", "Blake Shelton", "B.O.B", "The Black Eyed Peas"] + +Robbie::Autocomplete.predict("b").first +# ]> +``` + +*You can turn response caching* +```ruby +# on +Robbie.enable_cache +# and off +Robbie.disable_cache +``` + +## Contributing + +1. Fork it +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Added some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..f57ae68 --- /dev/null +++ b/Rakefile @@ -0,0 +1,2 @@ +#!/usr/bin/env rake +require "bundler/gem_tasks" diff --git a/lib/robbie.rb b/lib/robbie.rb new file mode 100644 index 0000000..849e621 --- /dev/null +++ b/lib/robbie.rb @@ -0,0 +1,39 @@ +require "digest/md5" +require "httparty" +require "oj" +require "multi_json" + +require "robbie/version" +require "robbie/parsers/base_parser" +require "robbie/parsers/search" +require "robbie/parsers/artist" +require "robbie/parsers/album" +require "robbie/parsers/track" +require "robbie/models/base_model" +require "robbie/models/artist" +require "robbie/models/album" +require "robbie/models/track" +require "robbie/models/genre" +require "robbie/autocomplete" + +module Robbie + @@cache_enabled = true + class << self + def setup(params) + const_set(:API_KEY, params[:api_key]) + const_set(:API_SECRET, params[:api_secret]) + end + + def enable_cache + @@cache_enabled = true + end + + def disable_cache + @@cache_enabled = false + end + + def cache_enabled? + @@cache_enabled + end + end +end diff --git a/lib/robbie/autocomplete.rb b/lib/robbie/autocomplete.rb new file mode 100644 index 0000000..bbaa817 --- /dev/null +++ b/lib/robbie/autocomplete.rb @@ -0,0 +1,13 @@ +module Robbie + class Autocomplete + class << self + def complete(q) + Parsers::Search.autocomplete(q) + end + + def predict(q) + Parsers::Search.single_stage_search(q) + end + end + end +end diff --git a/lib/robbie/models/album.rb b/lib/robbie/models/album.rb new file mode 100644 index 0000000..74b2985 --- /dev/null +++ b/lib/robbie/models/album.rb @@ -0,0 +1,15 @@ +module Robbie + class Album < BaseModel + attr_accessor :id, :title, :year, :tracks + + class << self + def search(q) + Parsers::Search.search(q).keep_if { |item| item.instance_of?(Robbie::Album) } + end + end + + def tracks + @tracks ||= Parsers::Album.find(id).tracks + end + end +end diff --git a/lib/robbie/models/artist.rb b/lib/robbie/models/artist.rb new file mode 100644 index 0000000..c384dc0 --- /dev/null +++ b/lib/robbie/models/artist.rb @@ -0,0 +1,23 @@ +module Robbie + class Artist < BaseModel + attr_accessor :id, :name, :is_group, :genres, :albums + + class << self + def search(q) + Parsers::Search.search(q).keep_if { |item| item.instance_of?(Robbie::Artist) } + end + + def find_by_name(name) + search(name).first + end + + def find(id) + Parsers::Artist.find(id) + end + end + + def albums + @albums ||= Parsers::Artist.find(id).albums + end + end +end diff --git a/lib/robbie/models/base_model.rb b/lib/robbie/models/base_model.rb new file mode 100644 index 0000000..fc45d0b --- /dev/null +++ b/lib/robbie/models/base_model.rb @@ -0,0 +1,9 @@ +module Robbie + class BaseModel + def initialize(values) + values.each do |key, val| + send(:"#{key}=", val) + end + end + end +end diff --git a/lib/robbie/models/genre.rb b/lib/robbie/models/genre.rb new file mode 100644 index 0000000..b5114ed --- /dev/null +++ b/lib/robbie/models/genre.rb @@ -0,0 +1,5 @@ +module Robbie + class Genre < BaseModel + attr_accessor :id, :name + end +end diff --git a/lib/robbie/models/track.rb b/lib/robbie/models/track.rb new file mode 100644 index 0000000..dba5391 --- /dev/null +++ b/lib/robbie/models/track.rb @@ -0,0 +1,5 @@ +module Robbie + class Track < BaseModel + attr_accessor :id, :disc_id, :position, :artists, :title, :duration + end +end diff --git a/lib/robbie/parsers/album.rb b/lib/robbie/parsers/album.rb new file mode 100644 index 0000000..674c530 --- /dev/null +++ b/lib/robbie/parsers/album.rb @@ -0,0 +1,48 @@ +module Robbie + module Parsers + class Album < BaseParser + class << self + def find(album_id) + response = query("/data/v1/album/info", { + albumid: album_id, + include: "tracks" + }) + parse(response["album"]) + end + + def parse(data) + return if data.nil? + + album = parse_meta(data) + if data["tracks"].is_a?(Array) + current_disc = 0 + position = 0 + album.tracks = data["tracks"].map do |track| + if track["disc"] && track["disc"] != current_disc + position = 0 + current_disc = track["disc"] + end + position += 1 + + Parsers::Track.parse_meta(track, current_disc, position) + end + end + album + end + + def parse_meta(data) + return if data.nil? + + params = {} + params[:id] = data["ids"]["albumId"] if data["ids"] + params[:title] = data["title"] + if data["album"] && data["originalReleaseDate"].is_a?(String) + params[:year] = data["originalReleaseDate"].split("-").first + end + + Robbie::Album.new(params) + end + end + end + end +end diff --git a/lib/robbie/parsers/artist.rb b/lib/robbie/parsers/artist.rb new file mode 100644 index 0000000..8714454 --- /dev/null +++ b/lib/robbie/parsers/artist.rb @@ -0,0 +1,40 @@ +module Robbie + module Parsers + class Artist < BaseParser + class << self + def find(artist_id) + response = query("/data/v1/name/info", { + nameid: artist_id, + include: "discography", + type: "main" + }) + parse(response["name"]) + end + + def parse(data) + return if data.nil? + + artist = parse_meta(data) + if data["discography"] && data["discography"].is_a?(Array) + artist.albums = data["discography"].map{ |album| Parsers::Album.parse_meta(album) }.reverse + end + artist + end + + def parse_meta(data) + return if data.nil? + + params = {} + params[:id] = data["ids"]["nameId"] if data["ids"] + params[:name] = data["name"] + params[:is_group] = data["isGroup"] + params[:genres] = data["musicGenres"].map{ |genre| + Robbie::Genre.new(id: genre["id"], name: genre["name"]) + } if data["musicGenres"].is_a?(Array) + + Robbie::Artist.new(params) + end + end + end + end +end diff --git a/lib/robbie/parsers/base_parser.rb b/lib/robbie/parsers/base_parser.rb new file mode 100644 index 0000000..28e17ba --- /dev/null +++ b/lib/robbie/parsers/base_parser.rb @@ -0,0 +1,41 @@ +module Robbie + module Parsers + class BaseParser + include HTTParty + base_uri "api.rovicorp.com" + format :json + + class << self + def sig + Digest::MD5.hexdigest("#{API_KEY}#{API_SECRET}#{Time.now.to_i}") + end + + def query(path, params) + unless Robbie.const_defined?(:API_KEY) and Robbie.const_defined?(:API_SECRET) + raise Exception.new("No API credentials given") + end + + params_str = params + .merge({ apikey: API_KEY, sig: sig, format: "json" }) + .map{ |key, val| "#{key}=#{val}" }.join("&") + + if Robbie.cache_enabled? + cache_key = "#{path}?#{params.inspect}".scan(/\w/).join + cache_file = File.expand_path("../../../../tmp/cache/#{cache_key}", __FILE__) + if File.exist?(cache_file) + MultiJson.load(File.open(cache_file).read) + else + data = get("#{path}?#{params_str}") + File.open(cache_file, "w") do |file| + file.write(MultiJson.dump(data)) unless data.nil? or data.empty? + end + data + end + else + get("#{path}?#{params_str}") + end + end + end + end + end +end diff --git a/lib/robbie/parsers/search.rb b/lib/robbie/parsers/search.rb new file mode 100644 index 0000000..9c2a363 --- /dev/null +++ b/lib/robbie/parsers/search.rb @@ -0,0 +1,45 @@ +module Robbie + module Parsers + class Search < BaseParser + class << self + def search(q) + response = query("/search/v2.1/music/search", { + query: q.gsub(" ", "%20"), + entitytype: "artist&entitytype=album" + }) + parse(response["searchResponse"]["results"]) + end + + def single_stage_search(q) + response = query("/search/v2.1/music/singlestagesearch", { + query: q.gsub(" ", "%20"), + entitytype: "artist&entitytype=album", + size: 10 + }) + parse(response["searchResponse"]["results"]) + end + + def parse(data) + return if data.nil? + + data.map do |result| + if result["type"] == "artist" + Parsers::Artist.parse_meta(result["name"]) + elsif result["type"] == "album" + Parsers::Album.parse_meta(result["album"]) + end + end + end + + def autocomplete(q) + response = query("/search/v2.1/music/autocomplete", { + query: q.gsub(" ", "%20"), + entitytype: "artist&entitytype=album", + size: 10 + }) + response["autocompleteResponse"]["results"] + end + end + end + end +end diff --git a/lib/robbie/parsers/track.rb b/lib/robbie/parsers/track.rb new file mode 100644 index 0000000..168e471 --- /dev/null +++ b/lib/robbie/parsers/track.rb @@ -0,0 +1,34 @@ +module Robbie + module Parsers + class Track < BaseParser + class << self + def find(track_id) + response = query("/data/v1/song/info", { + trackid: track_id + }) + + parse_meta(response["song"]) + end + + def parse_meta(data, disc = nil, position = nil) + return if data.nil? + + params = {} + params[:id] = data["ids"]["trackId"] if data["ids"] + params[:disc_id] = disc + params[:position] = position + params[:artists] = data["performers"].map{ |performer| + Robbie::Artist.new(id: performer["id"], name: performer["name"]) + } if data["performers"].is_a?(Array) + params[:artists] = data["primaryArtists"].map{ |artist| + Robbie::Artist.new(id: artist["id"], name: artist["name"]) + } if data["primaryArtists"].is_a?(Array) + params[:title] = data["title"] + params[:duration] = data["duration"] + + Robbie::Track.new(params) + end + end + end + end +end diff --git a/lib/robbie/version.rb b/lib/robbie/version.rb new file mode 100644 index 0000000..606e124 --- /dev/null +++ b/lib/robbie/version.rb @@ -0,0 +1,3 @@ +module Robbie + VERSION = "0.0.1" +end diff --git a/robbie.gemspec b/robbie.gemspec new file mode 100644 index 0000000..a4b7390 --- /dev/null +++ b/robbie.gemspec @@ -0,0 +1,21 @@ +# encoding: UTF-8 +require File.expand_path('../lib/robbie/version', __FILE__) + +Gem::Specification.new do |gem| + gem.authors = ["Gregory Eremin"] + gem.email = %w[ magnolia_fan@me.com ] + gem.description = %q{ Rovi API wrapper } + gem.summary = %q{ Rovi API wrapper } + gem.homepage = "https://github.com/magnolia-fan/robbie" + + gem.files = %x{ git ls-files }.split($\) + gem.executables = gem.files.grep(%r{^bin/}).map{ |file| File.basename(file) } + gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) + gem.name = "robbie" + gem.require_paths = %w[ lib ] + gem.version = Robbie::VERSION + gem.platform = Gem::Platform::RUBY + + gem.add_dependency "httparty" + gem.add_dependency "oj" +end