Permalink
Browse files

Initial commit.

  • Loading branch information...
0 parents commit 9708516e62d00572985f79b483189454f0cc39a2 @twe4ked committed May 7, 2012
Showing with 466 additions and 0 deletions.
  1. +1 −0 .gitignore
  2. +23 −0 Gemfile
  3. +103 −0 Gemfile.lock
  4. +12 −0 README.md
  5. +45 −0 Rakefile
  6. +3 −0 config.ru
  7. +14 −0 lib/fixnum.rb
  8. +13 −0 lib/unfollow.rb
  9. BIN public/background.png
  10. BIN public/logo.png
  11. +141 −0 tmpfollow.rb
  12. +85 −0 views/application.scss
  13. +11 −0 views/index.haml
  14. +15 −0 views/layout.haml
@@ -0,0 +1 @@
+.sass-cache
23 Gemfile
@@ -0,0 +1,23 @@
+source :rubygems
+
+gem 'sinatra-reloader'
+gem 'rack-flash3', :require => 'rack-flash'
+gem 'sinatra', :require => 'sinatra/base'
+gem 'compass'
+gem 'twitter'
+gem 'oauth'
+gem 'sass'
+gem 'thin'
+gem 'haml'
+gem 'rake'
+
+# DataMapper
+gem 'dm-core'
+gem 'dm-timestamps'
+gem 'dm-migrations'
+gem 'dm-validations'
+gem 'dm-postgres-adapter'
+
+group :development, :test do
+ gem 'pry'
+end
@@ -0,0 +1,103 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ activesupport (3.2.3)
+ i18n (~> 0.6)
+ multi_json (~> 1.0)
+ addressable (2.2.8)
+ backports (2.5.1)
+ chunky_png (1.2.5)
+ coderay (1.0.6)
+ compass (0.12.1)
+ chunky_png (~> 1.2)
+ fssm (>= 0.2.7)
+ sass (~> 3.1)
+ daemons (1.1.8)
+ data_objects (0.10.8)
+ addressable (~> 2.1)
+ dm-core (1.2.0)
+ addressable (~> 2.2.6)
+ dm-do-adapter (1.2.0)
+ data_objects (~> 0.10.6)
+ dm-core (~> 1.2.0)
+ dm-migrations (1.2.0)
+ dm-core (~> 1.2.0)
+ dm-postgres-adapter (1.2.0)
+ dm-do-adapter (~> 1.2.0)
+ do_postgres (~> 0.10.6)
+ dm-timestamps (1.2.0)
+ dm-core (~> 1.2.0)
+ dm-validations (1.2.0)
+ dm-core (~> 1.2.0)
+ do_postgres (0.10.8)
+ data_objects (= 0.10.8)
+ eventmachine (0.12.10)
+ faraday (0.8.0)
+ multipart-post (~> 1.1)
+ fssm (0.2.9)
+ haml (3.1.4)
+ i18n (0.6.0)
+ method_source (0.7.1)
+ multi_json (1.3.2)
+ multipart-post (1.1.5)
+ oauth (0.4.6)
+ pry (0.9.8.4)
+ coderay (~> 1.0.5)
+ method_source (~> 0.7.1)
+ slop (>= 2.4.4, < 3)
+ rack (1.4.1)
+ rack-flash3 (1.0.1)
+ rack
+ rack
+ rack-protection (1.2.0)
+ rack
+ rack-test (0.6.1)
+ rack (>= 1.0)
+ rake (0.9.2.2)
+ sass (3.1.16)
+ simple_oauth (0.1.7)
+ sinatra (1.3.2)
+ rack (~> 1.3, >= 1.3.6)
+ rack-protection (~> 1.2)
+ tilt (~> 1.3, >= 1.3.3)
+ sinatra-contrib (1.3.1)
+ backports (>= 2.0)
+ eventmachine
+ rack-protection
+ rack-test
+ sinatra (~> 1.3.0)
+ tilt (~> 1.3)
+ sinatra-reloader (1.0)
+ sinatra-contrib
+ slop (2.4.4)
+ thin (1.3.1)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
+ tilt (1.3.3)
+ twitter (2.2.5)
+ activesupport (>= 2.3.9, < 4)
+ faraday (~> 0.8)
+ multi_json (~> 1.3)
+ simple_oauth (~> 0.1.6)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ compass
+ dm-core
+ dm-migrations
+ dm-postgres-adapter
+ dm-timestamps
+ dm-validations
+ haml
+ oauth
+ pry
+ rack-flash3
+ rake
+ sass
+ sinatra
+ sinatra-reloader
+ thin
+ twitter
@@ -0,0 +1,12 @@
+tmp/follow
+==========
+
+Development
+-----------
+
+You will need to set the following environment variables.
+
+ ENV['TWITTER_CONSUMER_KEY']
+ ENV['TWITTER_CONSUMER_SECRET']
+
+To start Sinatra use `rackup`.
@@ -0,0 +1,45 @@
+namespace :db do
+ desc 'Auto-migrate the database (destroys data)'
+ task :migrate => :environment do
+ DataMapper.auto_migrate!
+ end
+
+ desc 'Auto-upgrade the database (preserves data)'
+ task :upgrade => :environment do
+ DataMapper.auto_upgrade!
+ end
+end
+
+# This task is called by the Heroku scheduler add-on
+desc 'Unfollow all users that need to be unfollowed today'
+task :unfollow_users => :environment do
+ # find all users that need to be unfollowed today
+ unfollows = Unfollow.all(:date => Date.today)
+
+ puts "Unfollowing #{unfollows.size} users..."
+
+ unfollows.each do |unfollow|
+ client = Twitter::Client.new(
+ :oauth_token => unfollow.oauth_token,
+ :oauth_token_secret => unfollow.oauth_token_secret
+ )
+
+ # unfollow user
+ client.unfollow unfollow.user
+
+ # TODO: catch errors of the user is unfollowed twice
+ # notify the user of the unfollow
+ client.direct_message_create client.current_user.id, "tmp/follow has unfollowed @#{unfollow.user} for you."
+
+ puts "Unfollowed '#{unfollow.user}'"
+
+ # remove record
+ unfollow.destroy
+ end
+
+ puts 'Done.'
+end
+
+task :environment do
+ require './tmpfollow'
+end
@@ -0,0 +1,3 @@
+require './tmpfollow'
+
+run TmpFollow
@@ -0,0 +1,14 @@
+class Fixnum
+ def ordinalize
+ if (11..13).include?(self % 100)
+ "#{self}th"
+ else
+ case self % 10
+ when 1; "#{self}st"
+ when 2; "#{self}nd"
+ when 3; "#{self}rd"
+ else "#{self}th"
+ end
+ end
+ end
+end
@@ -0,0 +1,13 @@
+require 'dm-timestamps'
+
+class Unfollow
+ include DataMapper::Resource
+
+ property :id, Serial
+ property :user, String, :required => true
+ property :oauth_token, String, :required => true
+ property :oauth_token_secret, String, :required => true
+ property :date, Date, :required => true
+
+ timestamps :at
+end
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,141 @@
+ENV['RACK_ENV'] ||= 'development'
+
+require 'bundler'
+Bundler.require(:default, ENV['RACK_ENV'].to_sym)
+
+require 'logger'
+require 'sinatra/reloader' if Sinatra::Base.development?
+
+class TmpFollow < Sinatra::Base
+ enable :sessions
+ use Rack::Flash
+
+ configure do
+ Dir.glob('./lib/*.rb') { |file| require file }
+
+ database_url = ENV['SHARED_DATABASE_URL'] || "postgres://#{ENV['USER']}@localhost/tmpfollow_#{development? ? 'development' : 'test'}"
+
+ DataMapper.setup(:default, database_url)
+ DataMapper::Logger.new($stdout, :debug)
+
+ Twitter.configure do |config|
+ config.consumer_key = ENV['TWITTER_CONSUMER_KEY']
+ config.consumer_secret = ENV['TWITTER_CONSUMER_SECRET']
+ end
+
+ Compass.configuration do |config|
+ config.project_path = File.dirname(__FILE__)
+ config.sass_dir = 'views'
+ end
+
+ set :haml, {format: :html5}
+ end
+
+ helpers do
+ def flashes
+ [:notice, :alert].map do |type|
+ "<p class='flash #{type}'>#{flash[type]}</p>" if flash[type] != nil
+ end.join("\n")
+ end
+ end
+
+ get '/' do
+ haml :index
+ end
+
+ before '/follow' do
+ # validate username matches twitter username format
+ # TODO: remove the '@' if it exists
+ unless params[:user] =~ /^[A-Za-z0-9_]+/
+ flash[:alert] = 'Username invalid.'
+ redirect to '/'
+ end
+
+ # validate days is an integer
+ unless is_numeric?(params[:days]) || params[:days] == ''
+ flash[:alert] = 'Not a number.'
+ redirect to '/'
+ end
+ end
+
+ post '/follow' do
+ unfollow = Unfollow.new.tap do |u|
+ u.user = params[:user]
+ u.date = Date.today + params[:days].to_i
+ u.oauth_token = session[:oauth_token]
+ u.oauth_token_secret = session[:oauth_token_secret]
+ end
+
+ client = Twitter::Client.new(
+ :oauth_token => unfollow.oauth_token,
+ :oauth_token_secret => unfollow.oauth_token_secret
+ )
+ client.follow unfollow.user
+
+ if unfollow.save
+ flash[:notice] = "@#{unfollow.user} will be unfollowed on #{unfollow.date.strftime("%A, %b #{unfollow.date.day.ordinalize}")}!"
+ else
+ flash[:alert] = 'Not saved.'
+ end
+
+ redirect to '/'
+ end
+
+ # redirects the user to Twitter for authentication
+ get '/twitter' do
+ callback_url = "#{base_url}/twitter/callback"
+ request_token = oauth_consumer.get_request_token(:oauth_callback => callback_url)
+ session[:request_token] = request_token.token
+ session[:request_token_secret] = request_token.secret
+ redirect request_token.authorize_url
+ end
+
+ # used by Twitter as the callback URL after the user has authenticated
+ get '/twitter/callback' do
+ request_token = OAuth::RequestToken.new(oauth_consumer, session[:request_token], session[:request_token_secret])
+ begin
+ @oauth_tokens = request_token.get_access_token(
+ {},
+ :oauth_token => params[:oauth_token],
+ :oauth_verifier => params[:oauth_verifier]
+ )
+ rescue OAuth::Unauthorized => exception
+ flash[:alert] = 'Authenticating with Twitter failed.'
+ redirect to '/'
+ # exception.message
+ end
+
+ # store oauth_token and oauth_token_secret in the session
+ session[:oauth_token] = @oauth_tokens.token
+ session[:oauth_token_secret] = @oauth_tokens.secret
+
+ redirect to '/'
+ end
+
+ get '/clear' do
+ session[:oauth_token] = nil
+ session[:oauth_token_secret] = nil
+
+ flash[:alert] = 'Tokens cleared.'
+ redirect to '/'
+ end
+
+ get '/application.css' do
+ scss :application, Compass.sass_engine_options
+ end
+
+ private
+
+ def base_url
+ port = (request.port == 80) ? "" : ":#{request.port.to_s}"
+ "http://#{request.host}#{port}"
+ end
+
+ def oauth_consumer
+ OAuth::Consumer.new(ENV['TWITTER_CONSUMER_KEY'], ENV['TWITTER_CONSUMER_SECRET'], :site => 'https://twitter.com')
+ end
+
+ def is_numeric?(str)
+ !!str.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/)
+ end
+end
Oops, something went wrong.

0 comments on commit 9708516

Please sign in to comment.