Skip to content

Instantly share code, notes, and snippets.

Created Jan 2, 2010
Embed
What would you like to do?
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