update.rb |
|
---|---|
An Update is a particular status message sent by one of our users. |
class Update
require 'cgi'
include MongoMapper::Document |
Determines what constitutes a username inside an update text |
USERNAME_REGULAR_EXPRESSION = /(^|[ \t\n\r\f"'\(\[{]+)@([^ \t\n\r\f&?=@%\/\#]*[^ \t\n\r\f&?=@%\/\#.!:;,"'\]}\)])(?:@([^ \t\n\r\f&?=@%\/\#]*[^ \t\n\r\f&?=@%\/\#.!:;,"'\]}\)]))?/ |
Updates are aggregated in Feeds |
belongs_to :feed
key :feed_id, ObjectId |
Updates are written by Authors |
belongs_to :author
key :author_id, ObjectId
validates_presence_of :author_id |
The content of the update, unaltered, is stored here |
key :text, String, :default => ""
validates_length_of :text, :minimum => 1, :maximum => 140
validate :do_not_repeat_yourself, :on => :create |
Mentions are stored in the following array |
key :mention_ids, Array
many :mentions, :in => :mention_ids, :class_name => 'Author'
before_save :get_mentions |
The following are extra features and identifications for the update |
key :tags, Array, :default => []
key :twitter, Boolean |
For speed, we generate the html for the update lazily when it is rendered |
key :html, String |
We also generate the tags upon editing the update |
before_save :get_tags |
Updates have a remote url that globally identifies them |
key :remote_url, String |
Reply and restate identifiers Local Update id: (nil if remote) |
key :referral_id |
Remote Update url: (nil if local) |
key :referral_url, String
def referral
Update.first(:id => referral_id)
end
def url
feed.local? ? "/updates/#{id}" : remote_url
end
def url=(the_url)
self.remote_url = the_url
end
def to_html
self.html || generate_html
end
def mentioned?(username)
matches = text.match(/@#{username}\b/)
matches.nil? ? false : matches.length > 0
end |
These handle sending the update to other nodes and services |
after_create :send_to_remote_mentions
after_create :send_to_external_accounts
timestamps!
def self.hot_updates
all(:limit => 6, :order => 'created_at desc')
end
def get_tags
self[:tags] = self.text.scan(/#([\w\-\.]*)/).flatten
end |
Return OStatus::Entry instance describing this Update |
def to_atom(base_uri)
links = []
links << Atom::Link.new({ :href => ("#{base_uri}updates/#{self.id.to_s}")})
mentions.each do |author|
author_url = author.url
if author_url.start_with?("/")
author_url = "http://#{author.domain}/feeds/#{author.feed.id}"
end
links << Atom::Link.new({ :rel => 'ostatus:attention', :href => author_url })
links << Atom::Link.new({ :rel => 'mentioned', :href => author_url })
end
OStatus::Entry.new(:title => self.text,
:content => Atom::Content::Html.new(self.to_html),
:updated => self.updated_at,
:published => self.created_at,
:activity => OStatus::Activity.new(:object_type => :note),
:author => self.author.to_atom,
:id => "#{base_uri}updates/#{self.id.to_s}",
:links => links)
end
def to_xml(base_uri)
to_atom(base_uri).to_xml
end
def self.create_from_ostatus(entry, feed)
u = new(:author => feed.author,
:created_at => entry.published,
:remote_url => entry.url,
:feed => feed,
:updated_at => entry.updated)
u.sanitize_external_text(entry.content, entry.url)
u.save
u
end
def sanitize_external_text(entry_text, entry_url) |
Strip HTML |
self.text = Nokogiri::HTML::Document.parse(entry_text).text |
Truncate text |
truncation_necessary = self.text.length > 140
if truncation_necessary
self.text = self.text[0..138]
end |
Generate HTML |
if truncation_necessary
self.html = "#{self.to_html}<a href='#{entry_url}'>\u2026</a>"
end
end
protected
def get_mentions
self.mentions = []
out = CGI.escapeHTML(text)
out.gsub!(USERNAME_REGULAR_EXPRESSION) do |match|
if $3 and a = Author.first(:username => /^#{$2}$/i, :domain => /^#{$3}$/i)
self.mentions << a
elsif not $3 and authors = Author.all(:username => /^#{$2}$/i)
a = nil
if authors.count == 1
a = authors.first
else |
Disambiguate |
|
Is it in update to this author? |
if in_reply_to = referral
if not authors.index(in_reply_to.author).nil?
a = in_reply_to.author
end
end |
Is this update is generated by a local user, look at who they are following |
if a.nil? and user = self.author.user
authors.each do |author|
if user.following_author?(author)
a = author
end
end
end
end
self.mentions << a unless a.nil?
end
match
end
self.mentions
end |
Generate and store the html |
def generate_html
out = CGI.escapeHTML(text) |
Replace any absolute addresses with a link Note: Do this first! Otherwise it will add anchors inside anchors! |
out.gsub!(/(http[s]?:\/\/\S+[a-zA-Z0-9\/}])/, "<a href='\\1'>\\1</a>") |
we let almost anything be in a username, except those that mess with urls. but you can’t end in a .:;, or ! also ignore container chars [] () “” ‘’ {} XXX: the correct solution will be to use an email validator |
out.gsub!(USERNAME_REGULAR_EXPRESSION) do |match|
if $3 and a = Author.first(:username => /^#{$2}$/i, :domain => /^#{$3}$/i)
author_url = a.url
if author_url.start_with?("/")
author_url = "http://#{author.domain}#{author_url}"
end
"#{$1}<a href='#{author_url}'>@#{$2}@#{$3}</a>"
elsif not $3 and a = Author.first(:username => /^#{$2}$/i)
author_url = a.url
if author_url.start_with?("/")
author_url = "http://#{author.domain}#{author_url}"
end
"#{$1}<a href='#{author_url}'>@#{$2}</a>"
else
match
end
end
out.gsub!(/(^|\s+)#(\w+)/) do |match|
"#{$1}<a href='/search?search=%23#{$2}'>##{$2}</a>"
end
self.html = out
end
def send_to_remote_mentions |
Only local users can do this |
if author.user |
For each mention, if they are not following this user, send this update to them as a salmon notification XXX: allow for authors that we do not know (who do not have feeds) |
mentions.each do |mentioned_author|
unless mentioned_author.domain == author.domain
mentioned_feed = mentioned_author.feed
unless author.user.followers.include? mentioned_feed
author.user.delay.send_mention_notification id, mentioned_feed.id
end
end
end
end
end |
If a user has twitter enabled on their account and they checked it on update form, repost the update to twitter |
def send_to_external_accounts
return if ENV['RAILS_ENV'] == 'development' |
If there is no user we can’t get to the oauth tokens, abort! |
if author.user |
If the twitter flag is true and the user has a twitter account linked send the update |
if self.twitter? && author.user.twitter?
begin
Twitter.configure do |config|
config.consumer_key = ENV["CONSUMER_KEY"]
config.consumer_secret = ENV["CONSUMER_SECRET"]
config.oauth_token = author.user.twitter.oauth_token
config.oauth_token_secret = author.user.twitter.oauth_secret
end
Twitter.update(text)
rescue Exception => e |
I should be shot for doing this. |
end
end
end
end
def do_not_repeat_yourself
errors.add(:text, "You already posted this update.") if already_posted?
end
def already_posted?
feed.last_update && feed.last_update.id != id && feed.last_update.text == text
end
end |