chirpstream 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -6,10 +6,10 @@ begin
6
6
  s.email = "joshbuddy@gmail.com"
7
7
  s.homepage = "http://github.com/joshbuddy/chirpstream"
8
8
  s.authors = ["Joshua Hull"]
9
- s.files = FileList["[A-Z]*", "{lib}/**/*"]
9
+ s.files = FileList["[A-Z]*", "{lib}/**/*", "{bin}/**/*"]
10
10
  s.add_dependency 'eventmachine', ">= 0.12.10"
11
11
  s.add_dependency 'em-http-request', ">= 0.2.7"
12
- s.add_dependency 'json', ">= 1.2.4"
12
+ s.add_dependency 'yajl-ruby', ">= 0.7.5"
13
13
  s.add_dependency 'load_path_find', ">= 0.0.5"
14
14
  end
15
15
  Jeweler::GemcutterTasks.new
data/Readme.rdoc CHANGED
@@ -12,8 +12,9 @@ This is an experimental EventMachine-based client for http://chirpstream.twitter
12
12
 
13
13
  chirp = Chirpstream.new('joshbuddy', 'xxxxxxxx')
14
14
  chirp.reconnect{ puts "reconnecting..." }
15
- chirp.tweet {|t| puts "#{t['text']} (from #{t['user']['name'].foreground(:red)} (#{('@' + t['user']['screen_name']).foreground(:green)}))" }
16
- chirp.follow {|t| puts "#{t.data['source'].data['screen_name'].foreground(:green)} following #{t.data['target'].data['screen_name'].foreground(:green)}" }
17
- chirp.favorite {|t| puts "#{t.data['source'].data['screen_name'].foreground(:green)} <3 -> #{t.data['target_object'].data['text']}" }
18
- chirp.retweet {|t| puts "#{t.data['source'].data['screen_name'].foreground(:green)} RT -> #{t.data['target_object'].data['text']}" }
15
+ chirp.tweet {|t| puts "#{t.text} (from #{t.user.name.foreground(:red)} (#{('@' + t.user.screen_name).foreground(:green)}))" }
16
+ chirp.follow {|t| puts "#{t.source.screen_name.foreground(:green)} following #{t.target.screen_name.foreground(:green)}" }
17
+ chirp.favorite {|t| puts "#{t.source.screen_name.foreground(:green)} <3 -> #{t.target_object.text}" }
18
+ chirp.retweet {|t| puts "#{t.source.screen_name.foreground(:green)} RT -> #{t.target_object.text}" }
19
19
  chirp.connect
20
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
data/bin/chirp_growl ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby -rubygems
2
+
3
+ begin
4
+ require 'growl'
5
+ rescue LoadError
6
+ puts "You'll need to install growl\nsudo gem install growl"
7
+ exit(1)
8
+ end
9
+
10
+ begin
11
+ require 'json'
12
+ rescue LoadError
13
+ puts "You'll need to install json\nsudo gem install json"
14
+ exit(1)
15
+ end
16
+
17
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'chirpstream')
18
+ require 'etc'
19
+ require 'fileutils'
20
+
21
+ FileUtils.mkdir_p(File.join(Etc.getpwuid.dir, '.chirpstream'))
22
+ FileUtils.mkdir_p(File.join(Etc.getpwuid.dir, '.chirpstream', 'cache'))
23
+
24
+ json_file = File.join(Etc.getpwuid.dir, '.chirpstream', 'user.json')
25
+ cache_dir = File.join(Etc.getpwuid.dir, '.chirpstream', 'cache')
26
+
27
+ if File.exist?(json_file)
28
+ data = JSON.parse(File.read(json_file))
29
+ @username = data['username']
30
+ @password = data['password']
31
+ else
32
+ puts "Enter your username:"
33
+ @username = gets.strip
34
+ puts "Enter your password:"
35
+ @password = gets.strip
36
+ File.open(json_file, 'w') {|f| f << {:username => @username, :password => @password}.to_json}
37
+ end
38
+
39
+ chirp = Chirpstream.new(@username, @password)
40
+
41
+ Growl.notify {
42
+ self.message = 'Starting chirp_growl...'
43
+ self.title = 'ChirpStreeeeem!'
44
+ self.image = File.join(File.dirname(__FILE__), '..', 'image', 'bird.png')
45
+ }
46
+
47
+ chirp.tweet { |t|
48
+ t.user.with_profile_image(cache_dir) do |image_path|
49
+ Growl.notify {
50
+ self.message = t.text
51
+ self.title = "@#{t.user.screen_name}"
52
+ self.image = image_path if image_path
53
+ }
54
+ end
55
+ }
56
+
57
+ chirp.connect
58
+
59
+
@@ -1,4 +1,7 @@
1
1
  class Chirpstream
2
2
  class Delete < Event
3
+ ATTRS = [:id, :user_id]
4
+ attr_accessor *ATTRS
5
+ user_writer :user_id
3
6
  end
4
7
  end
@@ -1,4 +1,8 @@
1
1
  class Chirpstream
2
2
  class Favorite < Event
3
+ ATTRS = [ :target, :source, :target_object ]
4
+ attr_accessor *ATTRS
5
+ user_writer :target, :source
6
+ tweet_writer :target_object
3
7
  end
4
8
  end
@@ -1,4 +1,7 @@
1
1
  class Chirpstream
2
2
  class Follow < Event
3
+ ATTRS = [ :target, :source ]
4
+ attr_accessor *ATTRS
5
+ user_writer :target, :source
3
6
  end
4
7
  end
@@ -1,4 +1,8 @@
1
1
  class Chirpstream
2
2
  class Retweet < Event
3
+ ATTRS = [ :target, :source, :target_object ]
4
+ attr_accessor *ATTRS
5
+ user_writer :target, :source
6
+ tweet_writer :target_object
3
7
  end
4
8
  end
@@ -1,19 +1,5 @@
1
1
  class Chirpstream
2
2
 
3
- class Event
4
-
5
- attr_reader :data
6
-
7
- def initialize(data)
8
- @data = data
9
- end
10
-
11
- def load_all(&block)
12
- if unloaded_data = @data.values.find{|d| d.respond_to?(:loaded?) && !d.loaded? }
13
- unloaded_data.load { load_all(&block) }
14
- else
15
- block.call(self)
16
- end
17
- end
3
+ class Event < TwitterObject
18
4
  end
19
5
  end
@@ -1,29 +1,17 @@
1
1
  class Chirpstream
2
- class Tweet
2
+ class Tweet < TwitterObject
3
3
 
4
- URL = "http://api.twitter.com/1/statuses/show/%s.json"
5
-
6
- attr_reader :id, :data
7
-
8
- def initialize(base, id)
9
- @base = base
10
- @id = id
11
- end
12
-
13
- def load
14
- unless loaded?
15
- http = EM::HttpRequest.new(URL % id).get(:head => {'authorization' => [@base.username, @base.password]})
16
- http.callback {
17
- if http.response_header.status == 200
18
- @data = JSON.parse(http.response)
19
- yield self
20
- end
21
- }
22
- end
4
+ ATTRS = [:coordinates, :favorited, :created_at, :truncated, :contributors, :text, :id, :geo, :in_reply_to_user_id, :place, :source, :in_reply_to_screen_name, :in_reply_to_status_id, :user]
5
+
6
+ attr_accessor *ATTRS
7
+ user_writer :user
8
+
9
+ def tweet_loadable_id
10
+ id
23
11
  end
24
12
 
25
13
  def loaded?
26
- @data
14
+ !text.nil?
27
15
  end
28
16
  end
29
17
  end
@@ -0,0 +1,135 @@
1
+ class TwitterObject
2
+
3
+ SINGLE_USER_URL = "http://api.twitter.com/1/users/show/%s.json"
4
+ SINGLE_TWEET_URL = "http://api.twitter.com/1/statuses/show/%s.json"
5
+
6
+ attr_reader :base
7
+
8
+ def initialize(base, data = nil)
9
+ @base = base
10
+ from_json(data) if data
11
+ yield self if block_given?
12
+ end
13
+
14
+ def self.user_writer(*attrs)
15
+ attrs.each do |attr|
16
+ module_eval "
17
+ def #{attr}=(#{attr})
18
+ @#{attr} = if #{attr}.is_a?(Hash)
19
+ Chirpstream::User.new(base, #{attr})
20
+ else
21
+ Chirpstream::User.new(base) {|u| u.id = #{attr}}
22
+ end
23
+ end
24
+ "
25
+ end
26
+ end
27
+
28
+ def self.tweet_writer(*attrs)
29
+ attrs.each do |attr|
30
+ module_eval "
31
+ def #{attr}=(#{attr})
32
+ @#{attr} = if #{attr}.is_a?(Hash)
33
+ Chirpstream::Tweet.new(base, #{attr})
34
+ else
35
+ Chirpstream::Tweet.new(base) {|t| t.id = #{attr}}
36
+ end
37
+ end
38
+ "
39
+ end
40
+ end
41
+
42
+ def from_json(data)
43
+ self.class.attrs.each { |a| self.send(:"#{a}=", data[a.to_s]) }
44
+ end
45
+
46
+ def self.attrs
47
+ const_get(:ATTRS)
48
+ end
49
+
50
+ def load_all(&block)
51
+ attrs = self.class.attrs
52
+ if respond_to?(:loaded?) && !loaded?
53
+ if respond_to?(:user_loadable_id)
54
+ from_json(get_user_data(user_loadable_id)[user_loadable_id])
55
+ load_all(&block)
56
+ elsif respond_to?(:tweet_loadable_id)
57
+ from_json(get_tweet_data(tweet_loadable_id)[tweet_loadable_id])
58
+ load_all(&block)
59
+ end
60
+ else
61
+ tweet_ids = {}
62
+ user_ids = {}
63
+ attrs.each do |a|
64
+ obj = send(a)
65
+ if obj.respond_to?(:loaded?) && !obj.loaded?
66
+ if obj.respond_to?(:user_loadable_id)
67
+ user_ids[a] = obj.user_loadable_id
68
+ elsif obj.respond_to?(:tweet_loadable_id)
69
+ tweet_ids[a] = obj.tweet_loadable_id
70
+ end
71
+ end
72
+ end
73
+ get_tweet_data(tweet_ids.values) { |tweet_data|
74
+ tweet_ids.each{|k,v| self.send(:"#{k}=", tweet_data[v])}
75
+ user_data = get_user_data(user_ids.values) { |user_data|
76
+ user_ids.each{|k,v| self.send(:"#{k}=", user_data[v])}
77
+ yield self
78
+ }
79
+ }
80
+ end
81
+
82
+ end
83
+
84
+ def get_tweet_data(ids)
85
+ ids = Array(ids).uniq
86
+ data = {}
87
+ if ids.empty?
88
+ yield data
89
+ else
90
+ load_tweet_data(ids, data) { yield data }
91
+ end
92
+ end
93
+
94
+ def load_tweet_data(ids, data, &block)
95
+ id = ids.shift
96
+ if (id)
97
+ parser = Yajl::Parser.new
98
+ parser.on_parse_complete = proc { |parsed|
99
+ data[id] = parsed
100
+ load_tweet_data(ids, data, &block)
101
+ }
102
+ http = EM::HttpRequest.new("http://api.twitter.com/1/statuses/show/%s.json" % id).get :head => {'authorization' => [base.username, base.password]}
103
+ http.stream { |chunk|
104
+ parser << chunk
105
+ }
106
+ else
107
+ yield
108
+ end
109
+ end
110
+
111
+ def get_user_data(ids)
112
+ ids = Array(ids).uniq
113
+ data = {}
114
+ if ids.empty?
115
+ yield data
116
+ else
117
+ load_user_data(ids, data) { yield data }
118
+ end
119
+ end
120
+
121
+ def load_user_data(ids, data)
122
+ parser = Yajl::Parser.new
123
+ parser.on_parse_complete = proc { |parsed|
124
+ parsed.each do |user|
125
+ data[user["id"]] = user
126
+ end
127
+ yield
128
+ }
129
+ http = EM::HttpRequest.new("http://api.twitter.com/1/users/lookup.json").post :head => {'authorization' => [base.username, base.password]}, :body => {'user_id' => ids.join(',')}
130
+ http.stream { |chunk|
131
+ parser << chunk
132
+ }
133
+ end
134
+
135
+ end
@@ -1,29 +1,46 @@
1
+ require 'em-http'
2
+ require 'digest/md5'
3
+
1
4
  class Chirpstream
2
- class User
3
-
4
- URL = "http://api.twitter.com/1/users/show/%s.json"
5
-
6
- attr_reader :id, :data
5
+ class User < TwitterObject
7
6
 
8
- def initialize(base, id)
9
- @base = base
10
- @id = id
7
+ ATTRS = [
8
+ :profile_background_tile, :name, :profile_sidebar_border_color, :profile_sidebar_fill_color, :profile_image_url, :location, :created_at,
9
+ :profile_link_color, :url, :contributors_enabled, :favourites_count, :utc_offset, :id, :followers_count, :protected, :lang,
10
+ :profile_text_color, :geo_enabled, :profile_background_color, :time_zone, :notifications, :description, :verified, :profile_background_image_url,
11
+ :statuses_count, :friends_count, :screen_name, :following
12
+ ]
13
+
14
+ attr_accessor *ATTRS
15
+
16
+ def loaded?
17
+ name
18
+ end
19
+
20
+ def user_loadable_id
21
+ id
11
22
  end
12
-
13
- def load
14
- unless loaded?
15
- http = EM::HttpRequest.new(URL % id).get(:head => {'authorization' => [@base.username, @base.password]})
16
- http.callback {
23
+
24
+ def with_profile_image(cache_dir)
25
+ raise unless loaded?
26
+
27
+ cached_file = File.join(cache_dir, "#{Digest::MD5.hexdigest(profile_image_url)}#{File.extname(profile_image_url)}")
28
+
29
+ if File.exist?(cached_file)
30
+ yield cached_file
31
+ else
32
+ http = EM::HttpRequest.new(profile_image_url).get :head => {'authorization' => [base.username, base.password]}
33
+ http.callback do
17
34
  if http.response_header.status == 200
18
- @data = JSON.parse(http.response)
19
- yield self
35
+ File.open(cached_file, 'w') {|f| f << http.response}
36
+ yield cached_file
37
+ else
38
+ yield nil
20
39
  end
21
- }
40
+ end
22
41
  end
42
+
23
43
  end
24
-
25
- def loaded?
26
- @data
27
- end
44
+
28
45
  end
29
46
  end
data/lib/chirpstream.rb CHANGED
@@ -1,11 +1,12 @@
1
1
  require 'eventmachine'
2
2
  require 'em-http'
3
- require 'json'
3
+ require 'yajl'
4
4
  require 'pp'
5
5
  require 'load_path_find'
6
6
 
7
7
  $LOAD_PATH.add_current
8
8
 
9
+ require 'chirpstream/twitter_object'
9
10
  require 'chirpstream/event'
10
11
  require 'chirpstream/event/follow'
11
12
  require 'chirpstream/event/retweet'
@@ -61,63 +62,63 @@ class Chirpstream
61
62
  def dispatch_friend(data)
62
63
  unless @handlers.friend.empty?
63
64
  data['friends'].each_slice(100) do |friend_ids|
64
- friend_http = EM::HttpRequest.new("http://api.twitter.com/1/users/lookup.json").post(:body => {'user_id' => friend_ids.join(',')}, :head => {'authorization' => [@username, @password]})
65
- friend_http.callback do
66
- friends = JSON.parse(friend_http.response)
65
+ parser = Yajl::Parser.new
66
+ parser.on_parse_complete = proc { |friends|
67
67
  friends.each do |friend|
68
68
  @handlers.friend.each{|h| h.call(friend)}
69
69
  end
70
- end
70
+ }
71
+ friend_http = EM::HttpRequest.new("http://api.twitter.com/1/users/lookup.json").post(:body => {'user_id' => friend_ids.join(',')}, :head => {'authorization' => [@username, @password]})
72
+ http.stream { |chunk|
73
+ parser << chunk
74
+ }
71
75
  end
72
76
  end
73
77
  end
74
78
 
75
79
  def dispatch_tweet(data)
76
80
  unless @handlers.tweet.empty?
77
- @handlers.tweet.each{|h| h.call(data)}
81
+ tweet = Tweet.new(self, data)
82
+ tweet.load_all { |t|
83
+ @handlers.tweet.each{|h| h.call(tweet)}
84
+ }
78
85
  end
79
86
  end
80
87
 
81
88
  def dispatch_follow(data)
82
89
  unless @handlers.follow.empty?
83
- data['target'] = User.new(self, data['target']['id'])
84
- data['source'] = User.new(self, data['source']['id'])
85
- e = Follow.new(data)
86
- e.load_all {
87
- @handlers.follow.each{|h| h.call(e)}
90
+ follow = Follow.new(self, data)
91
+ follow.load_all { |f|
92
+ @handlers.follow.each{|h| h.call(f)}
88
93
  }
89
94
  end
90
95
  end
91
96
 
92
97
  def dispatch_favorite(data)
93
- return if @handlers.follow.empty?
94
- data['target'] = User.new(self, data['target']['id'])
95
- data['source'] = User.new(self, data['source']['id'])
96
- data['target_object'] = Tweet.new(self, data['target_object']['id'])
97
- e = Favorite.new(data)
98
- e.load_all {
99
- @handlers.favorite.each{|h| h.call(e)}
100
- }
98
+ unless @handlers.favorite.empty?
99
+ favorite = Favorite.new(self, data)
100
+ favorite.load_all { |f|
101
+ @handlers.favorite.each{|h| h.call(f)}
102
+ }
103
+ end
101
104
  end
102
105
 
103
106
  def dispatch_retweet(data)
104
- return if @handlers.retweet.empty?
105
- data['target'] = User.new(self, data['target']['id'])
106
- data['source'] = User.new(self, data['source']['id'])
107
- data['target_object'] = Tweet.new(self, data['target_object']['id'])
108
- e = Retweet.new(data)
109
- e.load_all {
110
- @handlers.retweet.each{|h| h.call(e)}
111
- }
107
+ unless @handlers.retweet.empty?
108
+ retweet = Retweet.new(self, data)
109
+ retweet.load_all { |f|
110
+ @handlers.retweet.each{|h| h.call(f)}
111
+ }
112
+ end
112
113
  end
113
114
 
114
115
  def dispatch_delete(data)
115
- return if @handlers.delete.empty?
116
- data['delete']['user_id'] = User.new(self, data['delete']['user_id'])
117
- e = Delete.new(data)
118
- e.load_all {
119
- @handlers.delete.each{|h| h.call(e)}
120
- }
116
+ unless @handlers.delete.empty?
117
+ delete = Delete.new(self, data)
118
+ delete.load_all { |f|
119
+ @handlers.delete.each{|h| h.call(f)}
120
+ }
121
+ end
121
122
  end
122
123
 
123
124
  def dispatch_reconnect
@@ -125,45 +126,44 @@ class Chirpstream
125
126
  @handlers.reconnect.each{|h| h.call}
126
127
  end
127
128
 
129
+ def handle_tweet(parsed_data)
130
+ if parsed_data['friends']
131
+ dispatch_friend(parsed_data)
132
+ elsif parsed_data['text']
133
+ dispatch_tweet(parsed_data)
134
+ elsif parsed_data['event']
135
+ case parsed_data['event']
136
+ when 'follow'
137
+ dispatch_follow(parsed_data)
138
+ when 'favorite'
139
+ dispatch_favorite(parsed_data)
140
+ when 'retweet'
141
+ dispatch_retweet(parsed_data)
142
+ else
143
+ puts "weird event"
144
+ pp parsed_data
145
+ end
146
+ elsif parsed_data['delete']
147
+ dispatch_delete(parsed_data)
148
+ else
149
+ puts "i didn't know what to do with this!"
150
+ pp parsed_data
151
+ end
152
+ end
153
+
128
154
  def connect
129
155
  unless EM.reactor_running?
130
156
  EM.run { connect }
131
157
  else
158
+ parser = Yajl::Parser.new
159
+ parser.on_parse_complete = method(:handle_tweet)
132
160
  http = EM::HttpRequest.new(@connect_url).get :head => {'authorization' => [@username, @password]}
133
161
  http.errback { |e, err|
134
162
  dispatch_reconnect
135
163
  connect
136
164
  }
137
- http.stream { |chunk|
138
- @data << chunk
139
- begin
140
- parsed_data = JSON.parse(@data)
141
- @data = ''
142
- if parsed_data['friends']
143
- dispatch_friend(parsed_data)
144
- elsif parsed_data['text']
145
- dispatch_tweet(parsed_data)
146
- elsif parsed_data['event']
147
- case parsed_data['event']
148
- when 'follow'
149
- dispatch_follow(parsed_data)
150
- when 'favorite'
151
- dispatch_favorite(parsed_data)
152
- when 'retweet'
153
- dispatch_retweet(parsed_data)
154
- else
155
- puts "weird event"
156
- pp parsed_data
157
- end
158
- elsif parsed_data['delete']
159
- dispatch_delete(parsed_data)
160
- else
161
- puts "i didn't know what to do with this!"
162
- pp parsed_data
163
- end
164
- rescue JSON::ParserError
165
- #puts "need more"
166
- end
165
+ http.stream { |chunk|
166
+ parser << chunk
167
167
  }
168
168
  end
169
169
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 0
8
- - 2
9
- version: 0.0.2
8
+ - 3
9
+ version: 0.0.3
10
10
  platform: ruby
11
11
  authors:
12
12
  - Joshua Hull
@@ -14,8 +14,8 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-04-22 00:00:00 -04:00
18
- default_executable:
17
+ date: 2010-05-01 00:00:00 -04:00
18
+ default_executable: chirp_growl
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: eventmachine
@@ -46,17 +46,17 @@ dependencies:
46
46
  type: :runtime
47
47
  version_requirements: *id002
48
48
  - !ruby/object:Gem::Dependency
49
- name: json
49
+ name: yajl-ruby
50
50
  prerelease: false
51
51
  requirement: &id003 !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  segments:
56
- - 1
57
- - 2
58
- - 4
59
- version: 1.2.4
56
+ - 0
57
+ - 7
58
+ - 5
59
+ version: 0.7.5
60
60
  type: :runtime
61
61
  version_requirements: *id003
62
62
  - !ruby/object:Gem::Dependency
@@ -75,8 +75,8 @@ dependencies:
75
75
  version_requirements: *id004
76
76
  description: Eventmachine-based Chirpstream client
77
77
  email: joshbuddy@gmail.com
78
- executables: []
79
-
78
+ executables:
79
+ - chirp_growl
80
80
  extensions: []
81
81
 
82
82
  extra_rdoc_files: []
@@ -86,14 +86,15 @@ files:
86
86
  - Rakefile
87
87
  - Readme.rdoc
88
88
  - VERSION
89
+ - bin/chirp_growl
89
90
  - lib/chirpstream.rb
90
91
  - lib/chirpstream/event.rb
91
92
  - lib/chirpstream/event/delete.rb
92
93
  - lib/chirpstream/event/favorite.rb
93
94
  - lib/chirpstream/event/follow.rb
94
95
  - lib/chirpstream/event/retweet.rb
95
- - lib/chirpstream/loadable.rb
96
96
  - lib/chirpstream/tweet.rb
97
+ - lib/chirpstream/twitter_object.rb
97
98
  - lib/chirpstream/user.rb
98
99
  has_rdoc: true
99
100
  homepage: http://github.com/joshbuddy/chirpstream
File without changes