Created
May 31, 2010 08:17
-
-
Save ELLIOTTCABLE/419647 to your computer and use it in GitHub Desktop.
ItBacksUpYourFuckingTweets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*.json | |
*.log |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Update: I added a shell-script to make executing this a little bit easier, and a Rakefile to add this to LaunchD.