Skip to content

Instantly share code, notes, and snippets.

@ELLIOTTCABLE
Created May 31, 2010 08:17
Show Gist options
  • Save ELLIOTTCABLE/419647 to your computer and use it in GitHub Desktop.
Save ELLIOTTCABLE/419647 to your computer and use it in GitHub Desktop.
ItBacksUpYourFuckingTweets
*.json
*.log
require 'time'
require 'json/pure' # require 'json/ext' segfaults for me
require 'yaml'
require 'twitter'
######################################################################################################## Welcome!
# This file is ugly as fuck. Get over it, I was tired… not to mention that I hadn’t written any Ruby in, like, a
# year. C will do this to your head rather fast.
#
# Anyway. Usage is pretty simple… give it a status ID as an argument, and it will spit out the (ordered) JSON for
# all the tweets and retweets you’ve made since that particular status ID. Mind you, it spits out the JSON on
# STDOUT, but *does* require interaction on STDERR and STDIN: don’t redirect them, and don’t `cron` this.
# (Unless you want to scrape Twitter.com for the OAuth key, which is too much work for me. Sorry!)
#
# However, after the first time you run it, assuming it authenticates properly, your authentication token and key
# will be cached (in ~/.ItBacksUpYourFuckingTweets as well), so you can thereafter cron the task. Just make sure
# to check that it’s actually staying authenticated; you may have to run it by hand very ocassionally to ensure
# it’s not wanting to ask for a new authentication (I have no idea if Twitter’s OAuth expires your tokens after a
# while or not… I’m not really very familiar with OAuth.)
#
# Oh, also, you have to have a ~/.ItBacksUpYourFuckingTweets file (YAML) with your OAuth `token` and `secret`.
# You can create them on Twitter.com:
# <http://twitter.com/apps/new>
#
# Suggested use: accumulatively write the JSON to a local backup file. You can sync this file up to S3 or Dropbox
# or whatever the hell you want. Here’s one way to invoke the script:
#
# $> ruby ItBacksUpYourFuckingTweets.rb `\
# tail -n 1 "$HOME/Documents/tweets.json" |\
# awk -F':' '{ print $1 }'` \
# 1>>"$HOME/Documents/tweets.json"
#
# If you want, you can also cron the included shell script, `ItBacksUpYourFuckingTweets.sh` (or LaunchD it, if
# you’re on a Mac; I included a LaunchDr Rakefile that’ll handle that for you if you want:
# `gem install launchdr && rake launchd`). However, remember to invoke the ruby script directly at least once, to
# cache the necessary OAuth credientials from Twitter.
#################################################################################################################
STDERR.sync = true
ConfigFile = "#{ENV['HOME']}/.ItBacksUpYourFuckingTweets"
config = YAML.load_file(ConfigFile)
oauth = Twitter::OAuth.new(config['token'], config['secret'])
begin
# Fuck you, OAuth. All of this is necessary, just to log in… /-:
until config['atoken'] && config['asecret']
if config['rtoken'] && config['rsecret']
exit if ENV['NON_INTERACTIVE']
`open http://#{oauth.request_token.authorize_url}`
STDERR.puts "You're being redirected to your browser to authorize me."
STDERR.puts "Please type the PIN that Twitter gives you: "
pin = STDIN.gets.chomp
oauth.authorize_from_request(config['rtoken'], config['rsecret'], pin)
config['atoken'] = oauth.access_token.token
config['asecret'] = oauth.access_token.secret
config.delete 'rtoken'
config.delete 'rsecret'
else
config.update({
'rtoken' => oauth.request_token.token,
'rsecret' => oauth.request_token.secret,
})
end
end
oauth.authorize_from_access(config['atoken'], config['asecret'])
File.open(ConfigFile, File::WRONLY|File::TRUNC|File::CREAT) {|out| YAML.dump config, out }
twitter = Twitter::Base.new(oauth)
STDERR.puts "(Archiving since #{ARGV[0].inspect}.)"
STDERR.print " Tweets (per 200): "
results = Array.new; request = Array.new; page = 1; failures = 0
loop do; begin
request = twitter.user_timeline(count: 200, page: page, since_id: ARGV[0] || 1)
results += request; page += 1
request.empty? ? break : STDERR.print(page + 1 % 5 == 0 ? '!' : '.')
rescue Twitter::RateLimitExceeded
STDERR.print "!(#{failures})"
if (failures += 1) >= 2
r = twitter.rate_limit_status
STDERR.print "\nYou're out of requests (#{r['remaining_hits']}/#{r['hourly_limit']}). Try again in #{
(Time.parse(r['reset_time']) - Time.now) / 60}m."
break
else
sleep 10
end
rescue Twitter::Unavailable
STDERR.print "?(#{failures})"
if (failures += 1) >= 10
STDERR.print "\nTwitter is unstable; breaking off. Try again later."
break
end
sleep 10
end; end
STDERR.print "\n"
STDERR.print "Retweets (per 200): "
request = Array.new; page = 1
loop do; begin
request = twitter.retweeted_by_me(count: 200, page: page, since_id: ARGV[0] || 1)
results += request; page += 1
request.empty? ? break : STDERR.print(page + 1 % 5 == 0 ? '!' : '.')
rescue Twitter::RateLimitExceeded
STDERR.print "!(#{failures})"
if (failures += 1) >= 2
r = twitter.rate_limit_status
STDERR.print "\nYou're out of requests (#{r['remaining_hits']}/#{r['hourly_limit']}). Try again in #{
(Time.parse(r['reset_time']) - Time.now) / 60}m."
break
else
sleep 10
end
rescue Twitter::Unavailable
STDERR.print "?(#{failures})"
if (failures += 1) >= 10
STDERR.print "\nTwitter is unstable; breaking off. Try again later."
break
end
sleep 10
end; end unless failures > 5
STDERR.print "\n"
results.sort {|a, b| a["id"].to_i <=> b["id"].to_i }.each do |t|
# Yes, we *are* round-tripping from Twitter’s JSON, through a Ruby `Hash`, through the Twitter gem’s `Mash`,
# then through the `JSON` gem… back to JSON! Yay! It’s worth it for the Twitter gem’s OAuth handling, though.
puts "#{t['id']}: #{JSON.generate(t.to_hash)}"
end
rescue Twitter::Unauthorized, OAuth::Unauthorized
STDERR.puts "!! Something went wrong. We're going to try again."
%w(rtoken rsecret atoken asecret).each {|token| config.delete token } and retry if
config['rtoken'] || config['rsecret'] || config['atoken'] || config['asecret']
end
#!/bin/sh
if [[ -f "$RVM_ENVIRONMENT" ]]; then
. "$RVM_ENVIRONMENT"
fi
ruby ~/Code/ItBacksUpYourFuckingTweets/ItBacksUpYourFuckingTweets.rb `\
tail -n 1 "$TWEETSFILE" |\
awk -F':' '{ print $1 }'` \
1>>"$TWEETSFILE"
require 'pathname'
require 'launchdr'
desc 'Create a LaunchD entry for ItBacksUpYourFuckingTweets'
task :launchd do
LaunchDr "name.elliottcable.ItBacksUpYourFuckingTweets" do |plist|
plist[:user_name] = `whoami`.chomp
plist[:program] = (Pathname.new(__FILE__).dirname + "ItBacksUpYourFuckingTweets.sh").to_s
plist[:working_directory] = Pathname.new(__FILE__).dirname.to_s
plist[:standard_error_path] = (Pathname.new(__FILE__).dirname + "launchd.error.log").to_s
plist[:standard_out_path] = (Pathname.new(__FILE__).dirname + "launchd.out.log").to_s
plist[:environment_variables] = {
"HOME" => ENV["HOME"],
"NON_INTERACTIVE" => "yahhuh!",
"TWEETSFILE" => (Pathname.new(__FILE__).dirname + "tweets.json").to_s,
"RVM_ENVIRONMENT" => "#{ENV["HOME"]}/.rvm/environments/ruby-1.9.2-preview1"
}
plist[:run_at_load] = true
plist[:start_calendar_interval] = { :Minute => 0, :Hour => 0 } # 00:00 (midnight), daily
end
end
@ELLIOTTCABLE
Copy link
Author

Update: I added a shell-script to make executing this a little bit easier, and a Rakefile to add this to LaunchD.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment