fogli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|