shin 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.textile +23 -0
- data/lib/shin/base.rb +16 -0
- data/lib/shin/httparty_icebox.rb +259 -0
- data/lib/shin/play/svtplay.rb +152 -0
- data/lib/shin/play/viki.rb +104 -0
- data/lib/shin/play.rb +29 -0
- data/lib/shin/reviews/kritiker.rb +33 -0
- data/lib/shin/reviews/moviezine.rb +32 -0
- data/lib/shin/reviews/russin.rb +46 -0
- data/lib/shin/reviews.rb +26 -0
- data/lib/shin/version.rb +3 -0
- data/lib/shin.rb +43 -0
- data/shin.gemspec +29 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f2ef70e3ad1839ae50cc03c83431bd39b8cfa8a1
|
4
|
+
data.tar.gz: d524891b2e7af3b26a0d615b87066e0b0e4d715b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 644597661ad4e7af9ced7c8e1cad51517ad853add7b3be3e98a273384c20006cf1c55f404d7434a7eaf211bd022396abc7aa6eabebcd4b29ed59269aaf88f8fd
|
7
|
+
data.tar.gz: 5e3b98bb07b9c513c350db0451633ac6ea5f30e4102247ca6f5b9d9672bbc520bbcf20a1dd482ca8ca5ffa434b0dee6f3107aeccec7f289936a2a80b0837bc7f
|
data/README.textile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
h1. Shin Hye
|
2
|
+
|
3
|
+
h2. What?
|
4
|
+
|
5
|
+
Shin Hye is a Ruby library to talk to several different websites and data providers. The objective of the library is mostly just for the use of EPG.io.
|
6
|
+
|
7
|
+
h2. Why?
|
8
|
+
|
9
|
+
We use the data from several providers in EPG.io and we needed a library to provide this rather than using a file in lib/.
|
10
|
+
|
11
|
+
h2. How?
|
12
|
+
|
13
|
+
Set up the client
|
14
|
+
<code>shin = Shin.new</code>
|
15
|
+
|
16
|
+
Sometimes we need to provide an API for different cases such as review sites.
|
17
|
+
To do this use:
|
18
|
+
<code>shin = Shin.new moviezine: "a3ba5fd96ab238d8788c53683e7b72aa"</code>
|
19
|
+
|
20
|
+
Then you can do:
|
21
|
+
<code>shin.reviews.moviezine.find imdb: 'tt4201628'</code>
|
22
|
+
|
23
|
+
And it will return the review for "Pinocchio (2014 TV Series)":http://www.imdb.com/title/tt4201628/:
|
data/lib/shin/base.rb
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
# = Icebox : Caching for HTTParty
|
2
|
+
#
|
3
|
+
# Cache responses in HTTParty models [http://github.com/jnunemaker/httparty]
|
4
|
+
#
|
5
|
+
# === Usage
|
6
|
+
#
|
7
|
+
# class Foo
|
8
|
+
# include HTTParty
|
9
|
+
# include HTTParty::Icebox
|
10
|
+
# cache :store => 'file', :timeout => 600, :location => MY_APP_ROOT.join('tmp', 'cache')
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# Modeled after Martyn Loughran's APICache [http://github.com/newbamboo/api_cache]
|
14
|
+
# and Ruby On Rails's caching [http://api.rubyonrails.org/classes/ActiveSupport/Cache.html]
|
15
|
+
#
|
16
|
+
# Author: Karel Minarik [www.karmi.cz]
|
17
|
+
#
|
18
|
+
# === Notes
|
19
|
+
#
|
20
|
+
# Thanks to Amit Chakradeo to point out objects have to be stored marhalled on FS
|
21
|
+
# Thanks to Marlin Forbes to point out query parameters have to be include in the cache key
|
22
|
+
#
|
23
|
+
#
|
24
|
+
|
25
|
+
require 'logger'
|
26
|
+
require 'fileutils'
|
27
|
+
require 'tmpdir'
|
28
|
+
require 'pathname'
|
29
|
+
require 'digest/md5'
|
30
|
+
|
31
|
+
module HTTParty #:nodoc:
|
32
|
+
module Icebox
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
|
36
|
+
# Enable caching and set cache options
|
37
|
+
# Returns memoized cache object
|
38
|
+
#
|
39
|
+
# Following options are available, default values are in []:
|
40
|
+
#
|
41
|
+
# +store+:: Storage mechanism for cached data (memory, filesystem, your own) [memory]
|
42
|
+
# +timeout+:: Cache expiration in seconds [60]
|
43
|
+
# +logger+:: Path to logfile or logger instance [STDOUT]
|
44
|
+
#
|
45
|
+
# Any additional options are passed to the Cache constructor
|
46
|
+
#
|
47
|
+
# Usage:
|
48
|
+
#
|
49
|
+
# # Enable caching in HTTParty, in memory, for 1 minute
|
50
|
+
# cache # Use default values
|
51
|
+
#
|
52
|
+
# # Enable caching in HTTParty, on filesystem (/tmp), for 10 minutes
|
53
|
+
# cache :store => 'file', :timeout => 600, :location => '/tmp/'
|
54
|
+
#
|
55
|
+
# # Use your own cache store (see AbstractStore class below)
|
56
|
+
# cache :store => 'memcached', :timeout => 600, :server => '192.168.1.1:1001'
|
57
|
+
#
|
58
|
+
def cache(options={})
|
59
|
+
options[:store] ||= 'memory'
|
60
|
+
options[:timeout] ||= 60
|
61
|
+
logger = options[:logger]
|
62
|
+
@cache ||= Cache.new( options.delete(:store), options )
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# When included, extend class with +cache+ method
|
68
|
+
# and redefine +get+ method to use cache
|
69
|
+
#
|
70
|
+
def self.included(receiver) #:nodoc:
|
71
|
+
receiver.extend ClassMethods
|
72
|
+
receiver.class_eval do
|
73
|
+
|
74
|
+
# Get reponse from network
|
75
|
+
# TODO: Why alias :new :old is not working here? Returns NoMethodError
|
76
|
+
#
|
77
|
+
def self.get_without_caching(path, options={})
|
78
|
+
perform_request Net::HTTP::Get, path, options
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get response from cache, if available
|
82
|
+
#
|
83
|
+
def self.get_with_caching(path, options={})
|
84
|
+
key = path.clone
|
85
|
+
key << options[:query].to_s if defined? options[:query]
|
86
|
+
|
87
|
+
if cache.exists?(key) and not cache.stale?(key)
|
88
|
+
Cache.logger.debug "CACHE -- GET #{path}#{options[:query]}"
|
89
|
+
return cache.get(key)
|
90
|
+
else
|
91
|
+
Cache.logger.debug "/!\\ NETWORK -- GET #{path}#{options[:query]}"
|
92
|
+
response = get_without_caching(path, options)
|
93
|
+
cache.set(key, response) if response.code == 200
|
94
|
+
return response
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Redefine original HTTParty +get+ method to use cache
|
99
|
+
#
|
100
|
+
def self.get(path, options={})
|
101
|
+
self.get_with_caching(path, options)
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# === Cache container
|
108
|
+
#
|
109
|
+
# Pass a store name ('memory', etc) to initializer
|
110
|
+
#
|
111
|
+
class Cache
|
112
|
+
attr_accessor :store
|
113
|
+
|
114
|
+
def initialize(store, options={})
|
115
|
+
self.class.logger = options[:logger]
|
116
|
+
@store = self.class.lookup_store(store).new(options)
|
117
|
+
end
|
118
|
+
|
119
|
+
def get(key); @store.get encode(key) unless stale?(key); end
|
120
|
+
def set(key, value); @store.set encode(key), value; end
|
121
|
+
def exists?(key); @store.exists? encode(key); end
|
122
|
+
def stale?(key); @store.stale? encode(key); end
|
123
|
+
|
124
|
+
def self.logger; @logger || default_logger; end
|
125
|
+
def self.default_logger; logger = ::Logger.new(STDERR); end
|
126
|
+
|
127
|
+
# Pass a filename (String), IO object, Logger instance or +nil+ to silence the logger
|
128
|
+
def self.logger=(device); @logger = device.kind_of?(::Logger) ? device : ::Logger.new(device); end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
# Return store class based on passed name
|
133
|
+
def self.lookup_store(name)
|
134
|
+
store_name = "#{name.capitalize}Store"
|
135
|
+
return Store::const_get(store_name)
|
136
|
+
rescue NameError => e
|
137
|
+
raise Store::StoreNotFound, "The cache store '#{store_name}' was not found. Did you loaded any such class?"
|
138
|
+
end
|
139
|
+
|
140
|
+
def encode(key); Digest::MD5.hexdigest(key); end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
# === Cache stores
|
145
|
+
#
|
146
|
+
module Store
|
147
|
+
|
148
|
+
class StoreNotFound < StandardError; end #:nodoc:
|
149
|
+
|
150
|
+
# ==== Abstract Store
|
151
|
+
# Inherit your store from this class
|
152
|
+
# *IMPORTANT*: Do not forget to call +super+ in your +initialize+ method!
|
153
|
+
#
|
154
|
+
class AbstractStore
|
155
|
+
def initialize(options={})
|
156
|
+
raise ArgumentError, "You need to set the :timeout parameter" unless options[:timeout]
|
157
|
+
@timeout = options[:timeout]
|
158
|
+
message = "Cache: Using #{self.class.to_s.split('::').last}"
|
159
|
+
message << " in location: #{options[:location]}" if options[:location]
|
160
|
+
message << " with timeout #{options[:timeout]} sec"
|
161
|
+
Cache.logger.info message unless options[:logger].nil?
|
162
|
+
return self
|
163
|
+
end
|
164
|
+
%w{set get exists? stale?}.each do |method_name|
|
165
|
+
define_method(method_name) { raise NoMethodError, "Please implement method set in your store class" }
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# ===== Store objects in memory
|
170
|
+
#
|
171
|
+
Struct.new("TvdbResponse", :code, :body, :headers) { def to_s; self.body; end }
|
172
|
+
class MemoryStore < AbstractStore
|
173
|
+
def initialize(options={})
|
174
|
+
super; @store = {}; self
|
175
|
+
end
|
176
|
+
def set(key, value)
|
177
|
+
Cache.logger.info("Cache: set (#{key})")
|
178
|
+
@store[key] = [Time.now, value]; true
|
179
|
+
end
|
180
|
+
def get(key)
|
181
|
+
data = @store[key][1]
|
182
|
+
Cache.logger.info("Cache: #{data.nil? ? "miss" : "hit"} (#{key})")
|
183
|
+
data
|
184
|
+
end
|
185
|
+
def exists?(key)
|
186
|
+
!@store[key].nil?
|
187
|
+
end
|
188
|
+
def stale?(key)
|
189
|
+
return true unless exists?(key)
|
190
|
+
Time.now - created(key) > @timeout
|
191
|
+
end
|
192
|
+
private
|
193
|
+
def created(key)
|
194
|
+
@store[key][0]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# ===== Store objects on the filesystem
|
199
|
+
#
|
200
|
+
class FileStore < AbstractStore
|
201
|
+
def initialize(options={})
|
202
|
+
super
|
203
|
+
options[:location] ||= Dir::tmpdir
|
204
|
+
@path = Pathname.new( options[:location] )
|
205
|
+
FileUtils.mkdir_p( @path )
|
206
|
+
self
|
207
|
+
end
|
208
|
+
def set(key, value)
|
209
|
+
Cache.logger.info("Cache: set (#{key})")
|
210
|
+
File.open( @path.join(key), 'w' ) { |file| Marshal.dump(value, file) }
|
211
|
+
true
|
212
|
+
end
|
213
|
+
def get(key)
|
214
|
+
data = Marshal.load(File.new(@path.join(key)))
|
215
|
+
Cache.logger.info("Cache: #{data.nil? ? "miss" : "hit"} (#{key})")
|
216
|
+
data
|
217
|
+
end
|
218
|
+
def exists?(key)
|
219
|
+
File.exists?( @path.join(key) )
|
220
|
+
end
|
221
|
+
def stale?(key)
|
222
|
+
return true unless exists?(key)
|
223
|
+
Time.now - created(key) > @timeout
|
224
|
+
end
|
225
|
+
private
|
226
|
+
def created(key)
|
227
|
+
File.mtime( @path.join(key) )
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
# Major parts of this code are based on architecture of ApiCache.
|
237
|
+
# Copyright (c) 2008 Martyn Loughran
|
238
|
+
#
|
239
|
+
# Other parts are inspired by the ActiveSupport::Cache in Ruby On Rails.
|
240
|
+
# Copyright (c) 2005-2009 David Heinemeier Hansson
|
241
|
+
#
|
242
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
243
|
+
# a copy of this software and associated documentation files (the
|
244
|
+
# "Software"), to deal in the Software without restriction, including
|
245
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
246
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
247
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
248
|
+
# the following conditions:
|
249
|
+
#
|
250
|
+
# The above copyright notice and this permission notice shall be
|
251
|
+
# included in all copies or substantial portions of the Software.
|
252
|
+
#
|
253
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
254
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
255
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
256
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
257
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
258
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
259
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,152 @@
|
|
1
|
+
## SVT Play doesn't have an open API so let's parse their HTML
|
2
|
+
require 'date'
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module Shin
|
6
|
+
module Play
|
7
|
+
class Svtplay
|
8
|
+
|
9
|
+
def new
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get episodes for a slug
|
14
|
+
def episodes(params={})
|
15
|
+
# Response
|
16
|
+
response = Base.get('http://www.svtplay.se/' + params[:slug].to_s + '/hela-program?' + URI.encode_www_form(params))
|
17
|
+
raise HTTPError, "The response didn't have a 200 HTTP Code. It had #{response.code}." unless response.code == 200
|
18
|
+
|
19
|
+
# Nokogiri parse
|
20
|
+
@main_noko = Nokogiri::HTML response.body rescue nil
|
21
|
+
|
22
|
+
# Can't be nil
|
23
|
+
if @main_noko != nil
|
24
|
+
# Title
|
25
|
+
@title = @main_noko.css('div.play_gridpage__header-wrapper > h1 > a.play_link.play_link--discreet').text.strip rescue nil
|
26
|
+
@next_page = @main_noko.css('div.play_gridpage__pagination').css('a')[0]['href'][/\?sida\=(\d+)/, 1].to_i rescue nil
|
27
|
+
|
28
|
+
# Data
|
29
|
+
@array = {next_page: @next_page, results: []}
|
30
|
+
|
31
|
+
# Multiple episodes
|
32
|
+
@main_noko.css('div#gridpage-content > article').map do |e|
|
33
|
+
@video_id = e.css('a')[0]['href'][/\/video\/(\d+)\//, 1].to_i rescue nil
|
34
|
+
@url = "http://www.svtplay.se" + e.css('a')[0]['href'] rescue nil
|
35
|
+
@desc = e.css('a')[0]['title'] rescue nil
|
36
|
+
@season = e['data-season'].to_i rescue nil
|
37
|
+
@episode = e['data-title'][/Avsnitt\s+(\d+)/, 1].to_i rescue nil
|
38
|
+
@image = e.css('a img')[0]['src'].gsub("ALTERNATES/small", "ALTERNATES/extralarge") rescue nil
|
39
|
+
|
40
|
+
# Parse published_to
|
41
|
+
if pto = e['data-available']
|
42
|
+
# Days
|
43
|
+
if dayz = pto[/(\d+)\s+dag/, 1].to_i
|
44
|
+
@published_to = Time.parse("23:59", Date.today + dayz) rescue nil
|
45
|
+
else
|
46
|
+
@published_to = nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# subtitle
|
52
|
+
if @episode > 0
|
53
|
+
@subtitle = e['data-title'].gsub(@title + " - ", '').gsub("Avsnitt " + @episode.to_s + ':', '').gsub("Avsnitt " + @episode.to_s, '').strip
|
54
|
+
else
|
55
|
+
@subtitle = e['data-title'].gsub(@title + " - ", '').strip
|
56
|
+
end
|
57
|
+
|
58
|
+
@array[:results] << {id: @video_id, image: @image, season: @season, episode: @episode, subtitle: @subtitle, url: @url, description: @desc, published_to: @published_to}
|
59
|
+
end
|
60
|
+
else
|
61
|
+
raise NotValid, "Nokogiri failed to parse the HTML."
|
62
|
+
end
|
63
|
+
|
64
|
+
@array.to_hashugar
|
65
|
+
end
|
66
|
+
|
67
|
+
# Programs
|
68
|
+
def programs
|
69
|
+
# Response
|
70
|
+
response = Base.get('http://www.svtplay.se/program')
|
71
|
+
raise HTTPError, "The response didn't have a 200 HTTP Code. It had #{response.code}." unless response.code == 200
|
72
|
+
|
73
|
+
# Nokogiri parse
|
74
|
+
@main_noko = Nokogiri::HTML response.body rescue nil
|
75
|
+
|
76
|
+
# Foreach programs
|
77
|
+
@array = []
|
78
|
+
|
79
|
+
# Cant be nil
|
80
|
+
if @main_noko != nil
|
81
|
+
@main_noko.css('ul.play_alphabetic-list > li > ul > li').map do |p|
|
82
|
+
sluge = p.css('a')[0]['href'].strip.gsub("/", '')
|
83
|
+
titlee = p.css('a').text
|
84
|
+
@array << {slug: sluge, title: titlee}
|
85
|
+
end
|
86
|
+
|
87
|
+
else
|
88
|
+
raise NotValid, "Nokogiri failed to parse the HTML."
|
89
|
+
end
|
90
|
+
|
91
|
+
@array.to_hashugar
|
92
|
+
end
|
93
|
+
|
94
|
+
# Video
|
95
|
+
def video(params={})
|
96
|
+
# Response
|
97
|
+
response = Base.get('http://www.svtplay.se/video/' + params[:id].to_s)
|
98
|
+
raise HTTPError, "The response didn't have a 200 HTTP Code. It had #{response.code}." unless response.code == 200
|
99
|
+
|
100
|
+
# Nokogiri parse
|
101
|
+
@main_noko = Nokogiri::HTML response.body rescue nil
|
102
|
+
|
103
|
+
# Cant be nil
|
104
|
+
if @main_noko != nil
|
105
|
+
# Title
|
106
|
+
@title = @main_noko.css("h1.play_video-area-aside__title")[0].text.strip
|
107
|
+
|
108
|
+
# Subtitle data
|
109
|
+
submeta = @main_noko.css("h2.play_video-area-aside__sub-title")[0].text.strip.gsub("\n", ' ').squeeze(' ') rescue nil
|
110
|
+
if !submeta.nil?
|
111
|
+
@season = submeta[/S.song\s+(\d+)/, 1].to_i rescue nil
|
112
|
+
@episode = submeta[/Avsnitt\s+(\d+)/, 1].to_i rescue nil
|
113
|
+
end
|
114
|
+
|
115
|
+
# Desc
|
116
|
+
@desc = @main_noko.css('p.play_video-area-aside__info-text')[0].text.strip rescue nil
|
117
|
+
|
118
|
+
# Player data
|
119
|
+
playerdata = @main_noko.css("a.play_js-svtplayer")[0]
|
120
|
+
@published_on = Time.at(playerdata['data-popularity-publish-date'].to_i/1000) rescue nil
|
121
|
+
if playerdata['data-expires-timestamp'] != nil
|
122
|
+
@published_to = Time.at(playerdata['data-expires-timestamp'].to_i/1000) rescue nil
|
123
|
+
else
|
124
|
+
@published_to = nil
|
125
|
+
end
|
126
|
+
@url = playerdata['data-popularity-url'] rescue nil
|
127
|
+
@length = (playerdata['data-length'].to_i)/60 rescue nil
|
128
|
+
@image = @main_noko.css('meta[property="og:image"]')[0]['content'].gsub("ALTERNATES/medium", "ALTERNATES/extralarge") rescue nil
|
129
|
+
|
130
|
+
# Add subtitle from playerdata
|
131
|
+
if submeta != nil and @episode > 0
|
132
|
+
@subtitle = playerdata['data-title'].gsub(@title + " - ", '').gsub("Avsnitt " + @episode.to_s + ':', '').gsub("Avsnitt " + @episode.to_s, '').strip
|
133
|
+
|
134
|
+
@subtitle = nil if @subtitle == ""
|
135
|
+
else
|
136
|
+
@subtitle = playerdata['data-title'].gsub(@title + " - ", '').strip
|
137
|
+
@subtitle = nil if @subtitle == ""
|
138
|
+
end
|
139
|
+
|
140
|
+
{ id: params[:id].to_i, title: @title, image: @image, season: @season, episode: @episode, subtitle: @subtitle, length: @length, published_on: @published_on, published_to: @published_to, url: @url, description: @desc }.to_hashugar
|
141
|
+
else
|
142
|
+
raise NotValid, "Nokogiri failed to parse the HTML."
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
# Errors
|
148
|
+
class NotValid < StandardError; end
|
149
|
+
class HTTPError < StandardError; end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
## Viki provides an open API with embeds urls etc
|
2
|
+
## - Required options is app (id)
|
3
|
+
|
4
|
+
require 'oj'
|
5
|
+
|
6
|
+
module Shin
|
7
|
+
module Play
|
8
|
+
class Viki
|
9
|
+
|
10
|
+
def new
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
# Fix these before running
|
15
|
+
def before(params={})
|
16
|
+
raise MissingArgument, "You are missing the argument 'viki_app_id' which is required to use this source." unless Shin.get[:viki_app_id] != nil
|
17
|
+
|
18
|
+
# Timestamp
|
19
|
+
params[:t] = Time.now.to_i
|
20
|
+
|
21
|
+
"?app=" + Shin.get[:viki_app_id] + "&" + URI.encode_www_form(params)
|
22
|
+
end
|
23
|
+
|
24
|
+
# All <category>, params can be page=num, per_page=num etc.
|
25
|
+
def all(category, params={})
|
26
|
+
query = before(params)
|
27
|
+
raise NotValid, "Not a valid category. Please check again." unless ["films", "series", "news", "artists"].include?(category)
|
28
|
+
|
29
|
+
# Response
|
30
|
+
response = Base.get('http://api.viki.io/v5/' + category + '.json' + query)
|
31
|
+
data = Oj.load(response.body) rescue nil
|
32
|
+
ret = {more: data['more'], response: []}
|
33
|
+
|
34
|
+
# Multiple
|
35
|
+
if data != nil
|
36
|
+
data['response'].each do |r|
|
37
|
+
ret[:results] << r
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
ret.to_hashugar
|
43
|
+
end
|
44
|
+
|
45
|
+
# Search (params can be everything on the docs)
|
46
|
+
def search(params={})
|
47
|
+
query = before(params)
|
48
|
+
|
49
|
+
# Response
|
50
|
+
response = Base.get('http://api.viki.io/v5/search.json' + query)
|
51
|
+
data = Oj.load(response.body) rescue nil
|
52
|
+
ret = {more: data['more'], response: []}
|
53
|
+
|
54
|
+
# Multiple
|
55
|
+
if !data.empty? and data != nil
|
56
|
+
data['response'].each do |r|
|
57
|
+
ret[:results] << r
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
ret.to_hashugar
|
63
|
+
end
|
64
|
+
|
65
|
+
# Info (can be series id, movie id, video etc)
|
66
|
+
def info(params={})
|
67
|
+
id = params[:id]
|
68
|
+
query = before(params)
|
69
|
+
|
70
|
+
# Response
|
71
|
+
response = Base.get('http://api.viki.io/v5/containers/' + id.to_s + '.json' + query)
|
72
|
+
data = Oj.load(response.body) rescue nil
|
73
|
+
|
74
|
+
data.to_hashugar
|
75
|
+
end
|
76
|
+
|
77
|
+
# Episodes
|
78
|
+
def episodes(params={})
|
79
|
+
id = params[:id]
|
80
|
+
query = before(params)
|
81
|
+
|
82
|
+
# Response
|
83
|
+
response = Base.get('http://api.viki.io/v5/containers/' + id.to_s + '/episodes.json' + query)
|
84
|
+
data = Oj.load(response.body) rescue nil
|
85
|
+
ret = {more: data['more'], response: []}
|
86
|
+
|
87
|
+
# Multiple
|
88
|
+
if !data.empty? and data != nil
|
89
|
+
data['response'].each do |r|
|
90
|
+
ret[:results] << r
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
ret.to_hashugar
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
# Errors
|
99
|
+
class NotValid < StandardError; end
|
100
|
+
class MissingArgument < StandardError; end
|
101
|
+
class HTTPError < StandardError; end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/lib/shin/play.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'play/svtplay'
|
2
|
+
require_relative 'play/viki'
|
3
|
+
#require_relative 'play/tv4play'
|
4
|
+
#require_relative 'play/urplay'
|
5
|
+
#require_relative 'play/viaplay'
|
6
|
+
#require_relative 'play/netflix'
|
7
|
+
#require_relative 'play/viasat'
|
8
|
+
#require_relative 'play/headweb'
|
9
|
+
#require_relative 'play/film2home'
|
10
|
+
#require_relative 'play/plejmo'
|
11
|
+
|
12
|
+
module Shin
|
13
|
+
module Play
|
14
|
+
class << self
|
15
|
+
# I don't know why I need this
|
16
|
+
def new
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def svtplay
|
21
|
+
@svtplay ||= Svtplay.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def viki
|
25
|
+
@viki ||= Viki.new
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
## Kritiker provides a private API for people they allow.
|
2
|
+
## - Needed argument is IMDB ID
|
3
|
+
## - Required options is kritiker_token
|
4
|
+
|
5
|
+
require 'oj'
|
6
|
+
|
7
|
+
module Shin
|
8
|
+
module Reviews
|
9
|
+
class Kritiker
|
10
|
+
|
11
|
+
def new
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def find(h = {})
|
16
|
+
raise MissingArgument, "You are missing the argument 'imdb' or 'kritiker_token' which is required to use this source." unless h[:imdb] != "" and Shin.get[:kritiker_token] == ""
|
17
|
+
|
18
|
+
# We got the needed things
|
19
|
+
response = Base.get('http://api.kritiker.se/film/?imdb='+h[:imdb]+'&token='+Shin.get[:kritiker_token])
|
20
|
+
|
21
|
+
data = Oj.load(response.body) rescue nil
|
22
|
+
raise NotJSON, "Returned data isn't JSON. Couldn't parse it." if data == nil
|
23
|
+
|
24
|
+
year = data['datum'][/^(\d\d\d\d)/, 1].to_i unless data['datum'][/^(\d\d\d\d)/, 1].to_i == 0
|
25
|
+
{name: data['titel'], year: year, title: nil, rating: data['medelbetyg'].gsub(",", ".").to_f, url: data['kritiker'], votes: data['antalrecensioner'].to_i}.to_hashugar
|
26
|
+
end
|
27
|
+
|
28
|
+
class NotJSON < StandardError; end
|
29
|
+
class MissingArgument < StandardError; end
|
30
|
+
class HTTPError < StandardError; end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
## This Call is using the Film2home site's API. So only use this if you got approved by MZ to use the data.
|
2
|
+
## As they are very strict about it. I will move this to their API url when they get back to me sometime.
|
3
|
+
## - Needed agruments are imdb_id (only int, but it auto removes "tt")
|
4
|
+
|
5
|
+
module Shin
|
6
|
+
module Reviews
|
7
|
+
class Moviezine
|
8
|
+
|
9
|
+
def new
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
# Currently IMDB ID is the only way
|
14
|
+
def find(h = {})
|
15
|
+
raise MissingArgument, "You are missing the argument 'imdb' which is required to use this source." unless h[:imdb] != ""
|
16
|
+
|
17
|
+
# We got the needed things
|
18
|
+
imdb_id_int = h[:imdb].gsub(/^tt/, "")
|
19
|
+
response = Base.get('https://www.film2home.se/Services/MovieZine.svc/GetReview?imdbId='+imdb_id_int.to_s)
|
20
|
+
|
21
|
+
# Raise error if it didn't have a correct http code.
|
22
|
+
raise HTTPError, "The response didn't have a 200 HTTP Code. It had #{response.code}." unless response.code == 200
|
23
|
+
|
24
|
+
data = response.parsed_response
|
25
|
+
{name: nil, year: nil, title: data['Title'], rating: data['Rating'].to_i, url: data['Url'], votes: nil}.to_hashugar
|
26
|
+
end
|
27
|
+
|
28
|
+
class MissingArgument < StandardError; end
|
29
|
+
class HTTPError < StandardError; end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
## Russin.nu provides this API for non-commercial usage and only if you link it back.
|
2
|
+
## - Needed argument is title and optional is year.
|
3
|
+
|
4
|
+
module Shin
|
5
|
+
module Reviews
|
6
|
+
class Russin
|
7
|
+
|
8
|
+
def new
|
9
|
+
self
|
10
|
+
end
|
11
|
+
|
12
|
+
def find(h = {})
|
13
|
+
# Search can either have title and year or only title
|
14
|
+
if h[:year] != ""
|
15
|
+
response = Base.get('http://www.russin.nu/api.php?soktitel='+URI.encode(h[:title])+"&year="+h[:year].to_s)
|
16
|
+
elsif h[:title] != ""
|
17
|
+
response = Base.get('http://www.russin.nu/api.php?soktitel='+URI.encode(h[:title]))
|
18
|
+
else
|
19
|
+
raise MissingArgument, "You are missing the argument 'title' which is required to use this source."
|
20
|
+
end
|
21
|
+
|
22
|
+
# Raise error if it didn't have a correct http code.
|
23
|
+
raise HTTPError, "The response didn't have a 200 HTTP Code. It had #{response.code}." unless response.code == 200
|
24
|
+
|
25
|
+
# Data, it can be multiple reviews for a single movie from different reviewers
|
26
|
+
doc = Nokogiri::XML(response.body)
|
27
|
+
|
28
|
+
doc.remove_namespaces!
|
29
|
+
data = []
|
30
|
+
doc.xpath("//data/russinrecension").each do |review|
|
31
|
+
movie_title, movie_year = review.xpath('./filmtitel').text.split(", ")
|
32
|
+
title = review.xpath('./rubrik').text
|
33
|
+
rating = review.xpath('./betyg').text
|
34
|
+
url = review.xpath('./url').text
|
35
|
+
|
36
|
+
data << {name: movie_title, year: movie_year.to_i, title: title, rating: rating.to_i, url: url, votes: nil}
|
37
|
+
end
|
38
|
+
|
39
|
+
data.to_hashugar
|
40
|
+
end
|
41
|
+
|
42
|
+
class MissingArgument < StandardError; end
|
43
|
+
class HTTPError < StandardError; end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/shin/reviews.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative 'reviews/russin'
|
2
|
+
require_relative 'reviews/moviezine'
|
3
|
+
require_relative 'reviews/kritiker'
|
4
|
+
|
5
|
+
module Shin
|
6
|
+
module Reviews
|
7
|
+
class << self
|
8
|
+
# I don't know why I need this
|
9
|
+
def new
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
def russin
|
14
|
+
@russin ||= Russin.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def moviezine
|
18
|
+
@moviezine ||= Moviezine.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def kritiker
|
22
|
+
@kritiker ||= Kritiker.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/shin/version.rb
ADDED
data/lib/shin.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require_relative 'shin/version'
|
2
|
+
require 'json'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'httparty'
|
5
|
+
require 'hashugar'
|
6
|
+
|
7
|
+
require_relative 'shin/httparty_icebox'
|
8
|
+
require_relative 'shin/base'
|
9
|
+
require_relative 'shin/reviews'
|
10
|
+
require_relative 'shin/play'
|
11
|
+
|
12
|
+
module Shin
|
13
|
+
class Error < RuntimeError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Shin
|
18
|
+
def self.new(*a)
|
19
|
+
Shin.new(*a)
|
20
|
+
end
|
21
|
+
class Shin
|
22
|
+
#attr_accessor :options
|
23
|
+
def initialize(args={})
|
24
|
+
@@options = args
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.get
|
28
|
+
@@options ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def base
|
32
|
+
@base ||= Base.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def play
|
36
|
+
@play ||= Play.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def reviews
|
40
|
+
@reviews ||= Reviews.new
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/shin.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
require "shin/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "shin"
|
9
|
+
s.version = Shin::VERSION
|
10
|
+
s.platform = Gem::Platform::RUBY
|
11
|
+
s.licenses = ['GPL 2.0']
|
12
|
+
|
13
|
+
s.authors = ["Joakim Nylen"]
|
14
|
+
s.date = %q{2015-02-23}
|
15
|
+
s.email = %q{me@jnylen.nu}
|
16
|
+
s.files = `git ls-files`.split($/)
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
s.homepage = %q{https://github.com/jnylen/shin}
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
s.summary = "Shin provides a way to fetch data from various places."
|
21
|
+
s.description = "Shin provides a way to fetch data for various places such as SVTPlay, Viasat Image, DFI, SFI etc."
|
22
|
+
|
23
|
+
# We need these for all
|
24
|
+
s.add_dependency(%q<nokogiri>, ["~> 1.5"])
|
25
|
+
s.add_runtime_dependency 'oj', '~> 2.11', '>= 2.11.1'
|
26
|
+
s.add_runtime_dependency 'httparty', '~> 0.6', '>= 0.6.1'
|
27
|
+
s.add_runtime_dependency 'hashugar', '~> 1.0', '>= 1.0.0'
|
28
|
+
end
|
29
|
+
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shin
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joakim Nylen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nokogiri
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: oj
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.11'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 2.11.1
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.11'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.11.1
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: httparty
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.6'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.6.1
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0.6'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.6.1
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: hashugar
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '1.0'
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.0.0
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '1.0'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 1.0.0
|
87
|
+
description: Shin provides a way to fetch data for various places such as SVTPlay,
|
88
|
+
Viasat Image, DFI, SFI etc.
|
89
|
+
email: me@jnylen.nu
|
90
|
+
executables: []
|
91
|
+
extensions: []
|
92
|
+
extra_rdoc_files: []
|
93
|
+
files:
|
94
|
+
- README.textile
|
95
|
+
- lib/shin.rb
|
96
|
+
- lib/shin/base.rb
|
97
|
+
- lib/shin/httparty_icebox.rb
|
98
|
+
- lib/shin/play.rb
|
99
|
+
- lib/shin/play/svtplay.rb
|
100
|
+
- lib/shin/play/viki.rb
|
101
|
+
- lib/shin/reviews.rb
|
102
|
+
- lib/shin/reviews/kritiker.rb
|
103
|
+
- lib/shin/reviews/moviezine.rb
|
104
|
+
- lib/shin/reviews/russin.rb
|
105
|
+
- lib/shin/version.rb
|
106
|
+
- shin.gemspec
|
107
|
+
homepage: https://github.com/jnylen/shin
|
108
|
+
licenses:
|
109
|
+
- GPL 2.0
|
110
|
+
metadata: {}
|
111
|
+
post_install_message:
|
112
|
+
rdoc_options: []
|
113
|
+
require_paths:
|
114
|
+
- lib
|
115
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
requirements: []
|
126
|
+
rubyforge_project:
|
127
|
+
rubygems_version: 2.2.2
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: Shin provides a way to fetch data from various places.
|
131
|
+
test_files: []
|