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 +16 -0
- data/README.markdown +18 -6
- data/Rakefile +11 -11
- data/lib/trends.rb +36 -0
- data/lib/tweets.rb +45 -0
- data/lib/twitter_search.rb +53 -56
- data/shoulda_macros/twitter_search.rb +36 -0
- metadata +15 -5
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
|
-
|
|
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
|
-
##
|
|
81
|
+
## Contributors
|
|
70
82
|
|
|
71
|
-
|
|
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://
|
|
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
|
|
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.
|
|
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]*", "{
|
|
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
|
data/lib/twitter_search.rb
CHANGED
|
@@ -3,99 +3,96 @@ require 'net/http'
|
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'cgi'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
require File.join(File.dirname(__FILE__), 'tweets')
|
|
7
|
+
require File.join(File.dirname(__FILE__), 'trends')
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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
|
+
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-
|
|
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
|
-
|
|
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:
|
|
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
|
|