require 'rubygems' require 'socket' require 'digest' require 'json' require 'cgi' module Facebook class Error < StandardError end class APIProxy instance_methods.each { |m| undef_method m unless m =~ /^__/ } def initialize name, client @name, @client = name, client end def method_missing method, opts = {} @client.call "#{@name}.#{method}", opts end end class Client include Ramaze::Trinity if defined? Ramaze def initialize keepalive = true @keepalive = keepalive @proxies = {} end %w[ auth fbml feed fql friends notifications profile users pages events groups photos marketplace ].each do |n| define_method(n){ @proxies[n] ||= APIProxy.new(n, self) } end def call method, opts = {} args = { :api_key => KEY, :call_id => Time.now.to_f, :format => 'JSON', :v => '1.0', :session_key => params[:session_key] || SESSION, :method => method }.merge(opts).map{ |k,v| "#{k}=" + case v when Hash v.to_json when Array v.join(',') else v.to_s end }.sort data = Array["sig=#{Digest::MD5.hexdigest(args.join+SECRET)}", *args].join('&') begin ret = post(data) rescue Errno::ECONNRESET, Errno::EPIPE @server = connect retry end while ret.empty? and @server = connect ret = case when ret == 'true'; true when ret == 'false'; false when ret[0..0] == '"'; ret[1..-2] else begin JSON::parse(ret) rescue JSON::ParserError puts "Error parsing #{ret.inspect}" raise end end unless method == 'fql.query' ret = ret.first if ret.is_a? Array and ret.size == 1 and ret.first.is_a? Hash end raise Facebook::Error, ret['error_msg'] if ret.is_a? Hash and ret['error_code'] ret ensure unless @keepalive @server.close @server = nil end end def valid? return false unless respond_to?(:request) and not request['fb_sig'].nil? request['facebook.valid?'] ||= \ request['fb_sig'] == Digest::MD5.hexdigest(request.params.map{|k,v| "#{$1}=#{v}" if k =~ /^fb_sig_(.+)$/ }.compact.sort.join+SECRET) end def [] key params[key] end def redirect url url[0,0] = URL unless url =~ /^http/ if respond_to?(:response) response.build "" throw :respond else "" end end def addurl goto = '/' "http://apps.facebook.com/add.php?api_key=#{KEY}&next=#{CGI.escape '?next='+goto}" end def params return {} unless valid? request['facebook'] ||= \ request.params.inject({}) { |h,(k,v)| next h unless k =~ /^fb_sig_(.+)$/ k = $1.to_sym case k.to_s when 'friends' h[k] = v.split(',').map{|e|e.to_i} when /time$/ h[k] = Time.at(v.to_f) when 'expires' v = v.to_i h[k] = v>0 ? Time.at(v) : v when 'user' h[k] = v.to_i when /^(position_|in_|is_|added)/ h[k] = v=='1' else h[k] = v end h } end private def connect @socket.close if @socket TCPSocket.new('api.facebook.com', 80) end def post data @server ||= connect @server.puts "POST /restserver.php HTTP/1.1\r\n" @server.puts "Host: api.facebook.com\r\n" @server.puts "Connection: keep-alive\r\n" if @keepalive @server.puts "Content-Type: application/x-www-form-urlencoded\r\n" @server.puts "Content-Length: #{data.length}\r\n" @server.puts "\r\n#{data}\r\n" @server.puts "\r\n\r\n" buf = '' while @server.gets if $_ == "\r\n" @server.gets if $_.strip! == '0' @server.gets break end buf << @server.read($_.to_i(16)) end end buf end end end