JAN 27

With much thanks to this blog post, which reveals how to fix /etc/ushare.conf and /etc/init.d/ushare.

Install the dependencies:

$ sudo apt-get install libupnp-dev pkg-config
$ sudo echo "deb http://www.geexbox.org/debian/ unstable main" >> /etc/apt/sources.list
$ sudo apt-get install libdlna-dev ushare

(uShare’s being installed here just to grab the conf files.)

Pick up the latest version of uShare (assuming Mercurial is installed):

$ hg clone http://hg.geexbox.org/ushare

In the uShare directory:

$ ./configure --prefix=/usr/local
$ make
$ sudo make install

Now the configuration files need to be edited. Make the appropriate changes to /etc/ushare.conf, making sure to modify ENABLE_XBOX to USHARE_ENABLE_XBOX. The rest should be self-explanatory.

In /etc/init.d/ushare, add USHARE_OPTIONS="-f $CONFIGFILE" so that the correct command line is sent to uShare.

And now you should be able to stream videos from your Ubuntu fileserver to your Xbox 360.

JAN 03

The previous method was a bit messy, so I’ve cleaned it up a bit.

See the code at GitHub here.

twitter.rb

Now uses an Sqlite3 database to hold the data.

require 'active_record'
require 'hpricot'
require 'open-uri'

ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :dbfile => File.join(File.dirname(__FILE__), 'tweets.db')
)

class Tweet < ActiveRecord::Base
  def time
    DateTime.parse(time_before_type_cast)
  end
end

class Twitter
  def initialize(user)
    @user_url = "http://twitter.com/#{user}"

    @doc = Hpricot(open(@user_url))
    @page = 1

    @tweets = [current_tweet]
    @tweets += page_to_tweets
  end

  def current_tweet
    tweet,time = @doc/'div.desc'/'p'
    tweet = tweet.inner_html
    time = DateTime.parse(time.at('abbr')['title'])

    {:tweet => tweet, :time => time}
  end

  def page_to_tweets
    (@doc/'div.tab'/'tr.hentry').map do |tweet|
      tweet,time = tweet/'span'
      tweet = tweet.inner_html.gsub(/^\s*(.*)\s*$/, '\1')
      time = DateTime.parse(time.at('abbr')['title'])

      {:tweet => tweet, :time => time}
    end
  end

  def older?
    (@doc/'div.tab'/'div.pagination'/'a').last.inner_text =~ /Older/
  end

  def succ
    if @tweets.empty?
      return nil unless older?

      @page += 1
      @doc = Hpricot(open("#{@user_url}?page=#{@page}"))
      @tweets = page_to_tweets
    end

    @tweets.shift
  end
end

download_tweets.rb

#!/usr/bin/env ruby

require 'twitter'

last_tweet = Tweet.find(:first, :order => 'time DESC')

tweets = Twitter.new(ARGV[0])

if last_tweet.nil? or tweets.current_tweet[:time] > last_tweet.time
  while tweet = tweets.succ
    break if last_tweet and tweet[:time] <= last_tweet.time

    Tweet.create(tweet)
  end
end

generate_graphs.rb

#!/usr/bin/env ruby

require 'gchart'
require 'twitter'

month_data = Array.new(12, 0)
day_data = Array.new(7, 0)
hour_data = Array.new(24, 0)
reply_data = Hash.new(0)

Tweet.find(:all).select {|t| t.time.year == 2007 }.each do |t|
  month_data[t.time.month-1] += 1
  day_data[t.time.wday] += 1
  hour_data[(t.time.hour-8)%24] += 1
  reply_data[$1] += 1 if t[:tweet] =~ /@<a href="\/([^"]+)">\1<\/a>/
end

def min_max_label(data)
  "|#{data.min}|#{data.max}"
end

puts GChart.line(
  :title => 'Tweets per Hour',
  :data => hour_data,
  :width => 400,
  :height => 300,
  :extras => { 'chxt' => 'x,y', 'chxl' => "0:|#{(0..23).to_a.join('|')}|1:#{min_max_label(hour_data)}" }
).to_url

puts GChart.bar(
  :title => 'Tweets per Day',
  :data => day_data,
  :width => 400,
  :height => 300,
  :extras => { 'chxt' => 'x,y', 'chxl' => "0:|#{Date::ABBR_DAYNAMES.compact.join('|')}|1:#{min_max_label(day_data)}" },
  :orientation => :vertical
).to_url

puts GChart.bar(
  :title => 'Tweets per Month',
  :data => month_data,
  :width => 400,
  :height => 300,
  :extras => { 'chxt' => 'x,y', 'chxl' => "0:|#{Date::ABBR_MONTHNAMES.compact.join('|')}|1:#{min_max_label(month_data)}" },
  :orientation => :vertical
).to_url

reply_data = reply_data.sort_by {|_,v| v }.reverse
reply_data.each {|k,v| puts "#{k}: #{v}" }
reply_labels = reply_data.map {|k,_| k }
reply_data = reply_data.map {|_,v| v }
puts GChart.bar(
  :title => 'Most Replies',
  :data => reply_data,
  :width => 400,
  :height => 300,
  :extras => { 'chxt' => 'x,y', 'chxl' => "0:|#{reply_labels.join('|')}|1:#{min_max_label(reply_data)}" },
  :orientation => :vertical
).to_url

JAN 02

I saw Damon Cortesi’s Twitter Stats script last night, and decided to make a Ruby version. This was before he released his code, so it’s reverse-engineered rather than ported. I’ll take a look later tonight to see how much the logic differs.

Edit: This code is rather inelegant, and I’ve replaced the clunky CSV files with an Sqlite3 database. You can find the new and improved scripts here. The following should still work, and I’m leaving it here for posterity’s sake.

tweet.rb

First up, I wrote a quick Tweet class to actually get all of my tweets.

require 'hpricot'
require 'open-uri'

class Tweet
  def initialize(user)
    @user_url = "http://twitter.com/#{user}"

    @doc = Hpricot(open(@user_url))
    @page = 1

    @tweets = [current_tweet]
    @tweets += page_to_tweets
  end

  def current_tweet
    tweet,time = @doc/'div.desc'/'p'
    tweet = tweet.inner_html
    time = DateTime.parse(time.at('abbr')['title'])

    [tweet, time]
  end

  def page_to_tweets
    (@doc/'div.tab'/'tr.hentry').map do |tweet|
      tweet,time = tweet/'span'
      tweet = tweet.inner_html.gsub(/^\s*(.*)\s*$/, '\1')
      time = DateTime.parse(time.at('abbr')['title'])

      [tweet, time]
    end
  end

  def older?
    (@doc/'div.tab'/'div.pagination'/'a').last.inner_text =~ /Older/
  end

  def succ
    if @tweets.empty?
      return nil unless older?

      @page += 1
      @doc = Hpricot(open("#{@user_url}?page=#{@page}"))
      @tweets = page_to_tweets
    end

    @tweets.shift
  end
end

download_to_csv.rb

Next, a quick script to download the tweets into a CSV file. This is actually a bit over-engineered, as it’ll only download tweets that have not been previously downloaded. Note that this takes the username as a command line argument.

#!/usr/bin/env ruby

require 'fastercsv'
require 'tweet'

base_path = File.dirname(__FILE__)

csv_files = Dir["#{base_path}/*.csv"].sort_by do |filename|
  DateTime.parse(File.basename(filename, '.csv'))
end

last_update = DateTime.parse(File.basename(csv_files.last, '.csv')) unless csv_files.empty?

tweets = Tweet.new(ARGV.shift)
current_update_time = tweets.current_tweet.last

if last_update.nil? or current_update_time > last_update
  FasterCSV.open(File.join(base_path, "#{current_update_time.to_s}.csv"), 'w') do |csv|
    while t = tweets.succ
      tweet,time = t

      break if last_update and time <= last_update

      csv << [tweet, time.to_s]
    end
  end
end

generate_graphs.rb

And last, creating the graphs of the statistics from the CSV files.

#!/usr/bin/env ruby

require 'fastercsv'
require 'gchart'
require 'tweet'

base_path = File.dirname(__FILE__)
year = 2007

month_data = Array.new(12, 0)
hour_data = Array.new(24, 0)
reply_data = Hash.new(0)

Dir["#{base_path}/*.csv"].each do |filename|
  FasterCSV.foreach(filename) do |row|
    tweet = row.first
    time = DateTime.parse(row.last)

    month_data[time.month - 1] += 1 if time.year == year
    hour_data[(time.hour-8)%24] += 1 if time.year == year
    reply_data[$1] += 1 if tweet =~ /@<a href="\/([^"]+)">\1<\/a>/ and time.year == year
  end
end

puts GChart.line(
  :title => 'Tweets per Hour',
  :data => hour_data,
  :width => 400,
  :height => 300,
  :extras => { 'chxt' => 'x,y', 'chxl' => "0:|#{(0..23).to_a.join('|')}|1:|#{hour_data.min}|#{hour_data.max}" }
).to_url

puts GChart.bar(
  :title => 'Tweets per Month',
  :data => month_data,
  :width => 400,
  :height => 300,
  :extras => { 'chxt' => 'x,y', 'chxl' => "0:|#{Date::ABBR_MONTHNAMES.compact.join('|')}|1:|#{month_data.min}|#{month_data.max}" },
  :orientation => :vertical
).to_url

Example graphs