Skip to content

Instantly share code, notes, and snippets.

@sneakin
Created February 13, 2010 18:08
Show Gist options
  • Save sneakin/303594 to your computer and use it in GitHub Desktop.
Save sneakin/303594 to your computer and use it in GitHub Desktop.
FriendFeed archiver
#!/usr/bin/ruby
# FriendFeed archiver [with duct tape]
#
# Usage: ruby $0 command user [dir]
# command: "now" or "backup"
# "now" stores the present, "backup" the past
# user: user name to archive to dir
# dir: directory to store entries, defaults to "./friendfeed"
require 'httparty'
require 'yaml'
require 'delegate'
module FF
class Storage
class FileExists < RuntimeError
def initialize(path)
super("#{path} exists")
end
end
def initialize(dir = "friendfeed")
@dir = dir
end
def offset
Dir.glob(File.join(@dir, "*.yaml")).size
end
def write(entry)
path = path_for(entry)
raise FileExists, path if File.exists?(path)
File.open(path, 'w') do |f|
f.write(entry.to_yaml)
end
end
def path_for(entry)
file = entry['id'].gsub(/[\/.]/, '')
File.join(@dir, "#{file}.yaml")
end
end
class QueryString < DelegateClass(Hash)
def to_s
self.to_a.collect { |k, v| "#{k}=#{v}" }.join("&")
end
end
class Api
def initialize(user)
@user = user
end
def fetch_feed(options = {})
get(feed_url(options))
end
def fetch_updates(cursor, options = {})
get(updates_url(cursor, options))
end
private
def get(url)
r = HTTParty.get(url)
raise RuntimeError, "status = #{r.code}" if r.code != 200
r
end
def url(path, options)
"http://friendfeed-api.com/v2" + path + "?" + QueryString.new(options).to_s
end
def feed_url(options)
url("/feed/#{@user}", options)
end
def updates_url(cursor, options)
options = options.dup
options.merge!('cursor' => cursor) if cursor
url("/updates/feed/#{@user}", options)
end
end
class Backup
def initialize(ff, start, num = 20)
@ff = ff
@start = start
@num = num
end
def each_entry(&block)
start = @start
begin
x = @ff.fetch_feed(:num => @num, :start => start, :raw => '1')
x['entries'].each(&block)
start += @num
end until x['entries'].empty?
end
end
class Stream
def initialize(ff)
@ff = ff
@cursor = nil
end
def fetch
result = @ff.fetch_updates(@cursor, :timeout => 60, :raw => '1')
@cursor = result['realtime']['cursor']
result
end
def each_entry(&block)
begin
x = fetch
x['entries'].each(&block)
rescue Timeout::Error
$stderr.puts "Timed out..."
end while true
end
end
def self.create_feed(cmd, api, storage)
if cmd == 'backup' || cmd == 'resume'
Backup.new(api, (cmd == 'resume') ? storage.offset : 0)
elsif cmd == 'now'
Stream.new(api)
else
raise ArgumentError, "unknown command: #{cmd}"
end
end
def self.run(args)
cmd, user, dir, *rest = *args
dir ||= 'friendfeed'
storage = Storage.new(dir)
api = Api.new(user)
feed = create_feed(cmd, api, storage)
feed.each_entry do |e|
begin
puts e['id']
storage.write(e)
rescue Storage::FileExists
puts $!.message
end
end
end
end
if __FILE__ == $0
FF.run(ARGV)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment