acts_as_unvlogable_2 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f4f66fb07e85f15f6e72be62dd7e13b862f8d039
4
+ data.tar.gz: 4dc34b2c0108033653d18591fc079aaf5d78f820
5
+ SHA512:
6
+ metadata.gz: 3dd6c6e49b051cd4f510d9eec2ee18999561215c8e4cb0aa1790437f1440cf455e44ccf03461ddd5e748123711790d585e95b61c95ceb4e86fc8c5699d793a47
7
+ data.tar.gz: ff137715a74e0eacc3f22fd445e490fbbc5d59ed9a698232a6b952b9dfd6176f6e5ea50a662140f65277157fa6941a78984d7078ca89f5ea5c5cd547eea6c94b
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+
6
+ .DS_Store
@@ -0,0 +1,16 @@
1
+ language: ruby
2
+ cache: bundler
3
+
4
+ rvm:
5
+ - 2.1.0
6
+
7
+ branches:
8
+ only:
9
+ - refactor
10
+
11
+ notifications:
12
+ email:
13
+ recipients:
14
+ - mamuso@mamuso.net
15
+ on_failure: change
16
+ on_success: never
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in acts_as_unvlogable.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "bundler", "> 1.0.0"
8
+ gem "debugger"
9
+ end
10
+
11
+ group :development, :test do
12
+ gem "shoulda"
13
+ end
14
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 unvlog.com
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,130 @@
1
+ Acts as unvlogable
2
+ ==================
3
+
4
+ What the hell is this!
5
+ ----------------------
6
+
7
+ This is the plugin that we use in [unvlog.com](http://unvlog.com) to manage the supported video services. It is an easy way to obtain a few basics about a video only through its url.
8
+
9
+ A quick example:
10
+
11
+ To include [this video](http://www.youtube.com/watch?v=GPQnbtldFyo) in [this post](http://unvlog.com/blat/2008/3/10/otro-pelotazo) we need to know its title, the correct way to embed it and its thumbnail url. With this plugin we have an easy access to this data:
12
+
13
+ @aha = UnvlogIt.new("http://www.youtube.com/watch?v=GPQnbtldFyo")
14
+ @aha.title => "paradon del portero"
15
+ @aha.thumbnail => "http://i4.ytimg.com/vi/GPQnbtldFyo/default.jpg"
16
+ @aha.embed_url => "http://www.youtube.com/v/GPQnbtldFyo"
17
+ @aha.embed_html(width, height) => "<object [...]</object>"
18
+ @aha.flv => "http://...flv"
19
+ # all together :)
20
+ @aha.video_details(width, height) => {
21
+ :title => ...,
22
+ :thumbnail => ...,
23
+ :embed_url => ...,
24
+ :embed_html => ...,
25
+ :flv => ...
26
+ }
27
+
28
+ With this plugin we have an unique way to manage multiple services :)
29
+
30
+
31
+ Install it!
32
+ -----------
33
+
34
+ 1. Install it as a gem:
35
+
36
+ gem "acts_as_unvlogable"
37
+
38
+ 2. Optionally you can create the `config/unvlogable.yml` to store keys for the different services. You have in the plugin a [sample file](http://github.com/mamuso/acts_as_unvlogable/tree/master/unvlogable_sample.yml). At this moment you only need specify keys for flickr.
39
+
40
+
41
+ Dependencies
42
+ ------------
43
+
44
+ The plugin depends on [youtube-it](https://github.com/kylejginavan/youtube_it), [xml-simple](http://xml-simple.rubyforge.org/) and [hpricot](https://code.whytheluckystiff.net/hpricot/).
45
+
46
+ We have included a modified version of the flickr gem to skip the gem dependency and manage video capabilities.
47
+
48
+
49
+ Use it!
50
+ -------
51
+
52
+
53
+ The idea is make it as simple as possible. For a given video URL as <http://vimeo.com/1785993>:
54
+
55
+ videotron = UnvlogIt.new("http://vimeo.com/1785993")
56
+
57
+ Then we have methods to know the 'basics' for use this video on your application.
58
+
59
+ - __title:__ A method to know the title of the video on the service.
60
+
61
+ videotron.title
62
+ => "Beached"
63
+
64
+ - __service:__ A method to know the name of the video provider service.
65
+
66
+ videotron.service
67
+ => "Vimeo"
68
+
69
+ - __thumbnail:__ An image representation of the video. Each service has a different size, but... it works :)
70
+
71
+ videotron.thumbnail
72
+ => "http://bc1.vimeo.com/vimeo/thumbs/143104745_640.jpg"
73
+
74
+ - __embed\_url:__ The url (with flashvars) of the video player.
75
+
76
+ videotron.embed_url
77
+ => "http://vimeo.com/moogaloop.swf?clip_id=1785993 [...] &show_portrait=1"
78
+
79
+ - __embed\_html(width, height):__ Uses the embed\_url to build an oembed string. The default width x height is 425 x 344, but we can specify a different one.
80
+
81
+ videotron.embed_html(400, 300)
82
+ => "<object width='400' height='300'><param name='mo [...] 300'></embed></object>"
83
+
84
+ - __flv:__ Gets the flv url. In this edition we implement this method in all the services, but is possible that we can't get the flv in some scenarios. Remember that in some services the flv url expires and in most of their terms don't allow use the flv without its player.
85
+
86
+ videotron.flv
87
+ => "http://www.vimeo.com/moogaloop/play/clip:1785993/ [...] 8ee400/video.flv"
88
+
89
+ - __video\_details(width, height):__ All together :), returns all the previous elements in a hash. Width and height can be specified to build the embed\_html.
90
+
91
+ videotron.video_details
92
+ => "{ [...] }"
93
+
94
+
95
+ Supported services
96
+ ------------------
97
+
98
+ At this moment we support the following video services:
99
+
100
+ - [Youtube](http://www.youtube.com/)
101
+ - [Vimeo](http://vimeo.com/)
102
+ - [Flickr (videos)](http://flickr.com/)
103
+ - [Metacafe](http://metacafe.com/)
104
+ - [Dailymotion](http://dailymotion.com/)
105
+ - [Collegehumor](http://collegehumor.com/)
106
+ - [Blip.tv](http://blip.tv/)
107
+ - [Myspace](http://vids.myspace.com/)
108
+ - [Ted Talks](http://www.ted.com/talks/)
109
+ - [11870.com](http://11870.com/)
110
+ - [Marca.tv](http://www.marca.tv/)
111
+ - [Dalealplay](http://www.dalealplay.com/)
112
+ - [RuTube](http://www.rutube.ru/)
113
+
114
+ Broken services
115
+ ---------------
116
+
117
+ These services were implemented but due to changes in the website they don't work anymore. Any patch for fixing them would be great ;)
118
+
119
+ - [Qik](http://qik.com/)
120
+ - [MTV](http://www.mtvhive.com/)
121
+
122
+ You can detect new broken services when running the tests.
123
+
124
+ We are always open to incude new services.
125
+
126
+ And... what else?
127
+ -----------------
128
+ If you find a bug or want to suggest a new video service, please tell it to us in [a ticket](http://github.com/mamuso/acts_as_unvlogable/issues).
129
+
130
+ Thanks!!
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "acts_as_unvlogable/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "acts_as_unvlogable_2"
7
+ s.version = ActsAsUnvlogable::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Manuel Muñoz", "Fernando Blat", "Alberto Romero"]
10
+ s.email = ["mamusino@gmail.com", "ferblape@gmail.com", "denegro@gmail.com"]
11
+ s.homepage = "https://github.com/mamuso/acts_as_unvlogable"
12
+ s.summary = %q{An easy way to include external video services in a rails app}
13
+ s.description = %q{An easy way to include external video services in a rails app. This gem provides you wrappers for the most common video services, such as Youtube, Vimeo, Flickr, and so on...}
14
+
15
+ s.rubyforge_project = "acts_as_unvlogable"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+ s.add_development_dependency "shoulda"
22
+ s.add_dependency("xml-simple")
23
+ s.add_dependency("youtube_it")
24
+ s.add_dependency("hpricot")
25
+ end
@@ -0,0 +1,117 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ require "xmlsimple"
5
+ require "youtube_it"
6
+ require "hpricot"
7
+ require "net/http"
8
+ require "json"
9
+
10
+ require "acts_as_unvlogable/flickr"
11
+ # Extensions
12
+ if defined?(ActiveSupport).nil?
13
+ require "acts_as_unvlogable/string_base"
14
+ require "acts_as_unvlogable/object_base"
15
+ end
16
+ require "acts_as_unvlogable/string_extend"
17
+
18
+ # Video classes
19
+ videolibs = File.join(File.dirname(__FILE__), "acts_as_unvlogable", "vg_*.rb")
20
+ Dir.glob(videolibs).each {|file| require file}
21
+
22
+ class UnvlogIt
23
+
24
+ def initialize(url=nil, options={})
25
+ @object = VideoFactory.new(url, options).load_service
26
+ end
27
+
28
+ def title
29
+ @object.title #rescue nil
30
+ end
31
+
32
+ def thumbnail
33
+ @object.thumbnail rescue nil
34
+ end
35
+
36
+ def duration # duration is in seconds
37
+ @object.duration rescue nil
38
+ end
39
+
40
+ def embed_url
41
+ @object.embed_url rescue nil
42
+ end
43
+
44
+ def video_id
45
+ @object.video_id rescue nil
46
+ end
47
+
48
+ def embed_html(width=425, height=344, options={}, params={})
49
+ @object.embed_html(width, height, options, params) rescue nil
50
+ end
51
+
52
+ def flv
53
+ @object.flv #rescue nil
54
+ end
55
+
56
+ def download_url
57
+ @object.download_url rescue nil
58
+ end
59
+
60
+ def service
61
+ @object.service rescue nil
62
+ end
63
+
64
+ def video_details(width=425, height=344)
65
+ {
66
+ :title => @object.title,
67
+ :thumbnail => @object.thumbnail,
68
+ :embed_url => @object.embed_url,
69
+ :embed_html => @object.embed_html(width, height),
70
+ :flv => @object.flv,
71
+ :download_url => @object.download_url,
72
+ :service => @object.service,
73
+ :duration => @object.duration
74
+ }
75
+ end
76
+
77
+ class VideoFactory
78
+ def initialize(url, options = {})
79
+ raise ArgumentError.new("We need a video url") if url.blank?
80
+ @url = url
81
+ @options = options
82
+ end
83
+
84
+ def load_service
85
+ @object = service_object
86
+
87
+ validate_embed(@object)
88
+ end
89
+
90
+ private
91
+
92
+ def validate_embed(object)
93
+ unless object.instance_variable_get("@details").nil? || !object.instance_variable_get("@details").respond_to?("noembed")
94
+ if object.instance_variable_get("@details").noembed
95
+ raise ArgumentError.new("Embedding disabled by request")
96
+ end
97
+ end
98
+ object
99
+ end
100
+
101
+ def service_object
102
+ class_name = "vg_#{get_domain.downcase}".camelize
103
+ class_name.constantize.new(@url, @options)
104
+ rescue NameError
105
+ raise ArgumentError.new("Unsuported url or service. class: #{class_name}, url: #{@url}")
106
+ end
107
+
108
+ def get_domain
109
+ host = URI::parse(@url).host.split(".")
110
+ unless host.size == 1
111
+ host[host.size-2]
112
+ else
113
+ host[0]
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,713 @@
1
+ # included here to skip the gem dependency and modified to manage video capabilities.
2
+ # this is the updated version from http://github.com/ctagg/flickr/tree/master/lib/flickr.rb
3
+
4
+ # = Flickr
5
+ # An insanely easy interface to the Flickr photo-sharing service. By Scott Raymond.
6
+ #
7
+ # Author:: Scott Raymond <sco@redgreenblu.com>
8
+ # Copyright:: Copyright (c) 2005 Scott Raymond <sco@redgreenblu.com>. Additional content by Patrick Plattes and Chris Taggart (http://pushrod.wordpress.com)
9
+ # License:: MIT <http://www.opensource.org/licenses/mit-license.php>
10
+ #
11
+ # BASIC USAGE:
12
+ # require 'flickr'
13
+ # flickr = Flickr.new('some_flickr_api_key') # create a flickr client (get an API key from http://www.flickr.com/services/api/)
14
+ # user = flickr.users('sco@scottraymond.net') # lookup a user
15
+ # user.name # get the user's name
16
+ # user.location # and location
17
+ # user.photos # grab their collection of Photo objects...
18
+ # user.groups # ...the groups they're in...
19
+ # user.contacts # ...their contacts...
20
+ # user.favorites # ...favorite photos...
21
+ # user.photosets # ...their photo sets...
22
+ # user.tags # ...and their tags
23
+ # recentphotos = flickr.photos # get the 100 most recent public photos
24
+ # photo = recentphotos.first # or very most recent one
25
+ # photo.url # see its URL,
26
+ # photo.title # title,
27
+ # photo.description # and description,
28
+ # photo.owner # and its owner.
29
+ # File.open(photo.filename, 'w') do |file|
30
+ # file.puts p.file # save the photo to a local file
31
+ # end
32
+ # flickr.photos.each do |p| # get the last 100 public photos...
33
+ # File.open(p.filename, 'w') do |f|
34
+ # f.puts p.file('Square') # ...and save a local copy of their square thumbnail
35
+ # end
36
+ # end
37
+
38
+
39
+ require 'cgi'
40
+ require 'net/http'
41
+ require 'xmlsimple'
42
+ require 'digest/md5'
43
+
44
+ # Flickr client class. Requires an API key
45
+ class Flickr
46
+ attr_reader :api_key, :auth_token
47
+ attr_accessor :user
48
+
49
+ HOST_URL = 'http://flickr.com'
50
+ API_PATH = '/services/rest'
51
+
52
+ # Flickr, annoyingly, uses a number of representations to specify the size
53
+ # of a photo, depending on the context. It gives a label such a "Small" or
54
+ # "Medium" to a size of photo, when returning all possible sizes. However,
55
+ # when generating the uri for the page that features that size of photo, or
56
+ # the source url for the image itself it uses a single letter. Bizarrely,
57
+ # these letters are different depending on whether you want the Flickr page
58
+ # for the photo or the source uri -- e.g. a "Small" photo (240 pixels on its
59
+ # longest side) may be viewed at
60
+ # "http://www.flickr.com/photos/sco/2397458775/sizes/s/"
61
+ # but its source is at
62
+ # "http://farm4.static.flickr.com/3118/2397458775_2ec2ddc324_m.jpg".
63
+ # The VALID_SIZES hash associates the correct letter with a label
64
+ VALID_SIZES = { "Square" => ["s", "sq"],
65
+ "Thumbnail" => ["t", "t"],
66
+ "Small" => ["m", "s"],
67
+ "Medium" => [nil, "m"],
68
+ "Large" => ["b", "l"]
69
+ }
70
+
71
+ # To use the Flickr API you need an api key
72
+ # (see http://www.flickr.com/services/api/misc.api_keys.html), and the flickr
73
+ # client object shuld be initialized with this. You'll also need a shared
74
+ # secret code if you want to use authentication (e.g. to get a user's
75
+ # private photos)
76
+ # There are two ways to initialize the Flickr client. The preferred way is with
77
+ # a hash of params, e.g. 'api_key' => 'your_api_key', 'shared_secret' =>
78
+ # 'shared_secret_code'. The older (deprecated) way is to pass an ordered series of
79
+ # arguments. This is provided for continuity only, as several of the arguments
80
+ # are no longer usable ('email', 'password')
81
+ def initialize(api_key_or_params=nil, email=nil, password=nil, shared_secret=nil)
82
+ @host = HOST_URL
83
+ @api = API_PATH
84
+ if api_key_or_params.is_a?(Hash)
85
+ @api_key = api_key_or_params['api_key']
86
+ @shared_secret = api_key_or_params['shared_secret']
87
+ @auth_token = api_key_or_params['auth_token']
88
+ else
89
+ @api_key = api_key_or_params
90
+ @shared_secret = shared_secret
91
+ login(email, password) if email and password
92
+ end
93
+ end
94
+
95
+ # Gets authentication token given a Flickr frob, which is returned when user
96
+ # allows access to their account for the application with the api_key which
97
+ # made the request
98
+ def get_token_from(frob)
99
+ auth_response = request("auth.getToken", :frob => frob)['auth']
100
+ @auth_token = auth_response['token']
101
+ @user = User.new( 'id' => auth_response['user']['nsid'],
102
+ 'username' => auth_response['user']['username'],
103
+ 'name' => auth_response['user']['fullname'],
104
+ 'client' => self)
105
+ @auth_token
106
+ end
107
+
108
+ # Stores authentication credentials to use on all subsequent calls.
109
+ # If authentication succeeds, returns a User object.
110
+ # NB This call is no longer in API and will result in an error if called
111
+ def login(email='', password='')
112
+ @email = email
113
+ @password = password
114
+ user = request('test.login')['user'] rescue fail
115
+ @user = User.new(user['id'], nil, nil, nil, @api_key)
116
+ end
117
+
118
+ # Implements flickr.urls.lookupGroup and flickr.urls.lookupUser
119
+ def find_by_url(url)
120
+ response = urls_lookupUser('url'=>url) rescue urls_lookupGroup('url'=>url) rescue nil
121
+ (response['user']) ? User.new(response['user']['id'], nil, nil, nil, @api_key) : Group.new(response['group']['id'], @api_key) unless response.nil?
122
+ end
123
+
124
+ # Implements flickr.photos.getRecent and flickr.photos.search
125
+ def photos(*criteria)
126
+ criteria ? photos_search(*criteria) : recent
127
+ end
128
+
129
+ # flickr.photos.getRecent
130
+ # 100 newest photos from everyone
131
+ def recent
132
+ photos_request('photos.getRecent')
133
+ end
134
+
135
+ def photos_search(params={})
136
+ photos_request('photos.search', params)
137
+ end
138
+ alias_method :search, :photos_search
139
+
140
+ # Gets public photos with a given tag
141
+ def tag(tag)
142
+ photos('tags'=>tag)
143
+ end
144
+
145
+ # Implements flickr.people.findByEmail and flickr.people.findByUsername.
146
+ def users(lookup=nil)
147
+ user = people_findByEmail('find_email'=>lookup)['user'] rescue people_findByUsername('username'=>lookup)['user']
148
+ return User.new("id" => user["nsid"], "username" => user["username"], "client" => self)
149
+ end
150
+
151
+ # Implements flickr.groups.search
152
+ def groups(group_name, options={})
153
+ collection = groups_search({"text" => group_name}.merge(options))['groups']['group']
154
+ collection = [collection] if collection.is_a? Hash
155
+
156
+ collection.collect { |group| Group.new( "id" => group['nsid'],
157
+ "name" => group['name'],
158
+ "eighteenplus" => group['eighteenplus'],
159
+ "client" => self) }
160
+ end
161
+
162
+ # Implements flickr.tags.getRelated
163
+ def related_tags(tag)
164
+ tags_getRelated('tag'=>tag)['tags']['tag']
165
+ end
166
+
167
+ # Implements flickr.photos.licenses.getInfo
168
+ def licenses
169
+ photos_licenses_getInfo['licenses']['license']
170
+ end
171
+
172
+ # Returns url for user to login in to Flickr to authenticate app for a user
173
+ def login_url(perms)
174
+ "http://flickr.com/services/auth/?api_key=#{@api_key}&perms=#{perms}&api_sig=#{signature_from('api_key'=>@api_key, 'perms' => perms)}"
175
+ end
176
+
177
+ # Implements everything else.
178
+ # Any method not defined explicitly will be passed on to the Flickr API,
179
+ # and return an XmlSimple document. For example, Flickr#test_echo is not
180
+ # defined, so it will pass the call to the flickr.test.echo method.
181
+ def method_missing(method_id, params={})
182
+ request(method_id.id2name.gsub(/_/, '.'), params)
183
+ end
184
+
185
+ # Does an HTTP GET on a given URL and returns the response body
186
+ def http_get(url)
187
+ Net::HTTP.get_response(URI.parse(url)).body.to_s
188
+ end
189
+
190
+ # Takes a Flickr API method name and set of parameters; returns an XmlSimple object with the response
191
+ def request(method, params={})
192
+ url = request_url(method, params)
193
+ response = XmlSimple.xml_in(open(url), { 'ForceArray' => false })
194
+ raise response['err']['msg'] if response['stat'] != 'ok'
195
+ response
196
+ end
197
+
198
+ # acts like request but returns a PhotoCollection (a list of Photo objects)
199
+ def photos_request(method, params={})
200
+ photos = request(method, params)
201
+ PhotoCollection.new(photos, @api_key)
202
+ end
203
+
204
+ # Builds url for Flickr API REST request from given the flickr method name
205
+ # (exclusing the 'flickr.' that begins each method call) and params (where
206
+ # applicable) which should be supplied as a Hash (e.g 'user_id' => "foo123")
207
+ def request_url(method, params={})
208
+ method = 'flickr.' + method
209
+ url = "#{@host}#{@api}/?api_key=#{@api_key}&method=#{method}"
210
+ params.merge!('api_key' => @api_key, 'method' => method, 'auth_token' => @auth_token)
211
+ signature = signature_from(params)
212
+
213
+ url = "#{@host}#{@api}/?" + params.merge('api_sig' => signature).collect { |k,v| "#{k}=" + CGI::escape(v.to_s) unless v.nil? }.compact.join("&")
214
+ end
215
+
216
+ def signature_from(params={})
217
+ return unless @shared_secret # don't both getting signature if no shared_secret
218
+ request_str = params.reject {|k,v| v.nil?}.collect {|p| "#{p[0].to_s}#{p[1]}"}.sort.join # build key value pairs, sort in alpha order then join them, ignoring those with nil value
219
+ return Digest::MD5.hexdigest("#{@shared_secret}#{request_str}")
220
+ end
221
+
222
+ # A collection of photos is returned as a PhotoCollection, a subclass of Array.
223
+ # This allows us to retain the pagination info returned by Flickr and make it
224
+ # accessible in a friendly way
225
+ class PhotoCollection < Array
226
+ attr_reader :page, :pages, :perpage, :total
227
+
228
+ # builds a PhotoCollection from given params, such as those returned from
229
+ # photos.search API call
230
+ def initialize(photos_api_response={}, api_key=nil)
231
+ [ "page", "pages", "perpage", "total" ].each { |i| instance_variable_set("@#{i}", photos_api_response["photos"][i])}
232
+ collection = photos_api_response['photos']['photo'] || []
233
+ collection = [collection] if collection.is_a? Hash
234
+ collection.each { |photo| self << Photo.new(photo.delete('id'), api_key, photo) }
235
+ end
236
+ end
237
+
238
+ # Todo:
239
+ # logged_in?
240
+ # if logged in:
241
+ # flickr.blogs.getList
242
+ # flickr.favorites.add
243
+ # flickr.favorites.remove
244
+ # flickr.groups.browse
245
+ # flickr.photos.getCounts
246
+ # flickr.photos.getNotInSet
247
+ # flickr.photos.getUntagged
248
+ # flickr.photosets.create
249
+ # flickr.photosets.orderSets
250
+ # flickr.tags.getListUserPopular
251
+ # flickr.test.login
252
+ # uploading
253
+ class User
254
+
255
+ attr_reader :client, :id, :name, :location, :photos_url, :url, :count, :firstdate, :firstdatetaken
256
+
257
+ # A Flickr::User can be instantiated in two ways. The old (deprecated)
258
+ # method is with an ordered series of values. The new method is with a
259
+ # params Hash, which is easier when a variable number of params are
260
+ # supplied, which is the case here, and also avoids having to constantly
261
+ # supply nil values for the email and password, which are now irrelevant
262
+ # as authentication is no longer done this way.
263
+ # An associated flickr client will also be generated if an api key is
264
+ # passed among the arguments or in the params hash. Alternatively, and
265
+ # most likely, an existing client object may be passed in the params hash
266
+ # (e.g. 'client' => some_existing_flickr_client_object), and this is
267
+ # what happends when users are initlialized as the result of a method
268
+ # called on the flickr client (e.g. flickr.users)
269
+ def initialize(id_or_params_hash=nil, username=nil, email=nil, password=nil, api_key=nil)
270
+ if id_or_params_hash.is_a?(Hash)
271
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
272
+ else
273
+ @id = id_or_params_hash
274
+ @username = username
275
+ @email = email
276
+ @password = password
277
+ @api_key = api_key
278
+ end
279
+ @client ||= Flickr.new('api_key' => @api_key, 'shared_secret' => @shared_secret, 'auth_token' => @auth_token) if @api_key
280
+ @client.login(@email, @password) if @email and @password # this is now irrelevant as Flickr API no longer supports authentication this way
281
+ end
282
+
283
+ def username
284
+ @username.nil? ? getInfo.username : @username
285
+ end
286
+ def name
287
+ @name.nil? ? getInfo.name : @name
288
+ end
289
+ def location
290
+ @location.nil? ? getInfo.location : @location
291
+ end
292
+ def count
293
+ @count.nil? ? getInfo.count : @count
294
+ end
295
+ def firstdate
296
+ @firstdate.nil? ? getInfo.firstdate : @firstdate
297
+ end
298
+ def firstdatetaken
299
+ @firstdatetaken.nil? ? getInfo.firstdatetaken : @firstdatetaken
300
+ end
301
+
302
+ # Builds url for user's photos page as per
303
+ # http://www.flickr.com/services/api/misc.urls.html
304
+ def photos_url
305
+ "http://www.flickr.com/photos/#{id}/"
306
+ end
307
+
308
+ # Builds url for user's profile page as per
309
+ # http://www.flickr.com/services/api/misc.urls.html
310
+ def url
311
+ "http://www.flickr.com/people/#{id}/"
312
+ end
313
+
314
+ def pretty_url
315
+ @pretty_url ||= @client.urls_getUserProfile('user_id'=>@id)['user']['url']
316
+ end
317
+
318
+ # Implements flickr.people.getPublicGroups
319
+ def groups
320
+ collection = @client.people_getPublicGroups('user_id'=>@id)['groups']['group']
321
+ collection = [collection] if collection.is_a? Hash
322
+ collection.collect { |group| Group.new( "id" => group['nsid'],
323
+ "name" => group['name'],
324
+ "eighteenplus" => group['eighteenplus'],
325
+ "client" => @client) }
326
+ end
327
+
328
+ # Implements flickr.people.getPublicPhotos. Options hash allows you to add
329
+ # extra restrictions as per flickr.people.getPublicPhotos docs, e.g.
330
+ # user.photos('per_page' => '25', 'extras' => 'date_taken')
331
+ def photos(options={})
332
+ @client.photos_request('people.getPublicPhotos', {'user_id' => @id}.merge(options))
333
+ # what about non-public photos?
334
+ end
335
+
336
+ # Gets photos with a given tag
337
+ def tag(tag)
338
+ @client.photos('user_id'=>@id, 'tags'=>tag)
339
+ end
340
+
341
+ # Implements flickr.contacts.getPublicList and flickr.contacts.getList
342
+ def contacts
343
+ @client.contacts_getPublicList('user_id'=>@id)['contacts']['contact'].collect { |contact| User.new(contact['nsid'], contact['username'], nil, nil, @api_key) }
344
+ #or
345
+ end
346
+
347
+ # Implements flickr.favorites.getPublicList
348
+ def favorites
349
+ @client.photos_request('favorites.getPublicList', 'user_id' => @id)
350
+ end
351
+
352
+ # Implements flickr.photosets.getList
353
+ def photosets
354
+ @client.photosets_getList('user_id'=>@id)['photosets']['photoset'].collect { |photoset| Photoset.new(photoset['id'], @api_key) }
355
+ end
356
+
357
+ # Implements flickr.tags.getListUser
358
+ def tags
359
+ @client.tags_getListUser('user_id'=>@id)['who']['tags']['tag'].collect { |tag| tag }
360
+ end
361
+
362
+ # Implements flickr.photos.getContactsPublicPhotos and flickr.photos.getContactsPhotos
363
+ def contactsPhotos
364
+ @client.photos_request('photos.getContactsPublicPhotos', 'user_id' => @id)
365
+ end
366
+
367
+ def to_s
368
+ @name
369
+ end
370
+
371
+
372
+
373
+ private
374
+
375
+ # Implements flickr.people.getInfo, flickr.urls.getUserPhotos, and flickr.urls.getUserProfile
376
+ def getInfo
377
+ info = @client.people_getInfo('user_id'=>@id)['person']
378
+ @username = info['username']
379
+ @name = info['realname']
380
+ @location = info['location']
381
+ @count = info['photos']['count']
382
+ @firstdate = info['photos']['firstdate']
383
+ @firstdatetaken = info['photos']['firstdatetaken']
384
+ self
385
+ end
386
+
387
+ end
388
+
389
+ class Photo
390
+
391
+ attr_reader :id, :client, :title
392
+
393
+ def initialize(id=nil, api_key=nil, extra_params={})
394
+ @id = id
395
+ @api_key = api_key
396
+ extra_params.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
397
+ @client = Flickr.new @api_key
398
+ end
399
+
400
+ # Allows access to all photos instance variables through hash like
401
+ # interface, e.g. photo["datetaken"] returns @datetaken instance
402
+ # variable. Useful for accessing any weird and wonderful parameter
403
+ # that may have been returned by Flickr when finding the photo,
404
+ # e.g. those returned by the extras argument in
405
+ # flickr.people.getPublicPhotos
406
+ def [](param_name)
407
+ instance_variable_get("@#{param_name}")
408
+ end
409
+
410
+ def title
411
+ @title.nil? ? getInfo("title") : @title
412
+ end
413
+
414
+ # Returns the owner of the photo as a Flickr::User. If we have no info
415
+ # about the owner, we make an API call to get it. If we already have
416
+ # the owner's id, create a user based on that. Either way, we cache the
417
+ # result so we don't need to check again
418
+ def owner
419
+ case @owner
420
+ when Flickr::User
421
+ @owner
422
+ when String
423
+ @owner = Flickr::User.new(@owner, nil, nil, nil, @api_key)
424
+ else
425
+ getInfo("owner")
426
+ end
427
+ end
428
+
429
+ def server
430
+ @server.nil? ? getInfo("server") : @server
431
+ end
432
+
433
+ def isfavorite
434
+ @isfavorite.nil? ? getInfo("isfavorite") : @isfavorite
435
+ end
436
+
437
+ def license
438
+ @license.nil? ? getInfo("license") : @license
439
+ end
440
+
441
+ def rotation
442
+ @rotation.nil? ? getInfo("rotation") : @rotation
443
+ end
444
+
445
+ def description
446
+ @description || getInfo("description")
447
+ end
448
+
449
+ def notes
450
+ @notes.nil? ? getInfo("notes") : @notes
451
+ end
452
+
453
+ # Returns the URL for the photo size page
454
+ # defaults to 'Medium'
455
+ # other valid sizes are in the VALID_SIZES hash
456
+ def size_url(size='Medium')
457
+ uri_for_photo_from_self(size) || sizes(size)['url']
458
+ end
459
+
460
+ # converts string or symbol size to a capitalized string
461
+ def normalize_size(size)
462
+ size ? size.to_s.capitalize : size
463
+ end
464
+
465
+ # the URL for the main photo page
466
+ # if getInfo has already been called, this will return the pretty url
467
+ #
468
+ # for historical reasons, an optional size can be given
469
+ # 'Medium' returns the regular url; any other size returns a size page
470
+ # use size_url instead
471
+ def url(size = nil)
472
+ if normalize_size(size) != 'Medium'
473
+ size_url(size)
474
+ else
475
+ @url || uri_for_photo_from_self
476
+ end
477
+ end
478
+
479
+ # the 'pretty' url for a photo
480
+ # (if the user has set up a custom name)
481
+ # eg, http://flickr.com/photos/granth/2584402507/ instead of
482
+ # http://flickr.com/photos/23386158@N00/2584402507/
483
+ def pretty_url
484
+ @url || getInfo("pretty_url")
485
+ end
486
+
487
+ # Returns the URL for the image (default or any specified size)
488
+ def source(size='Medium')
489
+ image_source_uri_from_self(size) || sizes(size)['source']
490
+ end
491
+
492
+ # Returns the photo file data itself, in any specified size. Example: File.open(photo.title, 'w') { |f| f.puts photo.file }
493
+ def file(size='Medium')
494
+ Net::HTTP.get_response(URI.parse(source(size))).body
495
+ end
496
+
497
+ # Unique filename for the image, based on the Flickr NSID
498
+ def filename
499
+ "#{@id}.jpg"
500
+ end
501
+
502
+ # Implements flickr.photos.getContext
503
+ def context
504
+ context = @client.photos_getContext('photo_id'=>@id)
505
+ @previousPhoto = Photo.new(context['prevphoto'].delete('id'), @api_key, context['prevphoto']) if context['prevphoto']['id']!='0'
506
+ @nextPhoto = Photo.new(context['nextphoto'].delete('id'), @api_key, context['nextphoto']) if context['nextphoto']['id']!='0'
507
+ return [@previousPhoto, @nextPhoto]
508
+ end
509
+
510
+ # Implements flickr.photos.getExif
511
+ def exif
512
+ @client.photos_getExif('photo_id'=>@id)['photo']
513
+ end
514
+
515
+ # Implements flickr.photos.getPerms
516
+ def permissions
517
+ @client.photos_getPerms('photo_id'=>@id)['perms']
518
+ end
519
+
520
+ # Implements flickr.photos.getSizes
521
+ def sizes(size=nil)
522
+ size = normalize_size(size)
523
+ sizes = @client.photos_getSizes('photo_id'=>@id)['sizes']['size']
524
+ sizes = sizes.find{|asize| asize['label']==size} if size
525
+ return sizes
526
+ end
527
+
528
+ # flickr.tags.getListPhoto
529
+ def tags
530
+ @client.tags_getListPhoto('photo_id'=>@id)['photo']['tags']
531
+ end
532
+
533
+ # Implements flickr.photos.notes.add
534
+ def add_note(note)
535
+ end
536
+
537
+ # Implements flickr.photos.setDates
538
+ def dates=(dates)
539
+ end
540
+
541
+ # Implements flickr.photos.setPerms
542
+ def perms=(perms)
543
+ end
544
+
545
+ # Implements flickr.photos.setTags
546
+ def tags=(tags)
547
+ end
548
+
549
+ # Implements flickr.photos.setMeta
550
+ def title=(title)
551
+ end
552
+ def description=(title)
553
+ end
554
+
555
+ # Implements flickr.photos.addTags
556
+ def add_tag(tag)
557
+ end
558
+
559
+ # Implements flickr.photos.removeTag
560
+ def remove_tag(tag)
561
+ end
562
+
563
+ # Implements flickr.photos.transform.rotate
564
+ def rotate
565
+ end
566
+
567
+ # Implements flickr.blogs.postPhoto
568
+ def postToBlog(blog_id, title='', description='')
569
+ @client.blogs_postPhoto('photo_id'=>@id, 'title'=>title, 'description'=>description)
570
+ end
571
+
572
+ # Implements flickr.photos.notes.delete
573
+ def deleteNote(note_id)
574
+ end
575
+
576
+ # Implements flickr.photos.notes.edit
577
+ def editNote(note_id)
578
+ end
579
+
580
+ # Converts the Photo to a string by returning its title
581
+ def to_s
582
+ title
583
+ end
584
+
585
+
586
+ # unvlog
587
+ def media
588
+ @media || getInfo("media")
589
+ end
590
+
591
+ def secret
592
+ @secret || getInfo("secret")
593
+ end
594
+
595
+
596
+ private
597
+
598
+ # Implements flickr.photos.getInfo
599
+ def getInfo(attrib="")
600
+ return instance_variable_get("@#{attrib}") if @got_info
601
+ info = @client.photos_getInfo('photo_id'=>@id)['photo']
602
+ @got_info = true
603
+ info.each { |k,v| instance_variable_set("@#{k}", v)}
604
+ @media = info['media']
605
+ @secret = info['secret']
606
+ @owner = User.new(info['owner']['nsid'], info['owner']['username'], nil, nil, @api_key)
607
+ @tags = info['tags']['tag']
608
+ @notes = info['notes']['note']#.collect { |note| Note.new(note.id) }
609
+ @url = info['urls']['url']['content'] # assumes only one url
610
+ instance_variable_get("@#{attrib}")
611
+ end
612
+
613
+ # Builds source uri of image from params (often returned from other
614
+ # methods, e.g. User#photos). As specified at:
615
+ # http://www.flickr.com/services/api/misc.urls.html. If size is given
616
+ # should be one the keys in the VALID_SIZES hash, i.e.
617
+ # "Square", "Thumbnail", "Medium", "Large", "Original", "Small" (These
618
+ # are the values returned by flickr.photos.getSizes).
619
+ # If no size is given the uri for "Medium"-size image, i.e. with width
620
+ # of 500 is returned
621
+ # TODO: Handle "Original" size
622
+ def image_source_uri_from_self(size=nil)
623
+ return unless @farm&&@server&&@id&&@secret
624
+ s_size = VALID_SIZES[normalize_size(size)] # get the short letters array corresponding to the size
625
+ s_size = s_size&&s_size[0] # the first element of this array is used to build the source uri
626
+ if s_size.nil?
627
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}.jpg"
628
+ else
629
+ "http://farm#{@farm}.static.flickr.com/#{@server}/#{@id}_#{@secret}_#{s_size}.jpg"
630
+ end
631
+ end
632
+
633
+ # Builds uri of Flickr page for photo. By default returns the main
634
+ # page for the photo, but if passed a size will return the simplified
635
+ # flickr page featuring the given size of the photo
636
+ # TODO: Handle "Original" size
637
+ def uri_for_photo_from_self(size=nil)
638
+ return unless @owner&&@id
639
+ size = normalize_size(size)
640
+ s_size = VALID_SIZES[size] # get the short letters array corresponding to the size
641
+ s_size = s_size&&s_size[1] # the second element of this array is used to build the uri of the flickr page for this size
642
+ "http://www.flickr.com/photos/#{owner.id}/#{@id}" + (s_size ? "/sizes/#{s_size}/" : "")
643
+ end
644
+ end
645
+
646
+ # Todo:
647
+ # flickr.groups.pools.add
648
+ # flickr.groups.pools.getContext
649
+ # flickr.groups.pools.getGroups
650
+ # flickr.groups.pools.getPhotos
651
+ # flickr.groups.pools.remove
652
+ class Group
653
+ attr_reader :id, :client, :description, :name, :eighteenplus, :members, :online, :privacy, :url#, :chatid, :chatcount
654
+
655
+ def initialize(id_or_params_hash=nil, api_key=nil)
656
+ if id_or_params_hash.is_a?(Hash)
657
+ id_or_params_hash.each { |k,v| self.instance_variable_set("@#{k}", v) } # convert extra_params into instance variables
658
+ else
659
+ @id = id_or_params_hash
660
+ @api_key = api_key
661
+ @client = Flickr.new @api_key
662
+ end
663
+ end
664
+
665
+ # Implements flickr.groups.getInfo and flickr.urls.getGroup
666
+ # private, once we can call it as needed
667
+ def getInfo
668
+ info = @client.groups_getInfo('group_id'=>@id)['group']
669
+ @name = info['name']
670
+ @members = info['members']
671
+ @online = info['online']
672
+ @privacy = info['privacy']
673
+ # @chatid = info['chatid']
674
+ # @chatcount = info['chatcount']
675
+ @url = @client.urls_getGroup('group_id'=>@id)['group']['url']
676
+ self
677
+ end
678
+
679
+ end
680
+
681
+ # Todo:
682
+ # flickr.photosets.delete
683
+ # flickr.photosets.editMeta
684
+ # flickr.photosets.editPhotos
685
+ # flickr.photosets.getContext
686
+ # flickr.photosets.getInfo
687
+ # flickr.photosets.getPhotos
688
+ class Photoset
689
+
690
+ attr_reader :id, :client, :owner, :primary, :photos, :title, :description, :url
691
+
692
+ def initialize(id=nil, api_key=nil)
693
+ @id = id
694
+ @api_key = api_key
695
+ @client = Flickr.new @api_key
696
+ end
697
+
698
+ # Implements flickr.photosets.getInfo
699
+ # private, once we can call it as needed
700
+ def getInfo
701
+ info = @client.photosets_getInfo('photoset_id'=>@id)['photoset']
702
+ @owner = User.new(info['owner'], nil, nil, nil, @api_key)
703
+ @primary = info['primary']
704
+ @photos = info['photos']
705
+ @title = info['title']
706
+ @description = info['description']
707
+ @url = "http://www.flickr.com/photos/#{@owner.getInfo.username}/sets/#{@id}/"
708
+ self
709
+ end
710
+
711
+ end
712
+
713
+ end