jnunemaker-twitter 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
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: