anthonycrumley-twitter 0.3.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/History.txt +106 -0
  2. data/License.txt +19 -0
  3. data/Manifest.txt +71 -0
  4. data/README.txt +84 -0
  5. data/Rakefile +4 -0
  6. data/bin/twitter +15 -0
  7. data/config/hoe.rb +74 -0
  8. data/config/requirements.rb +17 -0
  9. data/examples/blocks.rb +15 -0
  10. data/examples/direct_messages.rb +28 -0
  11. data/examples/favorites.rb +20 -0
  12. data/examples/friends_followers.rb +25 -0
  13. data/examples/friendships.rb +13 -0
  14. data/examples/identica_timeline.rb +7 -0
  15. data/examples/location.rb +8 -0
  16. data/examples/posting.rb +9 -0
  17. data/examples/replies.rb +26 -0
  18. data/examples/search.rb +17 -0
  19. data/examples/sent_messages.rb +26 -0
  20. data/examples/timeline.rb +33 -0
  21. data/examples/twitter.rb +27 -0
  22. data/examples/verify_credentials.rb +13 -0
  23. data/lib/twitter.rb +21 -0
  24. data/lib/twitter/base.rb +252 -0
  25. data/lib/twitter/cli.rb +328 -0
  26. data/lib/twitter/cli/config.rb +9 -0
  27. data/lib/twitter/cli/helpers.rb +97 -0
  28. data/lib/twitter/cli/migrations/20080722194500_create_accounts.rb +13 -0
  29. data/lib/twitter/cli/migrations/20080722194508_create_tweets.rb +16 -0
  30. data/lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb +9 -0
  31. data/lib/twitter/cli/migrations/20080722214606_create_configurations.rb +13 -0
  32. data/lib/twitter/cli/models/account.rb +33 -0
  33. data/lib/twitter/cli/models/configuration.rb +13 -0
  34. data/lib/twitter/cli/models/tweet.rb +20 -0
  35. data/lib/twitter/direct_message.rb +22 -0
  36. data/lib/twitter/easy_class_maker.rb +43 -0
  37. data/lib/twitter/rate_limit_status.rb +19 -0
  38. data/lib/twitter/search.rb +94 -0
  39. data/lib/twitter/status.rb +22 -0
  40. data/lib/twitter/user.rb +37 -0
  41. data/lib/twitter/version.rb +9 -0
  42. data/script/destroy +14 -0
  43. data/script/generate +14 -0
  44. data/script/txt2html +74 -0
  45. data/setup.rb +1585 -0
  46. data/spec/base_spec.rb +109 -0
  47. data/spec/cli/helper_spec.rb +35 -0
  48. data/spec/direct_message_spec.rb +35 -0
  49. data/spec/fixtures/followers.xml +706 -0
  50. data/spec/fixtures/friends.xml +609 -0
  51. data/spec/fixtures/friends_for.xml +584 -0
  52. data/spec/fixtures/friends_lite.xml +192 -0
  53. data/spec/fixtures/friends_timeline.xml +66 -0
  54. data/spec/fixtures/public_timeline.xml +148 -0
  55. data/spec/fixtures/rate_limit_status.xml +7 -0
  56. data/spec/fixtures/search_results.json +1 -0
  57. data/spec/fixtures/status.xml +25 -0
  58. data/spec/fixtures/user.xml +38 -0
  59. data/spec/fixtures/user_timeline.xml +465 -0
  60. data/spec/search_spec.rb +89 -0
  61. data/spec/spec.opts +1 -0
  62. data/spec/spec_helper.rb +12 -0
  63. data/spec/status_spec.rb +40 -0
  64. data/spec/user_spec.rb +42 -0
  65. data/tasks/deployment.rake +50 -0
  66. data/tasks/environment.rake +7 -0
  67. data/tasks/website.rake +17 -0
  68. data/twitter.gemspec +49 -0
  69. data/website/css/common.css +47 -0
  70. data/website/images/terminal_output.png +0 -0
  71. data/website/index.html +156 -0
  72. metadata +180 -0
@@ -0,0 +1,16 @@
1
+ class CreateTweets < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :tweets do |t|
4
+ t.datetime :occurred_at
5
+ t.boolean :truncated, :favorited, :user_protected, :default => false
6
+ t.integer :twitter_id, :user_id, :in_reply_to_status_id, :in_reply_to_user_id, :user_followers_count
7
+ t.text :body
8
+ t.string :source, :user_name, :user_screen_name, :user_location, :user_description, :user_profile_image_url, :user_url
9
+ t.timestamps
10
+ end
11
+ end
12
+
13
+ def self.down
14
+ drop_table :tweets
15
+ end
16
+ end
@@ -0,0 +1,9 @@
1
+ class AddAccountIdToTweets < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :tweets, :account_id, :integer
4
+ end
5
+
6
+ def self.down
7
+ remove_column :tweets, :account_id
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ class CreateConfigurations < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :configurations do |t|
4
+ t.string :key
5
+ t.text :data
6
+ t.timestamps
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :accounts
12
+ end
13
+ end
@@ -0,0 +1,33 @@
1
+ class Account < ActiveRecord::Base
2
+ named_scope :current, :conditions => {:current => true}
3
+
4
+ has_many :tweets, :dependent => :destroy
5
+
6
+ def self.add(hash)
7
+ username = hash.delete(:username)
8
+ account = find_or_initialize_by_username(username)
9
+ account.attributes = hash
10
+ account.save
11
+ set_current(account) if new_active_needed?
12
+ end
13
+
14
+ def self.active
15
+ current.first
16
+ end
17
+
18
+ def self.set_current(account_or_id)
19
+ account = account_or_id.is_a?(Account) ? account_or_id : find(account_or_id)
20
+ account.update_attribute :current, true
21
+ Account.update_all "current = 0", "id != #{account.id}"
22
+ account
23
+ end
24
+
25
+ def self.new_active_needed?
26
+ self.current.count == 0 && self.count > 0
27
+ end
28
+
29
+ def to_s
30
+ "#{current? ? '*' : ' '} #{username}"
31
+ end
32
+ alias to_str to_s
33
+ end
@@ -0,0 +1,13 @@
1
+ class Configuration < ActiveRecord::Base
2
+ serialize :data
3
+
4
+ def self.[](key)
5
+ key = find_by_key(key.to_s)
6
+ key.nil? ? nil : key.data
7
+ end
8
+
9
+ def self.[]=(key, data)
10
+ c = find_or_create_by_key(key.to_s)
11
+ c.update_attribute :data, data
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ class Tweet < ActiveRecord::Base
2
+ belongs_to :account
3
+
4
+ def self.create_from_tweet(account, s)
5
+ tweet = account.tweets.find_or_initialize_by_twitter_id(s.id)
6
+ tweet.body = s.text
7
+ tweet.occurred_at = s.created_at
8
+
9
+ %w[truncated favorited in_reply_to_status_id in_reply_to_user_id source].each do |m|
10
+ tweet.send("#{m}=", s.send(m))
11
+ end
12
+
13
+ %w[id followers_count name screen_name location description
14
+ profile_image_url url protected].each do |m|
15
+ tweet.send("user_#{m}=", s.user.send(m))
16
+ end
17
+ tweet.save!
18
+ tweet
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Twitter
2
+ class DirectMessage
3
+ include EasyClassMaker
4
+
5
+ attributes :id, :text, :sender_id, :recipient_id, :created_at, :sender_screen_name, :recipient_screen_name
6
+
7
+ class << self
8
+ # Creates a new status from a piece of xml
9
+ def new_from_xml(xml)
10
+ DirectMessage.new do |d|
11
+ d.id = (xml).at('id').innerHTML
12
+ d.text = (xml).get_elements_by_tag_name('text').innerHTML
13
+ d.sender_id = (xml).at('sender_id').innerHTML
14
+ d.recipient_id = (xml).at('recipient_id').innerHTML
15
+ d.created_at = (xml).at('created_at').innerHTML
16
+ d.sender_screen_name = (xml).at('sender_screen_name').innerHTML
17
+ d.recipient_screen_name = (xml).at('recipient_screen_name').innerHTML
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ # This is pretty much just a macro for creating a class that allows
2
+ # using a block to initialize stuff and to define getters and setters
3
+ # really quickly.
4
+ module Twitter
5
+ module EasyClassMaker
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+ # creates the attributes class variable and creates each attribute's accessor methods
13
+ def attributes(*attrs)
14
+ @@attributes = attrs
15
+ @@attributes.each { |a| attr_accessor a }
16
+ end
17
+
18
+ # read method for attributes class variable
19
+ def self.attributes; @@attributes end
20
+ end
21
+
22
+ # allows for any class that includes this to use a block to initialize
23
+ # variables instead of assigning each one seperately
24
+ #
25
+ # Example:
26
+ #
27
+ # instead of...
28
+ #
29
+ # s = Status.new
30
+ # s.foo = 'thing'
31
+ # s.bar = 'another thing'
32
+ #
33
+ # you can ...
34
+ #
35
+ # Status.new do |s|
36
+ # s.foo = 'thing'
37
+ # s.bar = 'another thing'
38
+ # end
39
+ def initialize
40
+ yield self if block_given?
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,19 @@
1
+ module Twitter
2
+ class RateLimitStatus
3
+ include EasyClassMaker
4
+
5
+ attributes :reset_time_in_seconds, :reset_time, :remaining_hits, :hourly_limit
6
+
7
+ class << self
8
+ # Creates a new rate limi status from a piece of xml
9
+ def new_from_xml(xml)
10
+ RateLimitStatus.new do |s|
11
+ s.reset_time_in_seconds = xml.at('reset-time-in-seconds').inner_html.to_i
12
+ s.reset_time = Time.parse xml.at('reset-time').inner_html
13
+ s.remaining_hits = xml.at('remaining-hits').inner_html.to_i
14
+ s.hourly_limit = xml.at('hourly-limit').inner_html.to_i
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,94 @@
1
+ gem 'httparty'
2
+ require 'httparty'
3
+
4
+ module Twitter
5
+ class Search
6
+ include HTTParty
7
+ include Enumerable
8
+ base_uri 'search.twitter.com'
9
+
10
+ attr_reader :result, :query
11
+
12
+ def initialize(q=nil)
13
+ clear
14
+ containing(q) unless q.blank?
15
+ end
16
+
17
+ def from(user)
18
+ @query[:q] << "from:#{user}"
19
+ self
20
+ end
21
+
22
+ def to(user)
23
+ @query[:q] << "to:#{user}"
24
+ self
25
+ end
26
+
27
+ def referencing(user)
28
+ @query[:q] << "@#{user}"
29
+ self
30
+ end
31
+ alias :references :referencing
32
+ alias :ref :referencing
33
+
34
+ def containing(word)
35
+ @query[:q] << "#{word}"
36
+ self
37
+ end
38
+ alias :contains :containing
39
+
40
+ # adds filtering based on hash tag ie: #twitter
41
+ def hashed(tag)
42
+ @query[:q] << "##{tag}"
43
+ self
44
+ end
45
+
46
+ # lang must be ISO 639-1 code ie: en, fr, de, ja, etc.
47
+ #
48
+ # when I tried en it limited my results a lot and took
49
+ # out several tweets that were english so i'd avoid
50
+ # this unless you really want it
51
+ def lang(lang)
52
+ @query[:lang] = lang
53
+ self
54
+ end
55
+
56
+ # Limits the number of results per page
57
+ def per_page(num)
58
+ @query[:rpp] = num
59
+ self
60
+ end
61
+
62
+ # Only searches tweets since a given id.
63
+ # Recommended to use this when possible.
64
+ def since(since_id)
65
+ @query[:since_id] = since_id
66
+ self
67
+ end
68
+
69
+ # Search tweets by longitude, latitude and a given range.
70
+ # Ranges like 25km and 50mi work.
71
+ def geocode(long, lat, range)
72
+ @query[:geocode] = [long, lat, range].join(',')
73
+ self
74
+ end
75
+
76
+ # Clears all the query filters to make a new search
77
+ def clear
78
+ @query = {}
79
+ @query[:q] = []
80
+ self
81
+ end
82
+
83
+ # If you want to get results do something other than iterate over them.
84
+ def fetch
85
+ @query[:q] = @query[:q].join(' ')
86
+ self.class.get('/search.json', {:query => @query})
87
+ end
88
+
89
+ def each
90
+ @result = fetch()
91
+ @result['results'].each { |r| yield r }
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,22 @@
1
+ module Twitter
2
+ class Status
3
+ include EasyClassMaker
4
+
5
+ attributes :created_at, :id, :text, :user, :source, :truncated, :in_reply_to_status_id, :in_reply_to_user_id, :favorited
6
+
7
+ # Creates a new status from a piece of xml
8
+ def self.new_from_xml(xml)
9
+ s = new
10
+ s.id = (xml).at('id').innerHTML
11
+ s.created_at = (xml).at('created_at').innerHTML
12
+ s.text = (xml).get_elements_by_tag_name('text').innerHTML
13
+ s.source = (xml).at('source').innerHTML
14
+ s.truncated = (xml).at('truncated').innerHTML == 'false' ? false : true
15
+ s.favorited = (xml).at('favorited').innerHTML == 'false' ? false : true
16
+ s.in_reply_to_status_id = (xml).at('in_reply_to_status_id').innerHTML
17
+ s.in_reply_to_user_id = (xml).at('in_reply_to_user_id').innerHTML
18
+ s.user = User.new_from_xml(xml.at('user')) if (xml).at('user')
19
+ s
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ module Twitter
2
+ class User
3
+ include EasyClassMaker
4
+
5
+ attributes :id, :name, :screen_name, :status, :location, :description, :url,
6
+ :profile_image_url, :profile_background_color, :profile_text_color, :profile_link_color,
7
+ :profile_sidebar_fill_color, :profile_sidebar_border_color, :friends_count, :followers_count,
8
+ :favourites_count, :statuses_count, :utc_offset , :protected
9
+
10
+ # Creates a new user from a piece of xml
11
+ def self.new_from_xml(xml)
12
+ u = new
13
+ u.id = (xml).at('id').innerHTML
14
+ u.name = (xml).at('name').innerHTML
15
+ u.screen_name = (xml).at('screen_name').innerHTML
16
+ u.location = (xml).at('location').innerHTML
17
+ u.description = (xml).at('description').innerHTML
18
+ u.url = (xml).at('url').innerHTML
19
+ u.profile_image_url = (xml).at('profile_image_url').innerHTML
20
+
21
+ # optional, not always present
22
+ u.profile_background_color = (xml).at('profile_background_color').innerHTML if (xml).at('profile_background_color')
23
+ u.profile_text_color = (xml).at('profile_text_color').innerHTML if (xml).at('profile_text_color')
24
+ u.profile_link_color = (xml).at('profile_link_color').innerHTML if (xml).at('profile_link_color')
25
+ u.profile_sidebar_fill_color = (xml).at('profile_sidebar_fill_color').innerHTML if (xml).at('profile_sidebar_fill_color')
26
+ u.profile_sidebar_border_color = (xml).at('profile_sidebar_border_color').innerHTML if (xml).at('profile_sidebar_border_color')
27
+ u.friends_count = (xml).at('friends_count').innerHTML if (xml).at('friends_count')
28
+ u.followers_count = (xml).at('followers_count').innerHTML if (xml).at('followers_count')
29
+ u.favourites_count = (xml).at('favourites_count').innerHTML if (xml).at('favourites_count')
30
+ u.statuses_count = (xml).at('statuses_count').innerHTML if (xml).at('statuses_count')
31
+ u.utc_offset = (xml).at('utc_offset').innerHTML if (xml).at('utc_offset')
32
+ u.protected = (xml).at('protected').innerHTML == 'false' ? false : true if (xml).at('protected')
33
+ u.status = Status.new_from_xml(xml.at('status')) if (xml).at('status')
34
+ u
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,9 @@
1
+ module Twitter #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 3
5
+ TINY = 7
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.join(File.dirname(__FILE__), '..')
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ begin
5
+ require 'newgem'
6
+ rescue LoadError
7
+ puts "\n\nGenerating the website requires the newgem RubyGem"
8
+ puts "Install: gem install newgem\n\n"
9
+ exit(1)
10
+ end
11
+ require 'redcloth'
12
+ require 'syntax/convertors/html'
13
+ require 'erb'
14
+ require File.dirname(__FILE__) + '/../lib/twitter/version.rb'
15
+
16
+ version = Twitter.git::VERSION::STRING
17
+ download = 'http://rubyforge.org/projects/twitter'
18
+
19
+ class Fixnum
20
+ def ordinal
21
+ # teens
22
+ return 'th' if (10..19).include?(self % 100)
23
+ # others
24
+ case self % 10
25
+ when 1: return 'st'
26
+ when 2: return 'nd'
27
+ when 3: return 'rd'
28
+ else return 'th'
29
+ end
30
+ end
31
+ end
32
+
33
+ class Time
34
+ def pretty
35
+ return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
36
+ end
37
+ end
38
+
39
+ def convert_syntax(syntax, source)
40
+ return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
41
+ end
42
+
43
+ if ARGV.length >= 1
44
+ src, template = ARGV
45
+ template ||= File.join(File.dirname(__FILE__), '/../website/template.rhtml')
46
+
47
+ else
48
+ puts("Usage: #{File.split($0).last} source.txt [template.rhtml] > output.html")
49
+ exit!
50
+ end
51
+
52
+ template = ERB.new(File.open(template).read)
53
+
54
+ title = nil
55
+ body = nil
56
+ File.open(src) do |fsrc|
57
+ title_text = fsrc.readline
58
+ body_text = fsrc.read
59
+ syntax_items = []
60
+ body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
61
+ ident = syntax_items.length
62
+ element, syntax, source = $1, $2, $3
63
+ syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
64
+ "syntax-temp-#{ident}"
65
+ }
66
+ title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
67
+ body = RedCloth.new(body_text).to_html
68
+ body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
69
+ end
70
+ stat = File.stat(src)
71
+ created = stat.ctime
72
+ modified = stat.mtime
73
+
74
+ $stdout << template.result(binding)