jnunemaker-twitter 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History CHANGED
@@ -1,3 +1,12 @@
1
+ 0.4.1 - January 1, 2009
2
+ * 4 minor enhancements and 2 bug fixes:
3
+ * Added better exception handling (Billy Gray)
4
+ * Added page to search (Michael Ivey)
5
+ * Adding an option to display tweets on CLI in reverse order (oldest first). (Cameron Booth)
6
+ * Added in_reply_to_status_id option for replying to statuses (anthonycrumley)
7
+ * Fixed a bug where the @config was improperly set (K. Adam Christensen)
8
+ * Fix verify_credentials to include a format (breaks in laconica). (dustin)
9
+
1
10
  0.4.0 - December 23, 2008
2
11
  * 3 major changes
3
12
  * Removed active support as dependency
data/Manifest CHANGED
@@ -29,6 +29,8 @@ lib/twitter/direct_message.rb
29
29
  lib/twitter/easy_class_maker.rb
30
30
  lib/twitter/rate_limit_status.rb
31
31
  lib/twitter/search.rb
32
+ lib/twitter/search_result.rb
33
+ lib/twitter/search_result_info.rb
32
34
  lib/twitter/status.rb
33
35
  lib/twitter/user.rb
34
36
  lib/twitter/version.rb
@@ -45,8 +47,11 @@ spec/fixtures/friends.xml
45
47
  spec/fixtures/friends_for.xml
46
48
  spec/fixtures/friends_lite.xml
47
49
  spec/fixtures/friends_timeline.xml
50
+ spec/fixtures/friendship_already_exists.xml
51
+ spec/fixtures/friendship_created.xml
48
52
  spec/fixtures/public_timeline.xml
49
53
  spec/fixtures/rate_limit_status.xml
54
+ spec/fixtures/search_result_info.yml
50
55
  spec/fixtures/search_results.json
51
56
  spec/fixtures/status.xml
52
57
  spec/fixtures/user.xml
data/bin/twitter CHANGED
File without changes
data/examples/search.rb CHANGED
@@ -2,6 +2,7 @@ require 'rubygems'
2
2
  require File.join(File.dirname(__FILE__), '..', 'lib', 'twitter')
3
3
 
4
4
  Twitter::Search.new('httparty').each { |r| puts r.inspect,'' }
5
+ Twitter::Search.new('httparty').page(2).each { |r| puts r.inspect, '' }
5
6
 
6
7
  # search = Twitter::Search.new
7
8
  # search.from('jnunemaker').to('oaknd1').each { |r| puts r.inspect, '' }
data/lib/twitter/base.rb CHANGED
@@ -12,8 +12,10 @@ module Twitter
12
12
  # Identi.ca example:
13
13
  # Twitter.new('email/username', 'password', :api_host => 'identi.ca/api')
14
14
  def initialize(email, password, options={})
15
- @config, @config[:email], @config[:password] = {}, email, password
16
15
  @api_host = options.delete(:api_host) || 'twitter.com'
16
+ @config, @config[:email], @config[:password] = options, email, password
17
+ @proxy_host = options[:proxy_host]
18
+ @proxy_port = options[:proxy_port]
17
19
  end
18
20
 
19
21
  # Returns an array of statuses for a timeline; Defaults to your friends timeline.
@@ -159,6 +161,7 @@ module Twitter
159
161
  def post(status, options={})
160
162
  form_data = {'status' => status}
161
163
  form_data.merge!({'source' => options[:source]}) if options[:source]
164
+ form_data.merge!({'in_reply_to_status_id' => options[:in_reply_to_status_id]}) if options[:in_reply_to_status_id]
162
165
  Status.new_from_xml(request('statuses/update.xml', :auth => true, :method => :post, :form_data => form_data))
163
166
  end
164
167
  alias :update :post
@@ -166,7 +169,7 @@ module Twitter
166
169
  # Verifies the credentials for the auth user.
167
170
  # raises Twitter::CantConnect on failure.
168
171
  def verify_credentials
169
- request('account/verify_credentials', :auth => true)
172
+ request('account/verify_credentials.xml', :auth => true)
170
173
  end
171
174
 
172
175
  private
@@ -191,22 +194,11 @@ module Twitter
191
194
  request(build_path("statuses/#{method.to_s}.xml", args), options)
192
195
  end
193
196
 
194
- # Makes a request to twitter.
195
- def request(path, options={})
196
- options = {
197
- :headers => { "User-Agent" => @config[:email] },
198
- :method => :get,
199
- }.merge(options)
200
-
201
- unless options[:since].nil?
202
- since = options[:since].kind_of?(Date) ? options[:since].strftime('%a, %d-%b-%y %T GMT') : options[:since].to_s
203
- options[:headers]["If-Modified-Since"] = since
204
- end
205
-
197
+ def response(path, options={})
206
198
  uri = URI.parse("http://#{@api_host}")
207
199
 
208
200
  begin
209
- response = Net::HTTP::Proxy(options[:proxy_host], options[:proxy_port]).start(uri.host, uri.port) do |http|
201
+ response = Net::HTTP::Proxy(@proxy_host, @proxy_port).start(uri.host, uri.port) do |http|
210
202
  klass = Net::HTTP.const_get options[:method].to_s.downcase.capitalize
211
203
  req = klass.new("#{uri.path}/#{path}", options[:headers])
212
204
  req.basic_auth(@config[:email], @config[:password]) if options[:auth]
@@ -218,7 +210,24 @@ module Twitter
218
210
  rescue => error
219
211
  raise CantConnect, error.message
220
212
  end
213
+ end
214
+
215
+ # Makes a request to twitter.
216
+ def request(path, options={})
217
+ options = {
218
+ :headers => { "User-Agent" => @config[:email] },
219
+ :method => :get,
220
+ }.merge(options)
221
+
222
+ unless options[:since].nil?
223
+ since = options[:since].kind_of?(Date) ? options[:since].strftime('%a, %d-%b-%y %T GMT') : options[:since].to_s
224
+ options[:headers]["If-Modified-Since"] = since
225
+ end
221
226
 
227
+ handle_response!(response(path, options))
228
+ end
229
+
230
+ def handle_response!(response)
222
231
  if %w[200 304].include?(response.code)
223
232
  response = parse(response.body)
224
233
  raise RateExceeded if (response/:hash/:error).text =~ /Rate limit exceeded/
@@ -227,10 +236,15 @@ module Twitter
227
236
  raise Unavailable, response.message
228
237
  elsif response.code == '401'
229
238
  raise CantConnect, 'Authentication failed. Check your username and password'
239
+ elsif response.code == '403'
240
+ error_message = (parse(response.body)/:hash/:error).text
241
+ raise CantFindUsers, error_message if error_message =~ /Could not find both specified users/
242
+ raise AlreadyFollowing, error_message if error_message =~ /already on your list/
243
+ raise CantFollowUser, "Response code #{response.code}: #{response.message} #{error_message}"
230
244
  else
231
245
  raise CantConnect, "Twitter is returning a #{response.code}: #{response.message}"
232
246
  end
233
- end
247
+ end
234
248
 
235
249
  # Given a path and a hash, build a full path with the hash turned into a query string
236
250
  def build_path(path, options)
@@ -253,4 +267,4 @@ module Twitter
253
267
  Hpricot.XML(response || '')
254
268
  end
255
269
  end
256
- end
270
+ end
@@ -8,13 +8,15 @@ module Twitter
8
8
  options = {
9
9
  :cache => false,
10
10
  :since_prefix => '',
11
- :empty_msg => 'Nothing new since your last check.'
11
+ :empty_msg => 'Nothing new since your last check.',
12
+ :reverse => false
12
13
  }.merge(options)
13
14
 
14
15
  if collection.size > 0
15
16
  justify = collection.collect { |s| s.user.screen_name }.max { |a,b| a.length <=> b.length }.length rescue 0
16
17
  indention = ' ' * (justify + 3)
17
18
  say("\n#{indention}#{collection.size} new tweet(s) found.\n\n")
19
+ collection.reverse! if options[:reverse]
18
20
  collection.each do |s|
19
21
  Tweet.create_from_tweet(current_account, s) if options[:cache]
20
22
 
@@ -33,7 +35,7 @@ module Twitter
33
35
  say "#{CGI::unescapeHTML(formatted_name)}: #{CGI::unescapeHTML(formatted_msg)}\n#{indention}#{formatted_time}\n\n"
34
36
  end
35
37
 
36
- Configuration["#{options[:since_prefix]}_since_id"] = collection.first.id
38
+ Configuration["#{options[:since_prefix]}_since_id"] = options[:reverse] ? collection.last.id : collection.first.id
37
39
  else
38
40
  say(options[:empty_msg])
39
41
  end
data/lib/twitter/cli.rb CHANGED
@@ -179,7 +179,7 @@ Main {
179
179
  end
180
180
  post_thread.join
181
181
  progress_thread.join
182
- say "Got it! New twitter created at: #{status.created_at}\n"
182
+ say "Got it! New tweet created at: #{status.created_at}\n"
183
183
  end
184
184
  end
185
185
  end
@@ -217,7 +217,7 @@ Main {
217
217
  end
218
218
 
219
219
  mode 'follow' do
220
- description "Allows you to turn on notifications for a user"
220
+ description "Allows you to add notifications for a user (aka Follow Them)"
221
221
  argument('username') {
222
222
  required
223
223
  description 'username or id of twitterrer to follow'
@@ -277,21 +277,25 @@ Main {
277
277
  option('force', 'f') {
278
278
  description "Ignore since_id and show first page of results even if there aren't new ones"
279
279
  }
280
+ option('reverse', 'r') {
281
+ description 'Reverse the output so the oldest tweets are at the top'
282
+ }
280
283
 
281
284
  def run
282
285
  do_work do
283
286
  timeline = params['timeline'].value == 'me' ? 'user' : params['timeline'].value
284
287
  options, since_id = {}, Configuration["#{timeline}_since_id"]
285
288
  options[:since_id] = since_id if !since_id.nil? && !params['force'].given?
289
+ reverse = params['reverse'].given? ? true : false
286
290
  cache = [:friends, :user].include?(timeline)
287
291
  collection = base.timeline(timeline.to_sym, options)
288
- output_tweets(collection, {:cache => cache, :since_prefix => timeline})
292
+ output_tweets(collection, {:cache => cache, :since_prefix => timeline, :reverse => reverse})
289
293
  end
290
294
  end
291
295
  end
292
296
 
293
297
  mode 'replies' do
294
- description 'Allows you to view all @replies at you'
298
+ description 'Allows you to view all @replies sent to you'
295
299
  option('force', 'f') {
296
300
  description "Ignore since_id and show first page of replies even if there aren't new ones"
297
301
  }
@@ -59,6 +59,12 @@ module Twitter
59
59
  self
60
60
  end
61
61
 
62
+ # Which page of results to fetch
63
+ def page(num)
64
+ @query[:page] = num
65
+ self
66
+ end
67
+
62
68
  # Only searches tweets since a given id.
63
69
  # Recommended to use this when possible.
64
70
  def since(since_id)
@@ -83,7 +89,7 @@ module Twitter
83
89
  # If you want to get results do something other than iterate over them.
84
90
  def fetch
85
91
  @query[:q] = @query[:q].join(' ')
86
- self.class.get('/search.json', {:query => @query})
92
+ SearchResultInfo.new_from_hash(self.class.get('/search.json', {:query => @query}))
87
93
  end
88
94
 
89
95
  def each
@@ -91,4 +97,5 @@ module Twitter
91
97
  @result['results'].each { |r| yield r }
92
98
  end
93
99
  end
94
- end
100
+ end
101
+
@@ -1,3 +1,3 @@
1
1
  module Twitter #:nodoc:
2
- Version = '0.4.0'
2
+ Version = '0.4.1'
3
3
  end
data/lib/twitter.rb CHANGED
@@ -15,6 +15,8 @@ require 'twitter/search'
15
15
  require 'twitter/status'
16
16
  require 'twitter/direct_message'
17
17
  require 'twitter/rate_limit_status'
18
+ require 'twitter/search_result_info'
19
+ require 'twitter/search_result'
18
20
 
19
21
  module Twitter
20
22
  class Unavailable < StandardError; end
@@ -22,6 +24,9 @@ module Twitter
22
24
  class BadResponse < StandardError; end
23
25
  class UnknownTimeline < ArgumentError; end
24
26
  class RateExceeded < StandardError; end
27
+ class CantFindUsers < ArgumentError; end
28
+ class AlreadyFollowing < StandardError; end
29
+ class CantFollowUser < StandardError; end
25
30
 
26
31
  SourceName = 'twittergem'
27
32
  end
data/spec/base_spec.rb CHANGED
@@ -16,7 +16,11 @@ describe "Twitter::Base" do
16
16
  lambda { @base.timeline(:fakeyoutey) }.should raise_error(Twitter::UnknownTimeline)
17
17
  end
18
18
 
19
- it "should default to friends timeline"
19
+ it "should default to friends timeline" do
20
+ @base.should_receive(:call).with("friends_timeline", {:auth=>true, :args=>{}, :since=>nil})
21
+ @base.should_receive(:statuses)
22
+ @base.timeline
23
+ end
20
24
 
21
25
  it "should be able to retrieve friends timeline" do
22
26
  data = open(File.dirname(__FILE__) + '/fixtures/friends_timeline.xml').read
@@ -65,6 +69,20 @@ describe "Twitter::Base" do
65
69
  timeline.size.should == 29
66
70
  timeline.first.name.should == 'Blaine Cook'
67
71
  end
72
+
73
+ it "should be able to create a friendship" do
74
+ data = open(File.dirname(__FILE__) + '/fixtures/friendship_created.xml').read
75
+ @base.should_receive(:request).and_return(Hpricot::XML(data))
76
+ user = @base.create_friendship('jnunemaker')
77
+ end
78
+
79
+ it "should bomb if friendship already exists" do
80
+ data = open(File.dirname(__FILE__) + '/fixtures/friendship_already_exists.xml').read
81
+ response = Net::HTTPForbidden.new("1.1", '403', '')
82
+ response.stub!(:body).and_return(data)
83
+ @base.should_receive(:response).and_return(response)
84
+ lambda { @base.create_friendship('billymeltdown') }.should raise_error(Twitter::AlreadyFollowing)
85
+ end
68
86
  end
69
87
 
70
88
  it "should be able to get single status" do
@@ -106,4 +124,4 @@ describe "Twitter::Base" do
106
124
  @base.rate_limit_status.remaining_hits.should == 5
107
125
  end
108
126
  end
109
- end
127
+ end
@@ -35,7 +35,15 @@ describe Twitter::CLI::Helpers do
35
35
  end
36
36
 
37
37
  specify "should properly format" do
38
- output_tweets(@collection)
38
+ stdout_for {
39
+ output_tweets(@collection)
40
+ }.should match(/with a few words[\w\W]*with a\./)
41
+ end
42
+
43
+ specify 'should format in reverse' do
44
+ stdout_for {
45
+ output_tweets(@collection, :reverse => true)
46
+ }.should match(/with a\.[\w\W]*with a few words/)
39
47
  end
40
48
  end
41
49
  end
data/spec/search_spec.rb CHANGED
@@ -49,6 +49,10 @@ describe Twitter::Search do
49
49
  @search.per_page(25).query[:rpp].should == 25
50
50
  end
51
51
 
52
+ it "should be able to specify the page number" do
53
+ @search.page(20).query[:page].should == 20
54
+ end
55
+
52
56
  it "should be able to specify only returning results greater than an id" do
53
57
  @search.since(1234).query[:since_id].should == 1234
54
58
  end
@@ -73,7 +77,7 @@ describe Twitter::Search do
73
77
 
74
78
  describe "fetching" do
75
79
  before do
76
- @response = open(File.dirname(__FILE__) + '/fixtures/friends_timeline.xml').read
80
+ @response = YAML.load_file(File.dirname(__FILE__) + '/fixtures/search_result_info.yml')
77
81
  @search.class.stub!(:get).and_return(@response)
78
82
  end
79
83
 
@@ -81,6 +85,13 @@ describe Twitter::Search do
81
85
  @search.class.should_receive(:get).and_return(@response)
82
86
  @search.from('jnunemaker').fetch().should == @response
83
87
  end
88
+
89
+ it "should support dot notation" do
90
+ @search.class.should_receive(:get).and_return(@response)
91
+ info = @search.from('httparty').fetch()
92
+ info["max_id"].should == info.max_id
93
+ info["results"].first["text"].should == info.results.first.text
94
+ end
84
95
  end
85
96
 
86
97
  it "should be able to iterate over results" do
data/spec/spec_helper.rb CHANGED
@@ -9,4 +9,15 @@ end
9
9
  dir = File.dirname(__FILE__)
10
10
 
11
11
  $:.unshift(File.join(dir, '/../lib/'))
12
- require dir + '/../lib/twitter'
12
+ require dir + '/../lib/twitter'
13
+
14
+
15
+ def stdout_for(&block)
16
+ # Inspired by http://www.ruby-forum.com/topic/58647
17
+ old_stdout = $stdout
18
+ $stdout = StringIO.new
19
+ yield
20
+ output = $stdout.string
21
+ $stdout = old_stdout
22
+ output
23
+ end
data/twitter.gemspec CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{twitter}
5
- s.version = "0.4.0"
5
+ s.version = "0.4.1"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["John Nunemaker"]
9
- s.date = %q{2008-12-23}
9
+ s.date = %q{2009-01-05}
10
10
  s.default_executable = %q{twitter}
11
11
  s.description = %q{a command line interface for twitter, also a library which wraps the twitter api}
12
12
  s.email = %q{nunemaker@gmail.com}
13
13
  s.executables = ["twitter"]
14
14
  s.extra_rdoc_files = ["bin/twitter", "lib/twitter/base.rb", "lib/twitter/cli/config.rb", "lib/twitter/cli/helpers.rb", "lib/twitter/cli/migrations/20080722194500_create_accounts.rb", "lib/twitter/cli/migrations/20080722194508_create_tweets.rb", "lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb", "lib/twitter/cli/migrations/20080722214606_create_configurations.rb", "lib/twitter/cli/models/account.rb", "lib/twitter/cli/models/configuration.rb", "lib/twitter/cli/models/tweet.rb", "lib/twitter/cli.rb", "lib/twitter/direct_message.rb", "lib/twitter/easy_class_maker.rb", "lib/twitter/rate_limit_status.rb", "lib/twitter/search.rb", "lib/twitter/status.rb", "lib/twitter/user.rb", "lib/twitter/version.rb", "lib/twitter.rb", "README"]
15
- s.files = ["bin/twitter", "examples/blocks.rb", "examples/direct_messages.rb", "examples/favorites.rb", "examples/friends_followers.rb", "examples/friendships.rb", "examples/identica_timeline.rb", "examples/location.rb", "examples/posting.rb", "examples/replies.rb", "examples/search.rb", "examples/sent_messages.rb", "examples/timeline.rb", "examples/twitter.rb", "examples/verify_credentials.rb", "History", "lib/twitter/base.rb", "lib/twitter/cli/config.rb", "lib/twitter/cli/helpers.rb", "lib/twitter/cli/migrations/20080722194500_create_accounts.rb", "lib/twitter/cli/migrations/20080722194508_create_tweets.rb", "lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb", "lib/twitter/cli/migrations/20080722214606_create_configurations.rb", "lib/twitter/cli/models/account.rb", "lib/twitter/cli/models/configuration.rb", "lib/twitter/cli/models/tweet.rb", "lib/twitter/cli.rb", "lib/twitter/direct_message.rb", "lib/twitter/easy_class_maker.rb", "lib/twitter/rate_limit_status.rb", "lib/twitter/search.rb", "lib/twitter/status.rb", "lib/twitter/user.rb", "lib/twitter/version.rb", "lib/twitter.rb", "License", "Manifest", "Rakefile", "README", "spec/base_spec.rb", "spec/cli/helper_spec.rb", "spec/direct_message_spec.rb", "spec/fixtures/followers.xml", "spec/fixtures/friends.xml", "spec/fixtures/friends_for.xml", "spec/fixtures/friends_lite.xml", "spec/fixtures/friends_timeline.xml", "spec/fixtures/public_timeline.xml", "spec/fixtures/rate_limit_status.xml", "spec/fixtures/search_results.json", "spec/fixtures/status.xml", "spec/fixtures/user.xml", "spec/fixtures/user_timeline.xml", "spec/search_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/status_spec.rb", "spec/user_spec.rb", "website/css/common.css", "website/images/terminal_output.png", "website/index.html", "twitter.gemspec"]
15
+ s.files = ["bin/twitter", "examples/blocks.rb", "examples/direct_messages.rb", "examples/favorites.rb", "examples/friends_followers.rb", "examples/friendships.rb", "examples/identica_timeline.rb", "examples/location.rb", "examples/posting.rb", "examples/replies.rb", "examples/search.rb", "examples/sent_messages.rb", "examples/timeline.rb", "examples/twitter.rb", "examples/verify_credentials.rb", "History", "lib/twitter/base.rb", "lib/twitter/cli/config.rb", "lib/twitter/cli/helpers.rb", "lib/twitter/cli/migrations/20080722194500_create_accounts.rb", "lib/twitter/cli/migrations/20080722194508_create_tweets.rb", "lib/twitter/cli/migrations/20080722214605_add_account_id_to_tweets.rb", "lib/twitter/cli/migrations/20080722214606_create_configurations.rb", "lib/twitter/cli/models/account.rb", "lib/twitter/cli/models/configuration.rb", "lib/twitter/cli/models/tweet.rb", "lib/twitter/cli.rb", "lib/twitter/direct_message.rb", "lib/twitter/easy_class_maker.rb", "lib/twitter/rate_limit_status.rb", "lib/twitter/search.rb", "lib/twitter/status.rb", "lib/twitter/user.rb", "lib/twitter/version.rb", "lib/twitter.rb", "License", "Manifest", "Rakefile", "README", "spec/base_spec.rb", "spec/cli/helper_spec.rb", "spec/direct_message_spec.rb", "spec/fixtures/followers.xml", "spec/fixtures/friends.xml", "spec/fixtures/friends_for.xml", "spec/fixtures/friends_lite.xml", "spec/fixtures/friends_timeline.xml", "spec/fixtures/public_timeline.xml", "spec/fixtures/rate_limit_status.xml", "spec/fixtures/search_results.json", "spec/fixtures/status.xml", "spec/fixtures/user.xml", "spec/fixtures/user_timeline.xml", "spec/search_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "spec/status_spec.rb", "spec/user_spec.rb", "twitter.gemspec", "website/css/common.css", "website/images/terminal_output.png", "website/index.html"]
16
16
  s.has_rdoc = true
17
17
  s.homepage = %q{http://twitter.rubyforge.org}
18
18
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Twitter", "--main", "README"]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jnunemaker-twitter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Nunemaker
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-12-23 00:00:00 -08:00
12
+ date: 2009-01-05 00:00:00 -08:00
13
13
  default_executable: twitter
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -135,10 +135,10 @@ files:
135
135
  - spec/spec_helper.rb
136
136
  - spec/status_spec.rb
137
137
  - spec/user_spec.rb
138
+ - twitter.gemspec
138
139
  - website/css/common.css
139
140
  - website/images/terminal_output.png
140
141
  - website/index.html
141
- - twitter.gemspec
142
142
  has_rdoc: true
143
143
  homepage: http://twitter.rubyforge.org
144
144
  post_install_message: