fogli 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/Gemfile +14 -0
- data/LICENSE +21 -0
- data/README.md +168 -0
- data/Rakefile +37 -0
- data/VERSION +1 -0
- data/examples/README.md +6 -0
- data/examples/me/README.md +18 -0
- data/examples/me/me.rb +30 -0
- data/lib/fogli.rb +46 -0
- data/lib/fogli/album.rb +8 -0
- data/lib/fogli/categorized_object.rb +9 -0
- data/lib/fogli/comment.rb +5 -0
- data/lib/fogli/event.rb +9 -0
- data/lib/fogli/exception.rb +15 -0
- data/lib/fogli/facebook_graph.rb +125 -0
- data/lib/fogli/facebook_object.rb +225 -0
- data/lib/fogli/facebook_object/connection_proxy.rb +80 -0
- data/lib/fogli/facebook_object/connection_scope.rb +123 -0
- data/lib/fogli/facebook_object/connections.rb +56 -0
- data/lib/fogli/facebook_object/properties.rb +81 -0
- data/lib/fogli/facebook_object/scope_methods.rb +49 -0
- data/lib/fogli/group.rb +8 -0
- data/lib/fogli/link.rb +8 -0
- data/lib/fogli/named_object.rb +8 -0
- data/lib/fogli/note.rb +7 -0
- data/lib/fogli/oauth.rb +167 -0
- data/lib/fogli/page.rb +15 -0
- data/lib/fogli/photo.rb +8 -0
- data/lib/fogli/post.rb +18 -0
- data/lib/fogli/status.rb +7 -0
- data/lib/fogli/user.rb +36 -0
- data/lib/fogli/util/module_attributes.rb +61 -0
- data/lib/fogli/util/options.rb +32 -0
- data/lib/fogli/video.rb +7 -0
- data/test/fogli/facebook_graph_test.rb +124 -0
- data/test/fogli/facebook_object/connection_proxy_test.rb +42 -0
- data/test/fogli/facebook_object/connection_scope_test.rb +91 -0
- data/test/fogli/facebook_object/connections_test.rb +50 -0
- data/test/fogli/facebook_object/properties_test.rb +79 -0
- data/test/fogli/facebook_object/scope_methods_test.rb +69 -0
- data/test/fogli/facebook_object_test.rb +178 -0
- data/test/fogli/oauth_test.rb +56 -0
- data/test/fogli/post_test.rb +18 -0
- data/test/fogli/user_test.rb +25 -0
- data/test/fogli/util/options_test.rb +38 -0
- data/test/fogli_test.rb +16 -0
- data/test/test_helper.rb +14 -0
- metadata +150 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
module Fogli
|
2
|
+
class FacebookObject < FacebookGraph
|
3
|
+
# Allows for properties to be set on an object. A property is
|
4
|
+
# accessible instance data which is parsed from the JSON data blob
|
5
|
+
# which is returned by Graph API calls.
|
6
|
+
module Properties
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# Defines a property on the object. These properties map
|
13
|
+
# directly to keys in the returned JSON from the Facebook
|
14
|
+
# Graph API.
|
15
|
+
def property(*names)
|
16
|
+
options = names.last
|
17
|
+
options = {} if !options.kind_of?(Hash)
|
18
|
+
|
19
|
+
names.each do |name|
|
20
|
+
next if name.kind_of?(Hash)
|
21
|
+
name = name.to_s.downcase.to_sym
|
22
|
+
properties[name] = options
|
23
|
+
|
24
|
+
# Create a method for reading the property.
|
25
|
+
define_method(name) { read_property(name) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the properties associated with this facebook
|
30
|
+
# object.
|
31
|
+
#
|
32
|
+
# @return [Hash]
|
33
|
+
def properties
|
34
|
+
@_properties ||= {}
|
35
|
+
end
|
36
|
+
|
37
|
+
# Propagates properties to another class. This is used
|
38
|
+
# internally for making sure that subclasses get all the
|
39
|
+
# properties associated with the parent classes.
|
40
|
+
def propagate_properties(klass)
|
41
|
+
properties.each { |n, o| klass.property(n, o) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Populates the properties with the given hash of data. This
|
46
|
+
# method also handles parsing non-primitive data such as other
|
47
|
+
# facebook objects.
|
48
|
+
def populate_properties(data)
|
49
|
+
property_values.clear
|
50
|
+
|
51
|
+
self.class.properties.keys.each do |name|
|
52
|
+
# Try both the string and symbol lookup to get the item
|
53
|
+
# associated with a key
|
54
|
+
item = data[name.to_s] || data [name]
|
55
|
+
|
56
|
+
if item.is_a?(Hash)
|
57
|
+
# For now, assume its a NamedObject. In the future, we'll be
|
58
|
+
# more careful.
|
59
|
+
item = NamedObject.new(item.merge(:_loaded => true))
|
60
|
+
end
|
61
|
+
|
62
|
+
property_values[name] = item
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Reads a property.
|
69
|
+
def read_property(name)
|
70
|
+
value = property_values[name]
|
71
|
+
value = value.name if value.is_a?(NamedObject)
|
72
|
+
value
|
73
|
+
end
|
74
|
+
|
75
|
+
# Stores the values of the variables properties
|
76
|
+
def property_values
|
77
|
+
@_property_values ||= {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Fogli
|
2
|
+
class FacebookObject < FacebookGraph
|
3
|
+
# Includable module which has the scope methods to properly
|
4
|
+
# instantiate a {ConnectionScope}.
|
5
|
+
module ScopeMethods
|
6
|
+
# The various scoping types. Each method takes a single value
|
7
|
+
# associated with it, which is used to build up a new scope.
|
8
|
+
[:limit, :offset, :until, :since].each do |scope|
|
9
|
+
define_method(scope) do |value|
|
10
|
+
ConnectionScope.new(*_scope_initializer_args(scope, value))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Scope the connection by the fields you're interested in. By
|
15
|
+
# default, the connection will load all the possible fields for
|
16
|
+
# the connection, which can result in a fairly large overhead if
|
17
|
+
# you're only interested in the `first_name`, for example.
|
18
|
+
#
|
19
|
+
# The parameter to this method can be almost anything which can
|
20
|
+
# intuitively be converted down to either a string or
|
21
|
+
# array. Some example uses follow (admittingly some of these are
|
22
|
+
# a bit pathological, but they're meant to showcase the
|
23
|
+
# flexibility of the method):
|
24
|
+
#
|
25
|
+
# fields("name,birthday,last_name")
|
26
|
+
# fields(:name, :birthday, :last_name)
|
27
|
+
# fields([:name, "birthday"], :last_name)
|
28
|
+
# fields("name,birthday", ["last_name"])
|
29
|
+
#
|
30
|
+
def fields(*fields)
|
31
|
+
ConnectionScope.new(*_scope_initializer_args(:fields, fields.flatten.join(",")))
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the proper set of arguments to send to the
|
35
|
+
# {ConnectionScope} initializer given the scope and value.
|
36
|
+
#
|
37
|
+
# @param [Symbol] scope The scope option.
|
38
|
+
# @param [Object] value The value for the scope.
|
39
|
+
# @return [Array]
|
40
|
+
def _scope_initializer_args(scope, value)
|
41
|
+
if self.is_a?(ConnectionScope)
|
42
|
+
[proxy, options.merge(scope => value)]
|
43
|
+
else
|
44
|
+
[self, {scope => value}]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/fogli/group.rb
ADDED
data/lib/fogli/link.rb
ADDED
data/lib/fogli/note.rb
ADDED
data/lib/fogli/oauth.rb
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Fogli
|
4
|
+
# Handles OAuth authorization with Facebook to obtain an access key
|
5
|
+
# which can then be used to retrieve authorized data via the
|
6
|
+
# Facebook Open Graph API.
|
7
|
+
#
|
8
|
+
# # Requesting an Access Token
|
9
|
+
#
|
10
|
+
# Requesting an access token is a multi-step process:
|
11
|
+
#
|
12
|
+
# 1. Redirect the user to an authorization URL on the Facebook
|
13
|
+
# domain.
|
14
|
+
# 2. After authorizing, Facebook redirects the user back to your
|
15
|
+
# domain, with a verification string as the `code` GET parameter.
|
16
|
+
# 3. Request the access token from Facebook using the `code` GET
|
17
|
+
# parameter and the client ID and secret key given by Facebook
|
18
|
+
# during application creation.
|
19
|
+
#
|
20
|
+
# Fogli simplifies this into two separate API calls:
|
21
|
+
#
|
22
|
+
# 1. First call {authorize} to get the authorization URL.
|
23
|
+
# 2. Call {access_token} to get the access token.
|
24
|
+
#
|
25
|
+
# # Persisting an Access Token
|
26
|
+
#
|
27
|
+
# After retrieving an access token, it should be persisted into a
|
28
|
+
# cookie or some sort of session store. On subsequent requests, set
|
29
|
+
# the access token on the {Fogli} object:
|
30
|
+
#
|
31
|
+
# Fogli.access_token = cookies[:access_token]
|
32
|
+
#
|
33
|
+
# Fogli will then automatically use the access token on all API
|
34
|
+
# requests to retrieve authorized data.
|
35
|
+
#
|
36
|
+
# # Examples
|
37
|
+
#
|
38
|
+
# The following is a full length example based on
|
39
|
+
# [Sinatra](http://www.sinatrarb.com) syntax of authorizing a
|
40
|
+
# user. For examples on the wide variety of options that each method
|
41
|
+
# takes, please view {authorize} and {access_token}.
|
42
|
+
#
|
43
|
+
# require 'sinatra'
|
44
|
+
# reqiure 'fogli'
|
45
|
+
#
|
46
|
+
# # Set Fogli Configuration
|
47
|
+
# Fogli.client_id = "..."
|
48
|
+
# Fogli.client_secret = "..."
|
49
|
+
# Fogli.redirect_uri = "http://localhost:4567/my_email"
|
50
|
+
#
|
51
|
+
# get "/" do
|
52
|
+
# # Redirect user to authorization URL
|
53
|
+
# redirect_to Fogli::OAuth.authorize(:scope => "email")
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# get "/my_email" do
|
57
|
+
# # Store away the access token. In reality, this should be
|
58
|
+
# # persisted to some session storage (cookies, db, etc.)
|
59
|
+
# Fogli.access_token = Fogli::OAuth.access_token(:code => params[:code])
|
60
|
+
#
|
61
|
+
# # Output the current user's email
|
62
|
+
# User[:me].email
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
class OAuth < FacebookGraph
|
66
|
+
AUTHORIZE_URI = "http://graph.facebook.com/oauth/authorize?client_id=%s&redirect_uri=%s"
|
67
|
+
|
68
|
+
extend Util::Options
|
69
|
+
|
70
|
+
# Returns the authorization URL to redirect the user to in order
|
71
|
+
# to get an access token. This method takes a hash as its sole
|
72
|
+
# argument, and has the following possible keys:
|
73
|
+
#
|
74
|
+
# * `:redirect_uri` (optional) - The URI to redirect to after
|
75
|
+
# the user authorizes. This URI will be given a single parameter
|
76
|
+
# `code` as a GET variable. This should be passed into
|
77
|
+
# {access_token} to finally retrieve the access token.
|
78
|
+
# * `:client_id` (optional) - The client ID of your Facebook
|
79
|
+
# application
|
80
|
+
# * `:scope` (optional) - Specifies additional [extended
|
81
|
+
# permissions](http://developers.facebook.com/docs/authentication/permissions)
|
82
|
+
# to request with the authorization.
|
83
|
+
#
|
84
|
+
# # Examples
|
85
|
+
#
|
86
|
+
# ## Requesting a Basic Authorize URL
|
87
|
+
#
|
88
|
+
# With options in the arguments:
|
89
|
+
#
|
90
|
+
# Fogli::OAuth.authorize(:client_id => "...",
|
91
|
+
# :redirect_uri => "...")
|
92
|
+
#
|
93
|
+
# With statically set options:
|
94
|
+
#
|
95
|
+
# Fogli.client_id = "..."
|
96
|
+
# Fogli.redirect_uri = "..."
|
97
|
+
# redirect_to Fogli::OAuth.authorize
|
98
|
+
#
|
99
|
+
# ## Requesting Extended Permissions
|
100
|
+
#
|
101
|
+
# Fogli::OAuth.authorize(:scope => ["user_email", "user_photos"])
|
102
|
+
#
|
103
|
+
# @param [Hash] options Described above.
|
104
|
+
# @return [String] The authorization URL to redirect the user to.
|
105
|
+
def self.authorize(options=nil)
|
106
|
+
defaults = {
|
107
|
+
:client_id => Fogli.client_id,
|
108
|
+
:redirect_uri => Fogli.redirect_uri,
|
109
|
+
}
|
110
|
+
|
111
|
+
options = verify_options(options, {
|
112
|
+
:required_keys => defaults.keys,
|
113
|
+
:valid_keys => [defaults.keys, :scope].flatten,
|
114
|
+
:default => defaults
|
115
|
+
})
|
116
|
+
result = AUTHORIZE_URI % [options[:client_id], CGI.escape(options[:redirect_uri])]
|
117
|
+
if options[:scope]
|
118
|
+
options[:scope] = [options[:scope]] if !options.is_a?(Array)
|
119
|
+
result += "&scope=#{options[:scope].join(",")}"
|
120
|
+
end
|
121
|
+
|
122
|
+
result
|
123
|
+
end
|
124
|
+
|
125
|
+
# Exchanges a verfication code for an access token. After
|
126
|
+
# redirecting a user to the {authorize} URL, the user is
|
127
|
+
# redirected back with the `code` GET parameter. This parameter
|
128
|
+
# along with the `redirect_uri` should get passed into this
|
129
|
+
# method, which will then return the access token. The possible
|
130
|
+
# keys for the arguments hash are as follows:
|
131
|
+
#
|
132
|
+
# * `:redirect_uri` (**required**) - The URI which the user was
|
133
|
+
# redirected to with the `code` param.
|
134
|
+
# * `:code` (**required**) - The code which was given as a GET
|
135
|
+
# param by facebook.
|
136
|
+
# * `:client_id` (optional) - The client ID of your facebook
|
137
|
+
# application. If this is not specified here, it must be set via
|
138
|
+
# `Fogli.client_id=`
|
139
|
+
# * `:client_secret` (optional) - The client secret key of your
|
140
|
+
# facebook application. If this is not specified here, it must be
|
141
|
+
# set via `Fogli.client_secret=`.
|
142
|
+
#
|
143
|
+
# The return value is the access token. This token should be
|
144
|
+
# persisted in a cookie or some other session store, so it can be
|
145
|
+
# used on subsequent requests. The token can be set via
|
146
|
+
# `Fogli.access_token=`, which will cause all API calls to use the
|
147
|
+
# specified access token.
|
148
|
+
#
|
149
|
+
# @param [Hash] options Described above.
|
150
|
+
# @return [String] Access token
|
151
|
+
def self.access_token(options)
|
152
|
+
defaults = {
|
153
|
+
:client_id => Fogli.client_id,
|
154
|
+
:client_secret => Fogli.client_secret,
|
155
|
+
:redirect_uri => Fogli.redirect_uri,
|
156
|
+
:code => nil
|
157
|
+
}
|
158
|
+
|
159
|
+
options = verify_options(options, {
|
160
|
+
:required_keys => defaults.keys,
|
161
|
+
:default => defaults
|
162
|
+
})
|
163
|
+
result = get("/oauth/access_token", options)
|
164
|
+
result.split(/(&|=)/)[2]
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
data/lib/fogli/page.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Fogli
|
2
|
+
class Page < FacebookObject
|
3
|
+
property :name, :category
|
4
|
+
|
5
|
+
connection :feed, :tagged, :posts, :class => :Post
|
6
|
+
connection :links, :class => :Link
|
7
|
+
connection :photos, :class => :Photo
|
8
|
+
connection :groups, :class => :Group
|
9
|
+
connection :albums, :class => :Album
|
10
|
+
connection :statuses, :class => :Status
|
11
|
+
connection :videos, :class => :Video
|
12
|
+
connection :notes, :class => :Note
|
13
|
+
connection :events, :class => :Event
|
14
|
+
end
|
15
|
+
end
|
data/lib/fogli/photo.rb
ADDED
data/lib/fogli/post.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Fogli
|
2
|
+
class Post < FacebookObject
|
3
|
+
property :from, :to, :message, :link, :name, :caption, :description, :type,
|
4
|
+
:source, :icon, :attribution, :actions, :likes, :created_time
|
5
|
+
|
6
|
+
connection :comments, :class => :Comment
|
7
|
+
|
8
|
+
# Like a post. This method requires authorization.
|
9
|
+
def like!
|
10
|
+
post("/likes")
|
11
|
+
end
|
12
|
+
|
13
|
+
# Unlike a post. This method requires authorization.
|
14
|
+
def unlike!
|
15
|
+
post("/likes", :method => :delete)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/fogli/status.rb
ADDED
data/lib/fogli/user.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Fogli
|
2
|
+
class User < FacebookObject
|
3
|
+
property :first_name, :last_name, :name, :link, :about,
|
4
|
+
:birthday, :work, :education, :email, :website,
|
5
|
+
:hometown, :location, :gender, :interested_in,
|
6
|
+
:meeting_for, :relationship_status, :religion,
|
7
|
+
:political, :verified, :significant_other, :timezone
|
8
|
+
|
9
|
+
connection :friends, :class => :User
|
10
|
+
connection :home, :feed, :tagged, :posts, :class => :Post
|
11
|
+
connection :activities, :interests, :music, :books, :movies,
|
12
|
+
:television, :class => :CategorizedObject
|
13
|
+
connection :likes, :class => :Page
|
14
|
+
connection :photos, :class => :Photo
|
15
|
+
connection :albums, :class => :Album
|
16
|
+
connection :videos, :class => :Video
|
17
|
+
connection :groups, :class => :Group
|
18
|
+
connection :statuses, :class => :Status
|
19
|
+
connection :links, :class => :Link
|
20
|
+
connection :notes, :class => :Note
|
21
|
+
connection :events, :class => :Event
|
22
|
+
|
23
|
+
# Checks if a user is authorized. This returns a true or false
|
24
|
+
# depending if we're allowed to make authorized calls yet. If this
|
25
|
+
# returns false, the user must be authorized using the {OAuth}
|
26
|
+
# class via a simple two step process.
|
27
|
+
#
|
28
|
+
# @return [Boolean]
|
29
|
+
def self.authorized?
|
30
|
+
User.find(:me, :fields => :id).load!
|
31
|
+
true
|
32
|
+
rescue Fogli::Exception
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Taken directly from Rails 3 core. I claim no authorship of this
|
2
|
+
# code. Fogli first attempts to load the actual ActiveSupport code
|
3
|
+
# if it exists, but falls back to this implementation. This way,
|
4
|
+
# running Fogli with rails shouldn't conflict in any way.
|
5
|
+
|
6
|
+
#
|
7
|
+
# Extends the module object with module and instance accessors for class attributes,
|
8
|
+
# just like the native attr* accessors for instance attributes.
|
9
|
+
#
|
10
|
+
# module AppConfiguration
|
11
|
+
# mattr_accessor :google_api_key
|
12
|
+
# self.google_api_key = "123456789"
|
13
|
+
#
|
14
|
+
# mattr_accessor :paypal_url
|
15
|
+
# self.paypal_url = "www.sandbox.paypal.com"
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# AppConfiguration.google_api_key = "overriding the api key!"
|
19
|
+
class Module
|
20
|
+
def mattr_reader(*syms)
|
21
|
+
syms.each do |sym|
|
22
|
+
next if sym.is_a?(Hash)
|
23
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
24
|
+
unless defined? @@#{sym}
|
25
|
+
@@#{sym} = nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.#{sym}
|
29
|
+
@@#{sym}
|
30
|
+
end
|
31
|
+
|
32
|
+
def #{sym}
|
33
|
+
@@#{sym}
|
34
|
+
end
|
35
|
+
EOS
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def mattr_writer(*syms)
|
40
|
+
syms.each do |sym|
|
41
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
42
|
+
unless defined? @@#{sym}
|
43
|
+
@@#{sym} = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.#{sym}=(obj)
|
47
|
+
@@#{sym} = obj
|
48
|
+
end
|
49
|
+
|
50
|
+
def #{sym}=(obj)
|
51
|
+
@@#{sym} = obj
|
52
|
+
end
|
53
|
+
EOS
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def mattr_accessor(*syms)
|
58
|
+
mattr_reader(*syms)
|
59
|
+
mattr_writer(*syms)
|
60
|
+
end
|
61
|
+
end
|