#!/usr/bin/ruby require 'cgi' require 'net/http' require 'yaml' require 'rubygems' require 'builder' require 'builder_monkey_patch' MDASH = [0x2014].pack('U') STAR = [0x2605].pack('U') TITLE = 'Ruby Family Bookmarks' DAYS = 7 JID, PASSWORD = "rubys@rubix/bookmark", "password" SUBSCRIPTIONS = %w( rubys@rubix/Laptop rubyc@rubix/Laptop ) cgi = CGI.new x = Builder::XmlMarkup.new :indent => 2 cgi.out 'type' => 'application/xhtml+xml', 'charset' => 'UTF-8' do x.html :xmlns => 'http://www.w3.org/1999/xhtml' do x.header do x.link :rel => 'shortcut icon', :href => 'star.png' x.title TITLE x.text! "\n" x.style :type => "text/css" do x.text! <<-EOF.gsub(/^ /,'') html {margin: 0} h1 {margin: -8px -10px 0 -10px; padding: 0.4em} h1 {background: url("/images/blueback.png") repeat-x} form, input, textarea, button, pre {-moz-border-radius: 1em} form, pre {border: solid; margin: 1em; padding: 2em} input[type="text"], textarea {width: 100%; padding: 2px} input[type="checkbox"] {margin-left: 1em} textarea {margin-bottom: 1em} table {width: 100%} ul {list-style: none} .icon { height: 16px; width: 16px; margin-right: 1px } h1 {background-color: #039} h1 a {color: #FF0; text-decoration: none} form {background-color: #FE0} pre {background-color: #FCC} input, textarea {background-color: #FF8} button {margin: 0 1em 0 2em; background-color: #FFC} li > span:first-child { color: #039} EOF end x.text! "\n" x.script :type => "text/javascript" do x.text! <<-'EOF'.gsub(/^ /,'') function text_state(state) { var inputs = document.getElementsByTagName('input'); for (var i=0; i 'comment_focus()' do begin x.h1 { x.a "#{STAR} #{TITLE}", :href => '.' } if !cgi.params.empty? if cgi.request_method == 'GET' # input form x.text! "\n" x.form :method => 'post', :action => '.' do x.table do # input fields cgi.params.each do |name, value| next if name == 'user' x.tr do x.td name.capitalize x.td do x.input :type=>'text', :size => 80, :name =>name, :value => value.join end end end # comment text area x.tr do x.td 'Comments' x.td do x.textarea(:name => 'comments', :id => 'comments') {} end end # hidden field, buttons, and checkbox x.tr do x.td do x.input :type=>'hidden', :name =>'user', :value => cgi['user'] end x.td do x.button "post and alert", :type => "submit", :name => "post", :value =>"alert" x.button "post only", :type => "submit", :name => "post", :value =>"only" x.input 'text only', :type => 'checkbox', :name => 'text', :id => 'text', :value => 'only', :onclick => 'text_state(this.checked)' x.label 'text only', :for => 'text' end end end end else # defaults favicon = nil name = Time.now.iso8601 if cgi.has_key? 'uri' uri = URI.parse(cgi.params['uri'].first) # default location for favicon favicon = URI.parse("#{uri.scheme}://#{uri.host}/favicon.ico") # look for an explicit favicon link begin Net::HTTP.start(uri.host, uri.port) {|http| response=http.get(uri.request_uri) require 'html5' doc = HTML5.parse(response.body) doc.elements.each('html/head/link[@rel]') do |link| # http://germane-software.com/projects/rexml/ticket/131 next unless link.attributes['href'] next unless link.attributes['rel'].split.any? {|rel| %w(shortcut icon).include? rel } favicon = URI.join(uri.to_s, link.attributes['href']) end } rescue end # verify that the favicon exists begin Net::HTTP.start(favicon.host, favicon.port) {|http| favicon = nil if http.get(favicon.request_uri).code != '200' } rescue favicon = nil end # base the data file name on the uri require 'addressable/uri' name = Addressable::URI.parse(uri.to_s).normalize.to_s end # determine data file name for bookmark name.sub!(/^\w+:\/*(\w+:|www\.)?/,'') # remove scheme and www. name.gsub! /[?\/:|]+/, ',' # replace separator characters name.sub! /^[,.]*/, '' # remove initial junk name.sub! /[,.]*$/, '' # remove final junk # limit name length if name.length > 250 require 'digest/md5' parts, excess = name.split(','), [] excess << parts.pop while parts.join(',').length > 220 parts << Digest::MD5.hexdigest(excess.join(',')) name = parts.join(',') end # write out data file File.open(File.join('data',name),'w') do |file| params = {'favicon' => favicon.to_s} cgi.params.each {|n,v| params[n] = v.join(' ')} file.write(params.to_yaml) end # notify if cgi['post'] != 'only' require 'xmpp4r/client' client = Jabber::Client.new(Jabber::JID.new(JID)) client.connect.auth(PASSWORD) html = REXML::Element.new('html') html.add_namespace('http://jabber.org/protocol/xhtml-im') body = html.add_element('body') body.add_namespace('http://www.w3.org/1999/xhtml') if cgi.has_key? 'uri' link = body.add_element('a') # http://germane-software.com/projects/rexml/ticket/127 link.attributes['href'] = cgi['uri'] link.text = cgi['title'] end if !cgi['comments'].strip.empty? body.add_text " #{MDASH} " if cgi.has_key? 'uri' body.add_text cgi['comments'] end text = cgi['uri'] + body.texts.map {|text| text.value}.join SUBSCRIPTIONS.each do |user| next if user == cgi['user'] or user =~ /^#{cgi['user']}@/ msg = Jabber::Message.new(user, text) msg.add_element html client.send(msg) end client.close end end end # group bookmarks by date by_date = Hash.new {|hash,key| hash[key] = Array.new} Dir['data/*'].each do |file| next if file == 'index.cgi' mtime = File.mtime(file) date = Date.new(mtime.year, mtime.month, mtime.day) by_date[date] << [mtime, file] end # output last seven days of bookmarks by_date.keys.sort.reverse[0...DAYS].each do |date| x.text! "\n" x.h3 date.strftime("%A, %B %d, %Y").gsub(' 0',' ') by_date[date].sort.reverse.each do |mtime, file| bookmark = YAML.load(File.open(file)) x.ul do x.li :class => bookmark['user'] do if bookmark['favicon'] and !bookmark['favicon'].empty? x.img :src => bookmark['favicon'], :class => 'icon' else x.span "#{STAR} " end if bookmark.has_key? 'uri' x.a bookmark['title'], :href=>bookmark['uri'] end if !bookmark['comments'].strip.empty? x.text! " #{MDASH} " if bookmark.has_key? 'uri' x.text! "#{bookmark['comments']}\n" end end end end end rescue Exception => e # if things go wrong, let somebody know x.pre do x.text! "#{e.inspect}\n" e.backtrace.each {|line| x.text! " #{line}\n"} end end end end end