Skip to content

Instantly share code, notes, and snippets.

Created January 2, 2010 07:02
Show Gist options
  • Save anonymous/267412 to your computer and use it in GitHub Desktop.
Save anonymous/267412 to your computer and use it in GitHub Desktop.
basic setup to get facebook to publish to newsfeed using facebooker gem + rails
not ideal, but quick and dirty, it gets you started quickly
class FacebookerPublisher < Facebooker::Rails::Publisher
def publish_stream(user_with_session_to_use, user_to_update, params=nil)
send_as :publish_stream
from user_with_session_to_use
target user_to_update
#attachment params[:attachment]
message "hello"#params[:message]
#action_links params[:action_links]
end
end
module Facebooker
module Rails
# ActionMailer like module for publishing Facbook messages
#
# To use, create a subclass and define methods
# Each method should start by calling send_as to specify the type of message
# Valid options are :email and :notification, :user_action, :profile, :ref, :publish_stream
#
#
# Below is an example of each type
#
# class TestPublisher < Facebooker::Rails::Publisher
# # The new message templates are supported as well
# # First, create a method that contains your templates:
# # You may include multiple one line story templates and short story templates
# # but only one full story template
# # Your most specific template should be first
# #
# # Before using, you must register your template by calling register. For this example
# # You would call TestPublisher.register_publish_action
# # Registering the template will store the template id returned from Facebook in the
# # facebook_templates table that is created when you create your first publisher
# def publish_action_template
# one_line_story_template "{*actor*} did stuff with {*friend*}"
# one_line_story_template "{*actor*} did stuff"
# short_story_template "{*actor*} has a title {*friend*}", render(:partial=>"short_body")
# short_story_template "{*actor*} has a title", render(:partial=>"short_body")
# full_story_template "{*actor*} has a title {*friend*}", render(:partial=>"full_body")
# action_links action_link("My text {*template_var*}","{*link_url*}")
# end
#
# # To send a registered template, you need to create a method to set the data
# # The publisher will look up the template id from the facebook_templates table
# def publish_action(f)
# send_as :user_action
# from f
# story_size SHORT # or ONE_LINE or FULL
# data :friend=>"Mike"
# end
#
#
# # Provide a from user to send a general notification
# # if from is nil, this will send an announcement
# def notification(to,f)
# send_as :notification
# recipients to
# from f
# fbml "Not"
# end
#
# def email(to,f)
# send_as :email
# recipients to
# from f
# title "Email"
# fbml 'text'
# text fbml
# end
# # This will render the profile in /users/profile.fbml.erb
# # it will set @user to user_to_update in the template
# # The mobile profile will be rendered from the app/views/test_publisher/_mobile.erb
# # template
# def profile_update(user_to_update,user_with_session_to_use)
# send_as :profile
# from user_with_session_to_use
# recipients user_to_update
# profile render(:file=>"users/profile.fbml.erb",:assigns=>{:user=>user_to_update})
# profile_action "A string"
# mobile_profile render(:partial=>"mobile",:assigns=>{:user=>user_to_update})
# end
#
# # Update the given handle ref with the content from a
# # template
# def ref_update(user)
# send_as :ref
# from user
# fbml render(:file=>"users/profile",:assigns=>{:user=>user_to_update})
# handle "a_ref_handle"
# end
#
# # Publish a post into the stream on the user's Wall and News Feed.
# def publish_stream(user_with_session_to_use, user_to_update, params)
# send_as :publish_stream
# from user_with_session_to_use
# target user_to_update
# attachment params[:attachment]
# message params[:message]
# action_links params[:action_links]
# end
#
#
# To send a message, use ActionMailer like semantics
# TestPublisher.deliver_action(@user)
#
# For testing, you may want to create an instance of the underlying message without sending it
# TestPublisher.create_action(@user)
# will create and return an instance of Facebooker::Feeds::Action
#
# Publisher makes many helpers available, including the linking and asset helpers
class Publisher
#story sizes from the Facebooker API
ONE_LINE=1
SHORT=2
FULL=4
def initialize
@from = nil
@full_story_template = nil
@recipients = nil
@controller = PublisherController.new(self)
end
def self.default_url_options
{:host => Facebooker.canvas_server_base + Facebooker.facebook_path_prefix}
end
def default_url_options
self.class.default_url_options
end
# use facebook options everywhere
def request_comes_from_facebook?
true
end
class FacebookTemplate < ::ActiveRecord::Base
cattr_accessor :template_cache
self.template_cache = {}
def self.inspect(*args)
"FacebookTemplate"
end
def template_changed?(hash)
if respond_to?(:content_hash)
content_hash != hash
else
false
end
end
def deactivate
Facebooker::Session.create.deactivate_template_bundle_by_id(self.bundle_id)
return true
rescue Facebooker::Session::TemplateBundleInvalid => e
return false
end
class << self
def register(klass,method)
publisher = setup_publisher(klass,method)
template_id = Facebooker::Session.create.register_template_bundle(publisher.one_line_story_templates,publisher.short_story_templates,publisher.full_story_template,publisher.action_links)
template = find_or_initialize_by_template_name(template_name(klass,method))
template.deactivate if template.bundle_id # deactivate old templates to avoid exceeding templates/app limit
template.bundle_id = template_id
template.content_hash = hashed_content(klass,method) if template.respond_to?(:content_hash)
template.save!
cache(klass,method,template)
template
end
def for_class_and_method(klass,method)
find_cached(klass,method)
end
def bundle_id_for_class_and_method(klass,method)
for_class_and_method(klass,method).bundle_id
end
def cache(klass,method,template)
template_cache[template_name(klass,method)] = template
end
def clear_cache!
self.template_cache = {}
end
def find_cached(klass,method)
template_cache[template_name(klass,method)] || find_in_db(klass,method)
end
def find_in_db(klass,method)
template = find_by_template_name(template_name(klass,method))
if template.nil? || template.template_changed?(hashed_content(klass, method))
template = register(klass,method)
end
template
end
def setup_publisher(klass,method)
publisher = klass.new
publisher.send method + '_template'
publisher
end
def hashed_content(klass, method)
publisher = setup_publisher(klass,method)
# sort the Hash elements (in the short_story and full_story) before generating MD5
Digest::MD5.hexdigest [publisher.one_line_story_templates,
(publisher.short_story_templates and publisher.short_story_templates.collect{|ss| ss.to_a.sort_by{|e| e[0].to_s}}),
(publisher.full_story_template and publisher.full_story_template.to_a.sort_by{|e| e[0].to_s})
].to_json
end
def template_name(klass,method)
"#{Facebooker.api_key}: #{klass.name}::#{method}"
end
end
end
class_inheritable_accessor :master_helper_module
attr_accessor :one_line_story_templates, :short_story_templates
attr_writer :action_links
cattr_accessor :skip_registry
self.skip_registry = false
class InvalidSender < StandardError; end
class UnknownBodyType < StandardError; end
class UnspecifiedBodyType < StandardError; end
class Email
attr_accessor :title
attr_accessor :text
attr_accessor :fbml
end
class Notification
attr_accessor :fbml
end
class Profile
attr_accessor :profile
attr_accessor :profile_action
attr_accessor :mobile_profile
attr_accessor :profile_main
end
class Ref
attr_accessor :handle
attr_accessor :fbml
end
class UserAction
attr_accessor :data
attr_reader :target_ids
attr_accessor :body_general
attr_accessor :template_id
attr_accessor :template_name
attr_accessor :story_size
def target_ids=(val)
@target_ids = val.is_a?(Array) ? val.join(",") : val
end
def data_hash
data||{}
end
end
class PublishStream
attr_accessor :target
attr_accessor :attachment
attr_accessor :action_links
attr_accessor :message
end
cattr_accessor :ignore_errors
attr_accessor :_body
def recipients(*args)
if args.size==0
@recipients
else
@recipients=args.first
end
end
def from(*args)
if args.size==0
@from
else
@from=args.first
end
end
def send_as(option)
self._body=case option
when :action
Facebooker::Feed::Action.new
when :story
Facebooker::Feed::Story.new
when :templatized_action
Facebooker::Feed::TemplatizedAction.new
when :notification
Notification.new
when :email
Email.new
when :profile
Profile.new
when :ref
Ref.new
when :user_action
UserAction.new
when :publish_stream
PublishStream.new
else
raise UnknownBodyType.new("Unknown type to publish")
end
end
def full_story_template(title=nil,body=nil,params={})
if title.nil?
@full_story_template
else
@full_story_template=params.merge(:template_title=>title, :template_body=>body)
end
end
def one_line_story_template(str)
@one_line_story_templates ||= []
@one_line_story_templates << str
end
def short_story_template(title,body,params={})
@short_story_templates ||= []
@short_story_templates << params.merge(:template_title=>title, :template_body=>body)
end
def action_links(*links)
if links.blank?
@action_links
else
@action_links = links
end
end
def method_missing(name,*args)
if args.size==1 and self._body.respond_to?("#{name}=")
self._body.send("#{name}=",*args)
elsif self._body.respond_to?(name)
self._body.send(name,*args)
else
super
end
end
# work around the fact that facebook cares about the order of the keys in the hash
class ImageHolder
attr_accessor :src,:href
def initialize(src,href)
self.src=src
self.href=href
end
def ==(other)
self.src == other.src && self.href == other.href
end
def to_json(*args)
"{\"src\":#{src.to_json}, \"href\":#{href.to_json}}"
end
end
def image(src,target)
ImageHolder.new(image_path(src),target.respond_to?(:to_str) ? target : url_for(target))
end
def action_link(text,target)
{:text=>text, :href=>target}
end
def requires_from_user?(from,body)
! (announcement_notification?(from,body) or ref_update?(body) or profile_update?(body))
end
def profile_update?(body)
body.is_a?(Profile)
end
def ref_update?(body)
body.is_a?(Ref)
end
def announcement_notification?(from,body)
from.nil? and body.is_a?(Notification)
end
def send_message(method)
@recipients = @recipients.is_a?(Array) ? @recipients : [@recipients]
if from.nil? and @recipients.size==1 and requires_from_user?(from,_body)
@from = @recipients.first
end
# notifications can
# omit the from address
raise InvalidSender.new("Sender must be a Facebooker::User") unless from.is_a?(Facebooker::User) || !requires_from_user?(from,_body)
case _body
when Facebooker::Feed::TemplatizedAction,Facebooker::Feed::Action
from.publish_action(_body)
when Facebooker::Feed::Story
@recipients.each {|r| r.publish_story(_body)}
when Notification
(from.nil? ? Facebooker::Session.create : from.session).send_notification(@recipients,_body.fbml)
when Email
from.session.send_email(@recipients,
_body.title,
_body.text,
_body.fbml)
when Profile
# If recipient and from aren't the same person, create a new user object using the
# userid from recipient and the session from from
@from = Facebooker::User.new(Facebooker::User.cast_to_facebook_id(@recipients.first),Facebooker::Session.create)
@from.set_profile_fbml(_body.profile, _body.mobile_profile, _body.profile_action, _body.profile_main)
when Ref
Facebooker::Session.create.server_cache.set_ref_handle(_body.handle,_body.fbml)
when UserAction
@from.session.publish_user_action(_body.template_id,_body.data_hash,_body.target_ids,_body.body_general,_body.story_size)
when PublishStream
@from.publish_to(_body.target, {:attachment => _body.attachment, :action_links => @action_links, :message => _body.message })
else
raise UnspecifiedBodyType.new("You must specify a valid send_as")
end
end
# nodoc
# needed for actionview
def logger
RAILS_DEFAULT_LOGGER
end
# nodoc
# delegate to action view. Set up assigns and render
def render(opts)
opts = opts.dup
body = opts.delete(:assigns) || {}
initialize_template_class(body.dup.merge(:controller=>self)).render(opts)
end
def initialize_template_class(assigns)
template_root = "#{RAILS_ROOT}/app/views"
controller_root = File.join(template_root,self.class.controller_path)
#only do this on Rails 2.1
if ActionController::Base.respond_to?(:append_view_path)
# only add the view path once
unless ActionController::Base.view_paths.include?(controller_root)
ActionController::Base.append_view_path(controller_root)
ActionController::Base.append_view_path(controller_root+"/..")
end
view_paths = ActionController::Base.view_paths
else
view_paths = [template_root, controller_root]
end
returning ActionView::Base.new(view_paths, assigns, self) do |template|
template.controller=self
template.extend(self.class.master_helper_module)
def template.request_comes_from_facebook?
true
end
end
end
self.master_helper_module = Module.new
self.master_helper_module.module_eval do
# url_helper delegates to @controller,
# so we need to define that in the template
# we make it point to the publisher
include ActionView::Helpers::UrlHelper
include ActionView::Helpers::TextHelper
include ActionView::Helpers::TagHelper
include ActionView::Helpers::FormHelper
include ActionView::Helpers::FormTagHelper
include ActionView::Helpers::AssetTagHelper
include ActionView::Helpers::NumberHelper
include Facebooker::Rails::Helpers
#define this for the publisher views
def protect_against_forgery?
@paf ||= ActionController::Base.new.send(:protect_against_forgery?)
end
# url_for calls in publishers tend to want full paths
def url_for(options = {})
super(options.kind_of?(Hash) ? {:only_path => false}.update(options) : options)
end
end
ActionController::Routing::Routes.named_routes.install(self.master_helper_module)
include self.master_helper_module
class <<self
def register_all_templates_on_all_applications
Facebooker.with_all_applications do
puts "Registering templates for #{Facebooker.api_key}"
register_all_templates
end
end
def register_all_templates
all_templates = instance_methods.grep(/_template$/) - %w(short_story_template full_story_template one_line_story_template)
all_templates.each do |template|
template_name=template.sub(/_template$/,"")
puts "Registering #{template_name}"
send("register_"+template_name)
end
end
def unregister_inactive_templates
session = Facebooker::Session.create
active_template_ids = FacebookTemplate.all.map(&:bundle_id)
all_template_ids = session.active_template_bundles.map {|t| t["template_bundle_id"]}
(all_template_ids - active_template_ids).each do |template_bundle_id|
session.deactivate_template_bundle_by_id(template_bundle_id)
end
end
def method_missing(name,*args)
should_send = false
method = ''
if md = /^create_(.*)$/.match(name.to_s)
method = md[1]
elsif md = /^deliver_(.*)$/.match(name.to_s)
method = md[1]
should_send = true
elsif md = /^register_(.*)$/.match(name.to_s)
return FacebookTemplate.register(self, md[1])
else
super
end
#now create the item
(publisher=new).send(method,*args)
case publisher._body
when UserAction
publisher._body.template_name = method
publisher._body.template_id ||= FacebookTemplate.bundle_id_for_class_and_method(self,method)
end
should_send ? publisher.send_message(method) : publisher._body
end
def controller_path
self.to_s.underscore
end
def helper(*args)
args.each do |arg|
case arg
when Symbol,String
add_template_helper("#{arg.to_s.camelcase}Helper".constantize)
when Module
add_template_helper(arg)
end
end
end
def add_template_helper(helper_module) #:nodoc:
master_helper_module.send :include,helper_module
include master_helper_module
end
def inherited(child)
super
child.master_helper_module=Module.new
child.master_helper_module.__send__(:include,self.master_helper_module)
child.send(:include, child.master_helper_module)
FacebookTemplate.clear_cache!
end
end
class PublisherController
include Facebooker::Rails::Publisher.master_helper_module
include ActionController::UrlWriter
def initialize(source)
self.class.url_option_source = source
end
class << self
attr_accessor :url_option_source
alias :old_default_url_options :default_url_options
def default_url_options(*args)
url_option_source.default_url_options(*args)
end
end
end
end
end
end
<% if @permission %>
<%= @permission %>
<% end %>
#...
#usage:
require 'facebooker_publisher.rb'
if facebook_session.user.has_permission?(:status_update)
FacebookerPublisher.deliver_publish_stream(facebook_session.user,facebook_session.user)
else
@permissions = "<fb:prompt-permission perms='read_stream,publish_stream'>Would you like our application to read from and post to your News Feed?</fb:prompt-permission>"
end
#..
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment