Jump To …

feed.rb

require_relative '../../lib/finds_or_creates_feeds'

Feeds are pretty central to everything. They’re a representation of a PuSH enabled Atom feed. Every user has a feed of their updates, we keep feeds for remote users that our users are subscribed to, and maybe even other things in the future, like hashtags.

class Feed

XXX: Are these even needed? Bundler should be require-ing them.

  require 'osub'
  require 'opub'
  require 'nokogiri'
  require 'atom'

  include MongoMapper::Document

Feed url (and an indicator that it is local if this is nil)

  key :remote_url, String

OStatus subscriber information

  key :verify_token, String
  key :secret, String

For both pubs and subs, it needs to know what hubs the feed is in communication with in order to control pub/sub operations

  key :hubs, Array

  belongs_to :author
  key :author_id, ObjectId

  many :updates, :order => 'created_at desc'

  timestamps!

  after_create :default_hubs

  def self.find_or_create(subscribe_to)
    FindsOrCreatesFeeds.find_or_create(subscribe_to)
  end

  def self.create_from_feed_data(feed_data)
    feed = Feed.create(:remote_url => feed_data.url)
    feed.populate(feed_data.finger_data)
    feed
  end

This is because sometimes the mongomapper association returns nil even though there is an author_id and the Author exists; see Issue #421

  def author
    Author.find(author_id)
  end

  def populate(finger_data)

TODO: More entropy would be nice

    self.verify_token = Digest::MD5.hexdigest(rand.to_s)
    self.secret = Digest::MD5.hexdigest(rand.to_s)

    ostatus_feed = OStatus::Feed.from_url(url)

    avatar_url = ostatus_feed.icon
    if avatar_url == nil
      avatar_url = ostatus_feed.logo
    end

    a = ostatus_feed.author

    self.author = Author.create(:name => a.portable_contacts.display_name,
                                :username => a.name,
                                :email => a.email,
                                :remote_url => a.uri,
                                :domain => a.uri,
                                :salmon_url => ostatus_feed.salmon,
                                :bio => a.portable_contacts.note,
                                :image_url => avatar_url)

    if(finger_data)
      self.author.public_key = finger_data.public_key
      self.author.reset_key_lease

      self.author.salmon_url = finger_data.salmon_url
      self.author.save
    end

    self.hubs = ostatus_feed.hubs

    save

Save the first 3 updates in our feed

    entries = ostatus_feed.entries

    if entries.length > 3
      entries = entries[0..2]
    end
    populate_entries(entries)

    save
  end

  def populate_entries(os_entries)
    os_entries.each do |entry|
      if entry.url # This will drop some RTs from identica until ostatus

issue #4 is fixed, but at least this’ll fix issue #458

        existing_update = Update.first(:remote_url => entry.url)

        if existing_update

Don’t change anything about an existing update unless this is an update event.

          if entry.activity && entry.activity.verb == "update"
            existing_update.sanitize_external_text(entry.content, entry.url)
            existing_update.save
            self.updates << existing_update
          end
        else
          u = Update.create_from_ostatus(entry, self)
          self.updates << u
        end
      end
    end
    save
  end

Pings hub needs absolute url for feed to give to hub for callback

  def ping_hubs
    feed_url = "#{url}.atom"
    OPub::Publisher.new(feed_url, hubs).ping_hubs
  end

  def local?
    remote_url.nil?
  end

  def url(atom_format = false)
    url = (remote_url.nil? && author) ? "http://#{author.domain}/feeds/#{id}" : remote_url
    url << ".atom" if atom_format
    url
  end

  def update_entries(atom_xml, callback_url, feed_url, signature)
    sub = OSub::Subscription.new(callback_url, feed_url, self.secret)

    if sub.verify_content(atom_xml, signature)
      os_feed = OStatus::Feed.from_string(atom_xml)

XXX: Update author if necessary

      populate_entries(os_feed.entries)
    end
  end

  def default_hubs
    self.hubs << "http://rstatus.superfeedr.com/"

    save
  end

create atom feed need base_uri since urls outgoing should be absolute

  def atom(base_uri, params = {})
    if params[:since]
      atom_updates = updates.where(:created_at => {:$gt => params[:since]})
    elsif params[:num]
      atom_updates = updates.limit(params[:num])
    else
      atom_updates = updates.limit(20)
    end

Create the OStatus::Author object

    os_auth = author.to_atom

Gather entries as OStatus::Entry objects

    entries = atom_updates.map do |update|
      update.to_atom(base_uri)
    end

    avatar_url_abs = author.avatar_url
    if avatar_url_abs.start_with?("/")
      avatar_url_abs = "#{base_uri}#{author.avatar_url[1..-1]}"
    end

Create a Feed representation which we can generate the Atom feed and send out.

    atom_url   = "#{base_uri}feeds/#{id}.atom"
    salmon_url = "#{base_uri}feeds/#{id}/salmon"

    feed = OStatus::Feed.from_data(
      atom_url,
      :title   => "#{author.username}'s Updates",
      :logo    => avatar_url_abs,
      :id      => atom_url,
      :author  => os_auth,
      :updated => updated_at,
      :entries => entries,
      :links   => {
        :hub    => [{:href => hubs.first}],
        :salmon => [{:href => salmon_url}],
        :"http://salmon-protocol.org/ns/salmon-replies" =>
          [{:href => salmon_url}],
        :"http://salmon-protocol.org/ns/salmon-mention" =>
          [{:href => salmon_url}]
      }
    )

    feed.atom
  end

  def last_update
    updates.first
  end
end