bbrowning-twitter-search 0.5.8.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.textile ADDED
@@ -0,0 +1,32 @@
1
+ h2. 0.5.8 (June 30, 2009)
2
+
3
+ * subclass Tweets and Trends from Array. (Dan Croak)
4
+ * change test suite to use cURL'd JSON fixtures & Fakeweb for just HTTP calls
5
+ for more surgical tests. (Dan Croak)
6
+
7
+ h2. 0.5.7 (June 30, 2009)
8
+
9
+ * improved Fakeweb usage. (Matt Jankowski)
10
+ * account for possible 403 response, plus user-friendly message. (Matt Jankowski)
11
+
12
+ h2. 0.5.6 (June 6, 2009)
13
+
14
+ * When Twitter returns a 404, raise a TwitterSearch::SearchServerError instead
15
+ of a JSON parse error. (Luke Francl)
16
+
17
+ h2. 0.5.5 (May 18, 2009)
18
+
19
+ * raise error when query contains :near or :within:. (Dan Croak)
20
+
21
+ h2. 0.5.4 (May 16, 2009)
22
+
23
+ * Fixed bug in Twitter trends. (Dan Croak)
24
+ * Refactored test suite. Now organized by function, uses redgreen, began moving toward using JSON instead of YAML for fixtures. (Dan Croak)
25
+ * Exposed Shoulda Macros to apps that want to use them. (Dan Croak)
26
+
27
+ h2. 0.5.3 (May 15, 2009)
28
+
29
+ * Added Twitter trends. (Matt Sanford)
30
+ * Added overdue attribution for Luke Francl, Matt Sanford, Alejandro Crosa, Danny Burkes, Don Brown, & HotFusionMan.
31
+ * Added CHANGELOG. (Dan Croak)
32
+
data/README.markdown ADDED
@@ -0,0 +1,112 @@
1
+ # A Twitter Search client for Ruby.
2
+
3
+ Access the Twitter Search API from your Ruby code.
4
+
5
+ ## Usage
6
+
7
+ Install the gem.
8
+
9
+ sudo gem install dancroak-twitter-search -s http://gems.github.com
10
+
11
+ Require the gem.
12
+
13
+ require 'twitter_search'
14
+
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
+
17
+ client = TwitterSearch::Client.new('thunderthimble')
18
+
19
+ ### Search
20
+
21
+ Request tweets by calling the query method of your client. It takes either a String or a Hash of arguments.
22
+
23
+ tweets = client.query('twitter search')
24
+
25
+ The String form uses the default Twitter Search behavior, which in this example finds tweets containing both "twitter" and "search". It is identical to the more verbose, explicit version:
26
+
27
+ tweets = client.query(:q => 'twitter search')
28
+
29
+ Use the Twitter Search API's query operators with the :q key to access a variety of behavior.
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
+
41
+ ## Search Operators
42
+
43
+ The following operator examples find tweets...
44
+
45
+ * <a href="http://search.twitter.com/search?q=twitter+search">:q => 'twitter search'</a> - containing both "twitter" and "search". This is the default operator.
46
+ * <a href="http://search.twitter.com/search?q=%22happy+hour%22">:q => '<b>"</b>happy hour<b>"</b>'</a> - containing the exact phrase "happy hour".
47
+ * <a href="http://search.twitter.com/search?q=obama+OR+hillary">:q => 'obama <b>OR</b> hillary'</a> - containing either "obama" or "hillary" (or both).
48
+ * <a href="http://search.twitter.com/search?q=beer+-root">:q => 'beer <b>-</b>root'</a> - containing "beer" but not "root".
49
+ * <a href="http://search.twitter.com/search?q=%23haiku">:q => '<b>#</b>haiku</a>' - containing the hashtag "haiku".
50
+ * <a href="http://search.twitter.com/search?q=from%3Aalexiskold">:q => '<b>from:</b>alexiskold'</a> - sent from person "alexiskold".
51
+ * <a href="http://search.twitter.com/search?q=to%3Atechcrunch">:q => '<b>to:</b>techcrunch</a>' - sent to person "techcrunch".
52
+ * <a href="http://search.twitter.com/search?q=%40mashable">:q => '<b>@</b>mashable</a>' - referencing person "mashable".
53
+ * <a href="http://search.twitter.com/search?q=superhero+since%3A2008-05-01">:q => 'superhero <b>since:</b>2008-05-01'</a> - containing "superhero" and sent since date "2008-05-01" (year-month-day).
54
+ * <a href="http://search.twitter.com/search?q=ftw+until%3A2008-05-03">:q => 'ftw <b>until:</b>2008-05-03'</a> - containing "ftw" and sent up to date "2008-05-03".
55
+ * <a href="http://search.twitter.com/search?q=movie+-scary+%3A%29">:q => 'movie -scary <b>:)</b>'</a> - containing "movie", but not "scary", and with a positive attitude.
56
+ * <a href="http://search.twitter.com/search?q=flight+%3A%28">:q => 'flight <b>:(</b>'</a> - containing "flight" and with a negative attitude.
57
+ * <a href="http://search.twitter.com/search?q=traffic+%3F">:q => 'traffic <b>?</b>'</a> - containing "traffic" and asking a question.
58
+ * <a href="http://search.twitter.com/search?q=hilarious+filter%3Alinks">:q => 'hilarious <b>filter:links</b>'</a> - containing "hilarious" and linking to URLs.
59
+
60
+ ### Foreign Languages
61
+
62
+ The Twitter Search API supports foreign languages, accessible via the :lang key. Use the [ISO 639-1](http://en.wikipedia.org/wiki/ISO_639-1) codes as the value:
63
+
64
+ tweets = client.query(:q => 'programmé', :lang => 'fr')
65
+
66
+ ### Pagination
67
+
68
+ Alter the number of Tweets returned per page with the :rpp key. Stick with 10, 15, 20, 25, 30, or 50.
69
+
70
+ tweets = client.query(:q => 'Boston Celtics', :rpp => '30')
71
+
72
+ ## Gotchas
73
+
74
+ * Searches are case-insenstive.
75
+ * The "near" operator available in the Twitter Search web interface is not available via the API. You must geocode before making your Twitter Search API call, and use the :geocode key in your request using the pattern lat,lngmi or lat,lngkm:
76
+
77
+ tweets = client.query(:q => 'Pearl Jam', :geocode => '43.4411,-70.9846mi')
78
+
79
+ * Searching for a positive attitude :) returns tweets containing the text :), =), :D, and :-)
80
+
81
+ ## Contributing
82
+
83
+ Get the source and clone it.
84
+
85
+ The test suite uses JSON fixtures which were created from cURL. Usage example:
86
+
87
+ query = { :q => 'rails training' }
88
+ fake_query(query, 'rails_training.json')
89
+ @tweets = TwitterSearch::Client.new.query(query)
90
+
91
+ Then you assert any necessary expectations on @tweets.
92
+
93
+ To create your own JSON fixtures, first get the CGI escaped querystring in irb:
94
+
95
+ require 'lib/twitter_search'
96
+ TwitterSearch::Client.new.sanitize_query({ :q => 'rails training' })
97
+
98
+ Then take the output and append it to a simple cURL, sending the output into your new file:
99
+
100
+ curl -i 'http://search.twitter.com/search.json?q=rails+training' > test/json/rails_training.json
101
+
102
+ ## Contributors
103
+
104
+ Dustin Sallings, Dan Croak, Luke Francl, Matt Jankowski, Matt Sanford, Alejandro Crosa, Danny Burkes, Don Brown, & HotFusionMan.
105
+
106
+ ## Resources
107
+
108
+ * [Official Twitter Search API](http://apiwiki.twitter.com/Twitter-API-Documentation)
109
+
110
+ ## License
111
+
112
+ MIT License, same terms as Ruby.
data/Rakefile ADDED
@@ -0,0 +1,70 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ test_files_pattern = 'test/*_test.rb'
5
+ Rake::TestTask.new do |t|
6
+ t.libs << 'lib'
7
+ t.pattern = test_files_pattern
8
+ t.verbose = false
9
+ end
10
+
11
+ desc "Run the test suite"
12
+ task :default => :test
13
+
14
+ gem_spec = Gem::Specification.new do |gem_spec|
15
+ gem_spec.name = "twitter-search"
16
+ gem_spec.version = "0.5.8"
17
+ gem_spec.summary = "Ruby client for Twitter Search. Includes trends."
18
+ gem_spec.email = "dcroak@thoughtbot.com"
19
+ gem_spec.homepage = "http://github.com/dancroak/twitter-search"
20
+ gem_spec.description = "Ruby client for Twitter Search."
21
+ gem_spec.authors = ["Dustin Sallings", "Dan Croak", "Luke Francl", "Matt Jankowski", "Matt Sanford", "Alejandro Crosa", "Danny Burkes", "Don Brown", "HotFusionMan"]
22
+ gem_spec.files = FileList["[A-Z]*", "{lib,shoulda_macros}/**/*"]
23
+ gem_spec.add_dependency('json', '>= 1.1.2')
24
+ end
25
+
26
+ desc "Generate a gemspec file"
27
+ task :gemspec do
28
+ File.open("#{gem_spec.name}.gemspec", 'w') do |f|
29
+ f.write gem_spec.to_yaml
30
+ end
31
+ end
32
+
33
+ require File.expand_path('lib/twitter_search', File.dirname(__FILE__))
34
+ require 'rubygems'
35
+ require 'yaml'
36
+
37
+ namespace :yaml do
38
+ desc "Write Twitter Search results to yaml file so API is not hit every test."
39
+ task :write do
40
+ write_yaml :tweets => 'Obama', :file => 'obama'
41
+ write_yaml :tweets => 'twitter search', :file => 'twitter_search'
42
+ write_yaml :tweets => {:q => 'twitter search'}, :file => 'twitter_search_and'
43
+ write_yaml :tweets => {:q => '"happy hour"'}, :file => 'happy_hour_exact'
44
+ write_yaml :tweets => {:q => 'obama OR hillary'}, :file => 'obama_or_hillary'
45
+ write_yaml :tweets => {:q => 'beer -root'}, :file => 'beer_minus_root'
46
+ write_yaml :tweets => {:q => '#haiku'}, :file => 'hashtag_haiku'
47
+ write_yaml :tweets => {:q => 'from:alexiskold'}, :file => 'from_alexiskold'
48
+ write_yaml :tweets => {:q => 'to:techcrunch'}, :file => 'to_techcrunch'
49
+ write_yaml :tweets => {:q => '@mashable'}, :file => 'reference_mashable'
50
+ write_yaml :tweets => {:q => '"happy hour" near:"san francisco"'}, :file => 'happy_hour_near_sf'
51
+ write_yaml :tweets => {:q => 'near:NYC within:15mi'}, :file => 'within_15mi_nyc'
52
+ write_yaml :tweets => {:q => 'superhero since:2008-05-01'}, :file => 'superhero_since'
53
+ write_yaml :tweets => {:q => 'ftw until:2008-05-03'}, :file => 'ftw_until'
54
+ write_yaml :tweets => {:q => 'movie -scary :)'}, :file => 'movie_positive_tude'
55
+ write_yaml :tweets => {:q => 'flight :('}, :file => 'flight_negative_tude'
56
+ write_yaml :tweets => {:q => 'traffic ?'}, :file => 'traffic_question'
57
+ write_yaml :tweets => {:q => 'hilarious filter:links'}, :file => 'hilarious_links'
58
+ write_yaml :tweets => {:q => 'congratulations', :lang => 'en'}, :file => 'english'
59
+ write_yaml :tweets => {:q => 'با', :lang => 'ar'}, :file => 'arabic'
60
+ write_yaml :tweets => {:q => 'Boston Celtics', :rpp => '30'}, :file => 'results_per_page'
61
+ end
62
+ end
63
+
64
+ def write_yaml(opts = {})
65
+ @client = TwitterSearch::Client.new 'twitter-search'
66
+ tweets = @client.query(opts[:tweets])
67
+ File.open(File.join(File.dirname(__FILE__), 'test', 'yaml', "#{opts[:file]}.yaml"), 'w+') do |file|
68
+ file.puts tweets.to_yaml
69
+ end
70
+ end
data/TODO.markdown ADDED
@@ -0,0 +1,7 @@
1
+ Add tests for:
2
+
3
+ * since_id: returns tweets with status ids greater than the given id.
4
+ * geocode: returns tweets by users located within a given radius of the given latitude/longitude, where the user's location is taken from their Twitter profile.
5
+ * show_user: when "true", adds "<user>:" to the beginning of the tweet. This is useful for readers that do not display Atom's author field. The default is "false".
6
+ * callback: if supplied, the response will use the JSONP format with a callback of the given name. E.g., http://search.twitter.com/search.json?callback=foo&q=twitter
7
+
data/lib/trends.rb ADDED
@@ -0,0 +1,24 @@
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 < Array
14
+ VARS = [:date]
15
+ attr_reader *VARS
16
+
17
+ def initialize(opts)
18
+ trends = opts('trends').delete
19
+ trends = trends.values.first.collect { |each| Trend.new(each) }
20
+ super(trends)
21
+ VARS.each { |each| instance_variable_set "@#{each}", opts[each.to_s] }
22
+ end
23
+ end
24
+ end
data/lib/tweets.rb ADDED
@@ -0,0 +1,33 @@
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 < Array
14
+ VARS = [:since_id, :max_id, :results_per_page, :page, :query, :next_page]
15
+ attr_reader *VARS
16
+
17
+ def initialize(opts)
18
+ results = opts.delete('results') || []
19
+ results.collect! { |each| Tweet.new(each) }
20
+ super(results)
21
+ VARS.each { |each| instance_variable_set "@#{each}", opts[each.to_s] }
22
+ end
23
+
24
+ def has_next_page?
25
+ ! @next_page.nil?
26
+ end
27
+
28
+ def get_next_page
29
+ client = Client.new
30
+ return client.query( CGI.parse( @next_page[1..-1] ) )
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,103 @@
1
+ require 'rubygems'
2
+ require 'net/http'
3
+ require 'json'
4
+ require 'cgi'
5
+
6
+ require File.join(File.dirname(__FILE__), 'tweets')
7
+ require File.join(File.dirname(__FILE__), 'trends')
8
+
9
+ module TwitterSearch
10
+ class SearchOperatorError < ArgumentError
11
+ end
12
+ class SearchServerError < RuntimeError
13
+ end
14
+
15
+ class Client
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
+
20
+ attr_accessor :agent
21
+ attr_accessor :timeout
22
+
23
+ def initialize(agent = 'twitter-search', timeout = DEFAULT_TIMEOUT)
24
+ @agent = agent
25
+ @timeout = timeout
26
+ end
27
+
28
+ def headers
29
+ { "Content-Type" => 'application/json',
30
+ "User-Agent" => @agent }
31
+ end
32
+
33
+ def query(opts = {})
34
+ url = URI.parse(TWITTER_SEARCH_API_URL)
35
+ url.query = sanitize_query(opts)
36
+
37
+ ensure_no_location_operators(url.query)
38
+
39
+ req = Net::HTTP::Get.new(url.path)
40
+ http = Net::HTTP.new(url.host, url.port)
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,
49
+ "Twitter responded with a 404 for your query."
50
+ end
51
+
52
+ json = res.body
53
+ parsed_json = JSON.parse(json)
54
+
55
+ if parsed_json['error']
56
+ raise TwitterSearch::SearchServerError,
57
+ "Twitter responded with an error body: #{parsed_json['error']}"
58
+ end
59
+
60
+ Tweets.new parsed_json
61
+ end
62
+
63
+ def trends(opts = {})
64
+ url = URI.parse(TWITTER_TRENDS_API_URL)
65
+ if opts['exclude_hashtags']
66
+ url.query = sanitize_query_hash({ :exclude_hashtags => opts['exclude_hashtags'] })
67
+ end
68
+
69
+ req = Net::HTTP::Get.new(url.path)
70
+ http = Net::HTTP.new(url.host, url.port)
71
+ http.read_timeout = timeout
72
+
73
+ json = http.start { |http|
74
+ http.get("#{url.path}?#{url.query}", headers)
75
+ }.body
76
+
77
+ Trends.new JSON.parse(json)
78
+ end
79
+
80
+ def sanitize_query(opts)
81
+ if opts.is_a? String
82
+ "q=#{CGI.escape(opts)}"
83
+ elsif opts.is_a? Hash
84
+ "#{sanitize_query_hash(opts)}"
85
+ end
86
+ end
87
+
88
+ def sanitize_query_hash(query_hash)
89
+ query_hash.collect { |key, value|
90
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
91
+ }.join('&')
92
+ end
93
+
94
+ def ensure_no_location_operators(query_string)
95
+ if query_string.include?("near%3A") ||
96
+ query_string.include?("within%3A")
97
+ raise TwitterSearch::SearchOperatorError,
98
+ "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."
99
+ end
100
+ end
101
+
102
+ end
103
+ 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 ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bbrowning-twitter-search
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.8.2
5
+ platform: ruby
6
+ authors:
7
+ - Dustin Sallings
8
+ - Dan Croak
9
+ - Luke Francl
10
+ - Matt Jankowski
11
+ - Matt Sanford
12
+ - Alejandro Crosa
13
+ - Danny Burkes
14
+ - Don Brown
15
+ - HotFusionMan
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2009-06-29 21:00:00 -07:00
21
+ default_executable:
22
+ dependencies:
23
+ - !ruby/object:Gem::Dependency
24
+ name: json-jruby
25
+ type: :runtime
26
+ version_requirement:
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 1.1.2
32
+ version:
33
+ description: Ruby client for Twitter Search.
34
+ email: dcroak@thoughtbot.com
35
+ executables: []
36
+
37
+ extensions: []
38
+
39
+ extra_rdoc_files: []
40
+
41
+ files:
42
+ - CHANGELOG.textile
43
+ - Rakefile
44
+ - README.markdown
45
+ - TODO.markdown
46
+ - lib/trends.rb
47
+ - lib/tweets.rb
48
+ - lib/twitter_search.rb
49
+ - shoulda_macros/twitter_search.rb
50
+ has_rdoc: true
51
+ homepage: http://github.com/dancroak/twitter-search
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.2.0
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Ruby client for Twitter Search. Includes trends.
76
+ test_files: []
77
+