PageRenderTime 45ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/fgraph.rb

http://github.com/jugend/fgraph
Ruby | 401 lines | 197 code | 43 blank | 161 comment | 10 complexity | d3e919a8a8a1fbd151cf29e6a0aaad3e MD5 | raw file
  1. require 'httparty'
  2. require 'cgi'
  3. require 'fgraph/client'
  4. module FGraph
  5. include HTTParty
  6. base_uri 'https://graph.facebook.com'
  7. format :json
  8. # Facebook Error
  9. class FacebookError < StandardError
  10. attr_reader :data
  11. def initialize(data)
  12. @data = data
  13. super("(#{data['type']}) #{data['message']}")
  14. end
  15. end
  16. class QueryParseError < FacebookError; end
  17. class GraphMethodError < FacebookError; end
  18. class OAuthError < FacebookError; end
  19. class OAuthAccessTokenError < OAuthError; end
  20. # Collection objects for Graph response with array data.
  21. #
  22. class Collection < Array
  23. attr_reader :next_url, :previous_url, :next_options, :previous_options
  24. # Initialize Facebook response object with 'data' array value.
  25. def initialize(response)
  26. return super unless response
  27. super(response['data'])
  28. paging = response['paging'] || {}
  29. self.next_url = paging['next']
  30. self.previous_url = paging['previous']
  31. end
  32. def next_url=(url)
  33. @next_url = url
  34. @next_options = self.url_options(url)
  35. end
  36. def previous_url=(url)
  37. @previous_url = url
  38. @previous_options = self.url_options(url)
  39. end
  40. def first?
  41. @previous_url.blank? and not @next_url.blank?
  42. end
  43. def next?
  44. not @next_url.blank?
  45. end
  46. def previous?
  47. not @previous_url.blank?
  48. end
  49. def url_options(url)
  50. return unless url
  51. # Note: FB pass access token with '|' character, which cause URI::InvalidURIError
  52. uri = URI.parse(url.gsub('|', '%7C'))
  53. options = {}
  54. uri.query.split('&').each do |param_set|
  55. param_set = param_set.split('=')
  56. options[param_set[0]] = CGI.unescape(param_set[1])
  57. end
  58. options
  59. end
  60. end
  61. class << self
  62. attr_accessor :config
  63. # Single object query.
  64. #
  65. # # Users: https://graph.facebook.com/btaylor (Bret Taylor)
  66. # FGraph.object('btaylor')
  67. #
  68. # # Pages: https://graph.facebook.com/cocacola (Coca-Cola page)
  69. # FGraph.object('cocacola')
  70. #
  71. # # Fields selection with metadata
  72. # FGraph.object('btaylor', :fields => 'id,name,picture', :metadata => 1)
  73. #
  74. # # Page photos
  75. # FGraph.object('/cocacola/photos')
  76. # photos = FGraph.object_photos('cocacola')
  77. #
  78. # # Support id from object hash
  79. # friend = { 'name' => 'Mark Zuckerberg', 'id' => '4'}
  80. # friend_details = FGraph.object(friend)
  81. def object(id, options={})
  82. id = self.get_id(id)
  83. perform_get("/#{id}", options)
  84. end
  85. # call-seq:
  86. # FGraph.objects(id, id)
  87. # FGraph.objects(id, id, options_hash)
  88. #
  89. # Multiple objects query.
  90. #
  91. # # Multiple users select: https://graph.facebook.com?ids=arjun,vernal
  92. # FGraph.objects('arjun', 'vernel')
  93. #
  94. # # Filter fields: https://graph.facebook.com?ids=arjun,vernal&fields=id,name,picture
  95. # FGraph.objects('arjun', 'vernel', :fields => 'id,name,picture')
  96. #
  97. def objects(*args)
  98. options = args.last.is_a?(Hash) ? args.pop : {}
  99. # If first input before option is an array
  100. if args.length == 1 and args.first.is_a?(Array)
  101. args = args.first.map do |arg|
  102. self.get_id(arg)
  103. end
  104. end
  105. options = options.merge(:ids => args.join(','))
  106. perform_get("/", options)
  107. end
  108. # call-seq:
  109. # FGraph.me(category)
  110. # FGraph.me(category, options_hash)
  111. #
  112. # Returns current user object details.
  113. #
  114. # <tt>category</tt> - <tt>friends|home|feed|likes|movies|books|notes|photos|videos|events|groups</tt>
  115. #
  116. # # Current user: https://graph.facebook.com/me?access_token=...
  117. # FGraph.me(:access_token => '...')
  118. #
  119. # # Current user's friends: https://graph.facebook.com/me/friends?access_token=...
  120. # FGraph.me('friends', :access_token => '...')
  121. # FGraph.me_friends(:access_token => '...')
  122. #
  123. def me(*args)
  124. options = args.last.is_a?(Hash) ? args.pop : {}
  125. category = args.shift
  126. path = "me"
  127. path += "/#{category}" unless category.blank?
  128. self.object(path, options)
  129. end
  130. # Request authorization from Facebok to fetch private data in the profile or permission to publish on a
  131. # user's behalf. Returns Oauth Authorization URL, redirect to this URL to allow user to authorize your
  132. # application from Facebook.
  133. #
  134. # <tt>client_id</tt> - Application ID
  135. # <tt>redirect_uri</tt> - Needs to begin with your app's Connect URL. For instance, if your Connect URL
  136. # is http://www.example.com then your redirect URI could be http://www.example.com/oauth_redirect.
  137. # <tt>scope (optional)</tt> -
  138. #
  139. # ==== Options
  140. # * <tt>scope</tt> - Extended permission required to fetch private data or request permision to
  141. # publish to Facebook on a user's behalf.
  142. # * <tt>display</tt> - Other display type for authentication/authorization form, i.e. popup, touch.
  143. #
  144. # # https://graph.facebook.com/oauth/authorize?
  145. # # client_id=...&
  146. # # redirect_uri=http://www.example.com/oauth_redirect&
  147. # # scope=publish_stream
  148. #
  149. # FGraph.oauth_authorize_url('[client id]', 'http://www.example.com/oauth_redirect', :scope =>
  150. # 'publish_stream')
  151. #
  152. def oauth_authorize_url(client_id, redirect_uri, options={})
  153. self.format_url('/oauth/authorize', {
  154. :client_id => client_id,
  155. :redirect_uri => redirect_uri
  156. }.merge(options))
  157. end
  158. # Return OAuth access_token. There are two types of access token, user access token and application
  159. # access token.
  160. #
  161. # User access_token requires <tt>code</tt> and and <tt>redirect_uri</tt> options. <tt>code</tt> is
  162. # the autorization code appended as query string to redirect URI when accessing oauth authorization URL.
  163. #
  164. # # https://graph.facebook.com/oauth/access_token?
  165. # # client_id=...&
  166. # # client_secret=...&
  167. # # redirect_uri=http://www.example.com/oauth_redirect&
  168. # # code=...
  169. # FGraph.oauth_access_token('[client id]', '[client secret]',
  170. # :redirect_uri => 'http://www.example.com/oauth_redirect',
  171. # :code => '[authorization code]')
  172. #
  173. # Application access token requires <tt>:type => 'client_cred'</td> option. Used to access application
  174. # insights data.
  175. #
  176. # # https://graph.facebook.com/oauth/access_token?
  177. # # client_id=...&
  178. # # client_secret=...&
  179. # # type=client_cred
  180. # FGraph.oauth_access_token('[client id]', '[client secret]', :type => 'client_cred')
  181. #
  182. def oauth_access_token(client_id, client_secret, options={})
  183. url = self.format_url('/oauth/access_token', {
  184. :client_id => client_id,
  185. :client_secret => client_secret,
  186. :redirect_uri => ''
  187. }.merge(options || {}))
  188. response = self.perform_get(url)
  189. response_hash = {}
  190. response.split('&').each do |value|
  191. value_pair = value.split('=')
  192. response_hash[value_pair[0]] = value_pair[1]
  193. end
  194. response_hash
  195. end
  196. # Shortcut to retrieve application access token.
  197. def oauth_app_access_token(client_id, client_secret)
  198. self.oauth_access_token(client_id, client_secret, :type => 'client_cred')
  199. end
  200. # Publish to Facebook, you would need to be authorized and provide access token.
  201. #
  202. # # Post to user's feed.
  203. # # curl -F 'access_token=...' \
  204. # # -F 'message=Hello, Arjun. I like this new API.' \
  205. # # https://graph.facebook.com/arjun/feed
  206. # FGraph.publish('arjun/feed', :message => 'Hello, Arjun. I like this new API.',
  207. # :access_token => '...')
  208. # FGraph.publish_feed('arjun', :message => '...', :access_token => '...')
  209. # FGraph.publish_feed('me', ':message => '...', :access_token => '...')
  210. #
  211. # ==== Options
  212. #
  213. # Method Description Options
  214. # -------------------------------------------------------------------------------------
  215. # /PROFILE_ID/feed write to the given profile's feed/wall :message, :picture,
  216. # :link, :name, description
  217. # /POST_ID/comments comment on the given post :message
  218. # /POST_ID/likes like the given post none
  219. # /PROFILE_ID/notes write a note on the given profile :message, :subject
  220. # /PROFILE_ID/links write a link on the given profile :link, :message
  221. # /EVENT_ID/attending attend the given event none
  222. # /EVENT_ID/maybe maybe attend the given event none
  223. # /EVENT_ID/declined decline the given event none
  224. #
  225. def publish(id, options={})
  226. id = self.get_id(id)
  227. self.perform_post("/#{id}", options)
  228. end
  229. # Delete objects in the graph.
  230. #
  231. # # DELETE https://graph.facebook.com/ID?access_token=... HTTP/1.1
  232. #
  233. # FGraph.remove('[ID]')
  234. # FGraph.remove('[ID]/likes')
  235. # FGraph.remove_likes('[ID]')
  236. #
  237. def remove(id, options={})
  238. id = self.get_id(id)
  239. self.perform_delete("/#{id}", options)
  240. end
  241. # Search over all public objects in the social graph.
  242. #
  243. # # https://graph.facebook.com/search?q=watermelon&type=post
  244. # FGraph.search('watermelon', :type => 'post')
  245. # FGraph.search_post('watermelon')
  246. #
  247. # ==== Options
  248. # * <tt>type</tt> - <tt>album|event|group|link|note|page|photo|post|status|user|video</tt>
  249. # * <tt>limit</tt> - max no of records
  250. # * <tt>offset</tt> - offset
  251. # * <tt>until</tt> - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
  252. def search(query, options={})
  253. self.perform_get("/search", {
  254. :q => query
  255. }.merge(options|| {}))
  256. end
  257. # Download insights data for your application.
  258. #
  259. # # https://graph.facebook.com/[client_id]/insights?access_token=...
  260. # FGraph.insights('[client_id]', '[app_access_token]')
  261. #
  262. # # https://graph.facebook.com/[client_id]/insights/application_api_call/day?access_token=...
  263. # FGraph.insights('[client_id]', '[app_access_token]', :metric_path => 'application_api_call/day')
  264. #
  265. # ==== Options
  266. # * <tt>metric_path</tt> - e.g. application_api_calls/day
  267. # * <tt>since</tt> - since (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
  268. # * <tt>until</tt> - until (a unix timestamp or any date accepted by strtotime, e.g. yesterday)
  269. def insights(client_id, app_access_token, options={})
  270. metric_path = options.delete(:metric_path)
  271. path = "/#{client_id}/insights"
  272. path += "/#{metric_path}" if metric_path
  273. self.perform_get(path, {
  274. :access_token => app_access_token
  275. }.merge(options || {}))
  276. end
  277. def perform_get(uri, options = {})
  278. handle_response(get(uri, {:query => options}))
  279. end
  280. def perform_post(uri, options = {})
  281. handle_response(post(uri, {:body => options}))
  282. end
  283. def perform_delete(uri, options = {})
  284. handle_response(delete(uri, {:body => options}))
  285. end
  286. def handle_response(response)
  287. unless response['error']
  288. return FGraph::Collection.new(response) if response['data']
  289. response
  290. else
  291. case response['error']['type']
  292. when 'QueryParseException'
  293. raise QueryParseError, response['error']
  294. when 'GraphMethodException'
  295. raise GraphMethodError, response['error']
  296. when 'OAuthException'
  297. raise OAuthError, response['error']
  298. when 'OAuthAccessTokenException'
  299. raise OAuthAccessTokenError, response['error']
  300. else
  301. raise FacebookError, response['error']
  302. end
  303. end
  304. end
  305. def format_url(path, options={})
  306. url = self.base_uri.dup
  307. url << path
  308. unless options.blank?
  309. url << "?"
  310. option_count = 0
  311. stringified_options = {}
  312. options.each do |key, value|
  313. stringified_options[key.to_s] = value
  314. end
  315. options = stringified_options
  316. options.each do |option|
  317. next unless option[0]
  318. url << "&" if option_count > 0
  319. url << "#{option[0]}=#{CGI.escape(option[1].to_s)}"
  320. option_count += 1
  321. end
  322. end
  323. url
  324. end
  325. def method_missing(name, *args, &block)
  326. names = name.to_s.split('_')
  327. super unless names.length > 1
  328. case names.shift
  329. when 'object'
  330. # object_photos
  331. self.object("#{args[0]}/#{names[0]}", args[1])
  332. when 'me'
  333. # me_photos
  334. self.me(names[0], args[0])
  335. when 'publish'
  336. # publish_feed(id)
  337. self.publish("#{args[0]}/#{names[0]}", args[1])
  338. when 'remove'
  339. # remove_feed(id)
  340. self.remove("#{args[0]}/#{names[0]}", args[1])
  341. when 'search'
  342. # search_user(query)
  343. options = args[1] || {}
  344. options[:type] = names[0]
  345. self.search(args[0], options)
  346. else
  347. super
  348. end
  349. end
  350. # Return ID['id'] if ID is a hash object
  351. #
  352. def get_id(id)
  353. return unless id
  354. id = id['id'] || id[:id] if id.is_a?(Hash)
  355. id
  356. end
  357. end
  358. end