look-twitter-search 0.5.4 → 0.5.6

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/CHANGELOG.textile ADDED
@@ -0,0 +1,16 @@
1
+ h2. 0.5.5 (May 18, 2009)
2
+
3
+ * raise error when query contains :near or :within:. (Dan Croak)
4
+
5
+ h2. 0.5.4 (May 16, 2009)
6
+
7
+ * Fixed bug in Twitter trends. (Dan Croak)
8
+ * Refactored test suite. Now organized by function, uses redgreen, began moving toward using JSON instead of YAML for fixtures. (Dan Croak)
9
+ * Exposed Shoulda Macros to apps that want to use them. (Dan Croak)
10
+
11
+ h2. 0.5.3 (May 15, 2009)
12
+
13
+ * Added Twitter trends. (Matt Sanford)
14
+ * Added overdue attribution for Luke Francl, Matt Sanford, Alejandro Crosa, Danny Burkes, Don Brown, & HotFusionMan.
15
+ * Added CHANGELOG. (Dan Croak)
16
+
data/README.markdown CHANGED
@@ -14,8 +14,10 @@ Require the gem.
14
14
 
15
15
  Set up a TwitterSearch::Client. Name your client (a.k.a. 'user agent') to something meaningful, such as your app's name. This helps Twitter Search answer any questions about your use of the API.
16
16
 
17
- @client = TwitterSearch::Client.new 'politweets'
18
-
17
+ @client = TwitterSearch::Client.new 'politweets'
18
+
19
+ ### Search
20
+
19
21
  Request tweets by calling the query method of your client. It takes either a String or a Hash of arguments.
20
22
 
21
23
  @tweets = @client.query 'twitter search'
@@ -26,6 +28,16 @@ The String form uses the default Twitter Search behavior, which in this example
26
28
 
27
29
  Use the Twitter Search API's query operators with the :q key to access a variety of behavior.
28
30
 
31
+ ### Trends
32
+
33
+ Request the current trending topics by calling the trends method of your client. It takes an optional Hash of arguments.
34
+
35
+ @trends = @client.trends
36
+
37
+ The only supported option currently is exclude_hashtags to return trends that are not hashtags only.
38
+
39
+ @trends = @client.trends :exclude_hashtags => true
40
+
29
41
  ## Search Operators
30
42
 
31
43
  The following operator examples find tweets...
@@ -66,14 +78,14 @@ Alter the number of Tweets returned per page with the :rpp key. Stick with 10, 1
66
78
 
67
79
  * Searching for a positive attitude :) returns tweets containing the text :), =), :D, and :-)
68
80
 
69
- ## Authors
81
+ ## Contributors
70
82
 
71
- Written by Dustin Sallings (dustin@spy.net), forked by Dan Croak (dcroak@thoughtbot.com).
83
+ Dustin Sallings, Dan Croak, Luke Francl, Matt Sanford, Alejandro Crosa, Danny Burkes, Don Brown, & HotFusionMan.
72
84
 
73
85
  ## Resources
74
86
 
75
- * [Official Twitter Search API](http://search.twitter.com/api)
87
+ * [Official Twitter Search API](http://apiwiki.twitter.com/Twitter-API-Documentation)
76
88
 
77
89
  ## License
78
90
 
79
- MIT License, same terms as Ruby.
91
+ MIT License, same terms as Ruby.
data/Rakefile CHANGED
@@ -1,25 +1,25 @@
1
1
  require 'rake'
2
2
  require 'rake/testtask'
3
-
4
- test_files_pattern = 'test/twitter_search_test.rb'
3
+
4
+ test_files_pattern = 'test/*_test.rb'
5
5
  Rake::TestTask.new do |t|
6
6
  t.libs << 'lib'
7
7
  t.pattern = test_files_pattern
8
8
  t.verbose = false
9
9
  end
10
-
10
+
11
11
  desc "Run the test suite"
12
12
  task :default => :test
13
-
13
+
14
14
  gem_spec = Gem::Specification.new do |gem_spec|
15
15
  gem_spec.name = "twitter-search"
16
- gem_spec.version = "0.5.2"
17
- gem_spec.summary = "Ruby client for Twitter Search."
16
+ gem_spec.version = "0.5.6"
17
+ gem_spec.summary = "Ruby client for Twitter Search. Includes trends."
18
18
  gem_spec.email = "dcroak@thoughtbot.com"
19
19
  gem_spec.homepage = "http://github.com/dancroak/twitter-search"
20
20
  gem_spec.description = "Ruby client for Twitter Search."
21
- gem_spec.authors = ["Dustin Sallings", "Dan Croak"]
22
- gem_spec.files = FileList["[A-Z]*", "{generators,lib,shoulda_macros,rails}/**/*"]
21
+ gem_spec.authors = ["Dustin Sallings", "Dan Croak", "Luke Francl", "Matt Sanford", "Alejandro Crosa", "Danny Burkes", "Don Brown", "HotFusionMan"]
22
+ gem_spec.files = FileList["[A-Z]*", "{lib,shoulda_macros}/**/*"]
23
23
  gem_spec.add_dependency('json', '>= 1.1.2')
24
24
  end
25
25
 
@@ -36,7 +36,7 @@ require 'yaml'
36
36
 
37
37
  namespace :yaml do
38
38
  desc "Write Twitter Search results to yaml file so API is not hit every test."
39
- task :write do
39
+ task :write do
40
40
  write_yaml :tweets => 'Obama', :file => 'obama'
41
41
  write_yaml :tweets => 'twitter search', :file => 'twitter_search'
42
42
  write_yaml :tweets => {:q => 'twitter search'}, :file => 'twitter_search_and'
@@ -64,7 +64,7 @@ end
64
64
  def write_yaml(opts = {})
65
65
  @client = TwitterSearch::Client.new 'twitter-search'
66
66
  tweets = @client.query(opts[:tweets])
67
- File.open(File.join(File.dirname(__FILE__), 'test', 'yaml', "#{opts[:file]}.yaml"), 'w+') do |file|
67
+ File.open(File.join(File.dirname(__FILE__), 'test', 'yaml', "#{opts[:file]}.yaml"), 'w+') do |file|
68
68
  file.puts tweets.to_yaml
69
69
  end
70
- end
70
+ end
data/lib/trends.rb ADDED
@@ -0,0 +1,36 @@
1
+ module TwitterSearch
2
+ class Trend
3
+ VARS = [ :query, :name ]
4
+ attr_reader *VARS
5
+ attr_reader :exclude_hashtags
6
+
7
+ def initialize(opts)
8
+ @exclude_hashtags = !!opts['exclude_hashtags']
9
+ VARS.each { |each| instance_variable_set "@#{each}", opts[each.to_s] }
10
+ end
11
+ end
12
+
13
+ class Trends
14
+ VARS = [:date]
15
+ attr_reader *VARS
16
+
17
+ include Enumerable
18
+
19
+ def initialize(opts)
20
+ @trends = opts['trends'].values.first.collect { |each| Trend.new(each) }
21
+ VARS.each { |each| instance_variable_set "@#{each}", opts[each.to_s] }
22
+ end
23
+
24
+ def each(&block)
25
+ @trends.each(&block)
26
+ end
27
+
28
+ def size
29
+ @trends.size
30
+ end
31
+
32
+ def [](index)
33
+ @trends[index]
34
+ end
35
+ end
36
+ end
data/lib/tweets.rb ADDED
@@ -0,0 +1,45 @@
1
+ module TwitterSearch
2
+ class Tweet
3
+ VARS = [:text, :from_user, :to_user, :to_user_id, :id, :iso_language_code, :from_user_id, :created_at, :profile_image_url, :source ]
4
+ attr_reader *VARS
5
+ attr_reader :language
6
+
7
+ def initialize(opts)
8
+ @language = opts['iso_language_code']
9
+ VARS.each { |each| instance_variable_set "@#{each}", opts[each.to_s] }
10
+ end
11
+ end
12
+
13
+ class Tweets
14
+ VARS = [:since_id, :max_id, :results_per_page, :page, :query, :next_page]
15
+ attr_reader *VARS
16
+
17
+ include Enumerable
18
+
19
+ def initialize(opts)
20
+ @results = opts['results'].collect { |each| Tweet.new(each) }
21
+ VARS.each { |each| instance_variable_set "@#{each}", opts[each.to_s] }
22
+ end
23
+
24
+ def each(&block)
25
+ @results.each(&block)
26
+ end
27
+
28
+ def size
29
+ @results.size
30
+ end
31
+
32
+ def [](index)
33
+ @results[index]
34
+ end
35
+
36
+ def has_next_page?
37
+ ! @next_page.nil?
38
+ end
39
+
40
+ def get_next_page
41
+ client = Client.new
42
+ return client.query( CGI.parse( @next_page[1..-1] ) )
43
+ end
44
+ end
45
+ end
@@ -3,99 +3,96 @@ require 'net/http'
3
3
  require 'json'
4
4
  require 'cgi'
5
5
 
6
- module TwitterSearch
6
+ require File.join(File.dirname(__FILE__), 'tweets')
7
+ require File.join(File.dirname(__FILE__), 'trends')
7
8
 
8
- class Tweet
9
- VARS = [:text, :from_user, :to_user, :to_user_id, :id, :iso_language_code, :from_user_id, :created_at, :profile_image_url, :source ]
10
- attr_reader *VARS
11
- attr_reader :language
12
-
13
- def initialize(opts)
14
- @language = opts['iso_language_code']
15
- VARS.each { |each| instance_variable_set "@#{each}", opts[each.to_s] }
16
- end
9
+ module TwitterSearch
10
+ class SearchOperatorError < ArgumentError
17
11
  end
18
-
19
- class Tweets
20
- VARS = [:since_id, :max_id, :results_per_page, :page, :query, :next_page]
21
- attr_reader *VARS
22
-
23
- include Enumerable
24
-
25
- def initialize(opts)
26
- @results = opts['results'].collect { |each| Tweet.new(each) }
27
- VARS.each { |each| instance_variable_set "@#{each}", opts[each.to_s] }
28
- end
29
-
30
- def each(&block)
31
- @results.each(&block)
32
- end
33
-
34
- def size
35
- @results.size
36
- end
37
-
38
- def [](index)
39
- @results[index]
40
- end
41
-
42
- def has_next_page?
43
- ! @next_page.nil?
44
- end
45
-
46
- def get_next_page
47
- client = Client.new
48
- return client.query( CGI.parse( @next_page[1..-1] ) )
49
- end
12
+ class SearchServerError < RuntimeError
50
13
  end
51
14
 
52
15
  class Client
53
- TWITTER_API_URL = 'http://search.twitter.com/search.json'
54
- TWITTER_API_DEFAULT_TIMEOUT = 5
55
-
16
+ TWITTER_SEARCH_API_URL = 'http://search.twitter.com/search.json'
17
+ TWITTER_TRENDS_API_URL = 'http://search.twitter.com/trends/current.json'
18
+ DEFAULT_TIMEOUT = 5
19
+
56
20
  attr_accessor :agent
57
21
  attr_accessor :timeout
58
-
59
- def initialize(agent = 'twitter-search', timeout = TWITTER_API_DEFAULT_TIMEOUT)
22
+
23
+ def initialize(agent = 'twitter-search', timeout = DEFAULT_TIMEOUT)
60
24
  @agent = agent
61
25
  @timeout = timeout
62
26
  end
63
-
27
+
64
28
  def headers
65
29
  { "Content-Type" => 'application/json',
66
30
  "User-Agent" => @agent }
67
31
  end
68
-
32
+
69
33
  def query(opts = {})
70
- url = URI.parse(TWITTER_API_URL)
34
+ url = URI.parse(TWITTER_SEARCH_API_URL)
71
35
  url.query = sanitize_query(opts)
72
36
 
37
+ ensure_no_location_operators(url.query)
38
+
73
39
  req = Net::HTTP::Get.new(url.path)
74
40
  http = Net::HTTP.new(url.host, url.port)
75
41
  http.read_timeout = timeout
42
+
43
+ res = http.start { |http|
44
+ http.get("#{url.path}?#{url.query}", headers)
45
+ }
46
+
47
+ if res.code == '404'
48
+ raise TwitterSearch::SearchServerError, "Twitter responded with a 404 for your query. It is likely too complicated to process."
49
+ end
50
+
51
+ json = res.body
76
52
 
53
+ Tweets.new JSON.parse(json)
54
+ end
55
+
56
+ def trends(opts = {})
57
+ url = URI.parse(TWITTER_TRENDS_API_URL)
58
+ if opts['exclude_hashtags']
59
+ url.query = sanitize_query_hash({ :exclude_hashtags => opts['exclude_hashtags'] })
60
+ end
61
+
62
+ req = Net::HTTP::Get.new(url.path)
63
+ http = Net::HTTP.new(url.host, url.port)
64
+ http.read_timeout = timeout
65
+
77
66
  json = http.start { |http|
78
67
  http.get("#{url.path}?#{url.query}", headers)
79
68
  }.body
80
- Tweets.new JSON.parse(json)
69
+
70
+ Trends.new JSON.parse(json)
81
71
  end
82
72
 
83
73
  private
84
74
 
85
75
  def sanitize_query(opts)
86
76
  if opts.is_a? String
87
- "q=#{CGI.escape(opts)}"
77
+ "q=#{CGI.escape(opts)}"
88
78
  elsif opts.is_a? Hash
89
79
  "#{sanitize_query_hash(opts)}"
90
80
  end
91
81
  end
92
82
 
93
83
  def sanitize_query_hash(query_hash)
94
- query_hash.collect { |key, value|
95
- "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
84
+ query_hash.collect { |key, value|
85
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
96
86
  }.join('&')
97
87
  end
98
-
99
- end
100
88
 
89
+ def ensure_no_location_operators(query_string)
90
+ if query_string.include?("near%3A") ||
91
+ query_string.include?("within%3A")
92
+ raise TwitterSearch::SearchOperatorError,
93
+ "near: and within: are available from the Twitter Search web interface, but not the API. The API requires the geocode parameter. See dancroak/twitter-search README."
94
+ end
95
+ end
96
+
97
+ end
101
98
  end
@@ -0,0 +1,36 @@
1
+ module TwitterSearch
2
+ module Shoulda
3
+ def should_have_default_search_behaviors
4
+ should_find_tweets
5
+ should_have_text_for_all_tweets
6
+ should_return_page 1
7
+ should_return_tweets_in_sets_of 15
8
+ end
9
+
10
+ def should_find_tweets
11
+ should 'find tweets' do
12
+ assert @tweets.any?
13
+ end
14
+ end
15
+
16
+ def should_have_text_for_all_tweets
17
+ should 'have text for all tweets' do
18
+ assert @tweets.all? { |tweet| tweet.text.size > 0 }
19
+ end
20
+ end
21
+
22
+ def should_return_page(number)
23
+ should "return page #{number}" do
24
+ assert_equal number, @tweets.page
25
+ end
26
+ end
27
+
28
+ def should_return_tweets_in_sets_of(number)
29
+ should "return tweets in sets of #{number}" do
30
+ assert_equal number, @tweets.results_per_page
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ Test::Unit::TestCase.extend(TwitterSearch::Shoulda)
metadata CHANGED
@@ -1,16 +1,22 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: look-twitter-search
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dustin Sallings
8
8
  - Dan Croak
9
+ - Luke Francl
10
+ - Matt Sanford
11
+ - Alejandro Crosa
12
+ - Danny Burkes
13
+ - Don Brown
14
+ - HotFusionMan
9
15
  autorequire:
10
16
  bindir: bin
11
17
  cert_chain: []
12
18
 
13
- date: 2009-02-03 21:00:00 -08:00
19
+ date: 2009-05-17 21:00:00 -07:00
14
20
  default_executable:
15
21
  dependencies:
16
22
  - !ruby/object:Gem::Dependency
@@ -32,11 +38,15 @@ extensions: []
32
38
  extra_rdoc_files: []
33
39
 
34
40
  files:
41
+ - CHANGELOG.textile
35
42
  - Rakefile
36
43
  - README.markdown
37
44
  - TODO.markdown
45
+ - lib/trends.rb
46
+ - lib/tweets.rb
38
47
  - lib/twitter_search.rb
39
- has_rdoc: false
48
+ - shoulda_macros/twitter_search.rb
49
+ has_rdoc: true
40
50
  homepage: http://github.com/dancroak/twitter-search
41
51
  post_install_message:
42
52
  rdoc_options: []
@@ -60,7 +70,7 @@ requirements: []
60
70
  rubyforge_project:
61
71
  rubygems_version: 1.2.0
62
72
  signing_key:
63
- specification_version: 2
64
- summary: Ruby client for Twitter Search.
73
+ specification_version: 3
74
+ summary: Ruby client for Twitter Search. Includes trends.
65
75
  test_files: []
66
76