jsl-osprey 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,16 @@
1
+ = Description
2
+
3
+ Osprey is a tool for watching and grabbing Twitter tweets. Currently, it provides an interface over the "Search" API.
4
+
5
+ It keeps track of tweets that have been seen for a particular term so that you don't have to.
6
+
7
+ = Usage
8
+
9
+ results = Osprey::Search.new('swine flu').fetch
10
+
11
+ results.records # Returns all records found as Osprey::Tweet objects
12
+ results.new_records # Returns only the records that you haven't seen in response to the query 'swine flu'
13
+
14
+ = Author
15
+
16
+ Justin S. Leitgeb, justin@phq.org
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ require 'rake'
5
+ require 'spec/rake/spectask'
6
+ require 'rake/rdoctask'
7
+
8
+ require 'lib/osprey'
9
+
10
+ desc 'Test the plugin.'
11
+ Spec::Rake::SpecTask.new(:spec) do |t|
12
+ t.spec_opts = ["--format", "progress", "--colour"]
13
+ t.libs << 'lib'
14
+ t.verbose = true
15
+ end
16
+
17
+ begin
18
+ require 'jeweler'
19
+ Jeweler::Tasks.new do |gemspec|
20
+ gemspec.name = "osprey"
21
+ gemspec.summary = "A Twitter API that keeps track of tweets you've seen"
22
+ gemspec.email = "justin@phq.org"
23
+ gemspec.homepage = "http://github.com/jsl/osprey"
24
+ gemspec.description = "Osprey interfaces with the Twitter search API and keeps track of tweets you've seen"
25
+ gemspec.authors = ["Justin Leitgeb"]
26
+ end
27
+ rescue LoadError
28
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
29
+ end
30
+
31
+ desc "Run all the tests"
32
+ task :default => :spec
33
+
34
+ desc 'Generate documentation'
35
+ Rake::RDocTask.new(:rdoc) do |rdoc|
36
+ rdoc.rdoc_dir = 'rdoc'
37
+ rdoc.title = 'Osprey'
38
+ rdoc.options << '--line-numbers' << '--inline-source'
39
+ rdoc.rdoc_files.include('README')
40
+ rdoc.rdoc_files.include('lib/**/*.rb')
41
+ end
42
+
@@ -0,0 +1,8 @@
1
+ class Array
2
+
3
+ # From Rails' ActiveSupport library
4
+ def extract_options!
5
+ last.is_a?(::Hash) ? pop : {}
6
+ end
7
+
8
+ end
@@ -0,0 +1,5 @@
1
+ class Hash
2
+ def reverse_merge(other_hash)
3
+ other_hash.merge(self)
4
+ end
5
+ end
@@ -0,0 +1,21 @@
1
+ module Osprey
2
+ module Backend
3
+
4
+ # Class exists for cases where you don't want Feedzirra to remember what
5
+ # it has fetched. If this backend is selected, user needs to pass arguments
6
+ # in the form of a Feed object to the Reader class to help Feedzirra know when
7
+ # a page hasn't changed, and which feed entries have been updated.
8
+ class DevNull
9
+
10
+ def initialize(options = { })
11
+ end
12
+
13
+ def get(url)
14
+ end
15
+
16
+ def set(url, result)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,32 @@
1
+ require 'md5'
2
+ require 'uri'
3
+
4
+ module Osprey
5
+ module Backend
6
+ class Filesystem
7
+
8
+ DEFAULTS = {
9
+ :path => File.expand_path(File.join(%w[ ~ / .osprey ]))
10
+ } unless defined?(DEFAULTS)
11
+
12
+ def initialize(options = { })
13
+ @options = DEFAULTS.merge(options)
14
+ end
15
+
16
+ def get(url)
17
+ f = filename_for(url)
18
+ Marshal.load(File.read(f)) if File.exist?(f)
19
+ end
20
+
21
+ def set(url, result)
22
+ File.open(filename_for(url), 'w') {|f| f.write(Marshal.dump(result)) }
23
+ end
24
+
25
+ private
26
+
27
+ def filename_for(url)
28
+ File.join(@options[:path], MD5.hexdigest(URI.parse(url).normalize.to_s))
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ module Osprey
2
+ module Backend
3
+
4
+ # Can be used to set up Memcache, or clients able to speak the Memcache protocol such as
5
+ # Tokyo Tyrant, as a Feedzirra::Backend.
6
+ class Memcache
7
+ DEFAULTS = {
8
+ :server => 'localhost',
9
+ :port => '11211'
10
+ } unless defined?(DEFAULTS)
11
+
12
+ def initialize(options = { })
13
+ @options = DEFAULTS.merge(options)
14
+ @cache = MemCache.new([ @options[:server], @options[:port] ].join(':'), :namespace => 'Osprey')
15
+ end
16
+
17
+ def get(url)
18
+ res = @cache.get(key_for(url))
19
+ Marshal.load(res) unless res.nil?
20
+ end
21
+
22
+ def set(url, result)
23
+ @cache.set(key_for(url), Marshal.dump(result))
24
+ end
25
+
26
+ private
27
+
28
+ def key_for(url)
29
+ MD5.hexdigest(URI.parse(url).normalize.to_s)
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,22 @@
1
+ module Osprey
2
+ module Backend
3
+
4
+ # Memory store uses a ruby Hash to store the results of feed fetches.
5
+ # It won't persist after the application exits.
6
+ class Memory
7
+
8
+ def initialize(options = { })
9
+ @store = { }
10
+ end
11
+
12
+ def get(url)
13
+ @store[url]
14
+ end
15
+
16
+ def set(url, result)
17
+ @store[url] = result
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ module Osprey
2
+ class ResultSet < Array
3
+ def records
4
+ self
5
+ end
6
+
7
+ def new_records
8
+ self.select{|r| r.new_record?}
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,73 @@
1
+ module Osprey
2
+ class Search
3
+ attr_reader :term
4
+
5
+ DEFAULTS = {
6
+ :backend => {
7
+ :klass => Osprey::Backend::Memory
8
+ }
9
+ } unless defined?(DEFAULTS)
10
+
11
+ def initialize(term, options = { })
12
+ @term = term
13
+ @options = options.reverse_merge(DEFAULTS)
14
+ @backend = initialize_backend(@options[:backend][:klass])
15
+ end
16
+
17
+ def fetch
18
+ res = Curl::Easy.perform(url)
19
+
20
+ if res.response_code == 200
21
+ parse_tweets(res.body_str)
22
+ else
23
+ $stderr.puts "Received invalid twitter response code of #{res.response_code}."
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ # Initializes the on-disk structure that keeps track of the Twitter ids that
30
+ # we've seen. We use a sql lite data store because it handles concurrency
31
+ # for us, which is important if there are multiple threads hitting the data
32
+ # store.
33
+ def initialize_backend(klass)
34
+ klass.new
35
+ end
36
+
37
+ # Returns an Array of ids for the Tweets in result
38
+ def twitter_ids_from_json_result(json)
39
+ json['results'].map{|r| r['id'].to_i }
40
+ end
41
+
42
+ def twitter_ids_in(id_list)
43
+ id_list
44
+ end
45
+
46
+ def parse_tweets(json_text)
47
+ json = JSON.parse(json_text)
48
+
49
+ res = Osprey::ResultSet.new
50
+
51
+ presults = previous_results
52
+
53
+ json['results'].each do |t|
54
+ new_record = presults.detect{|rec| rec.twitter_id == t['id'] }.nil?
55
+ res << Osprey::Tweet.new(t.merge(:new_record? => new_record, :twitter_id => t['id']))
56
+ end
57
+
58
+ @backend.set(url, res)
59
+
60
+ res
61
+ end
62
+
63
+ def previous_results
64
+ @backend.get(url) || [ ]
65
+ end
66
+
67
+ # Returns the Twitter search URL for @term
68
+ def url
69
+ "http://search.twitter.com/search.json?q=#{CGI.escape(@term)}"
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,4 @@
1
+ module Osprey
2
+ class Tweet < OpenStruct
3
+ end
4
+ end
data/lib/osprey.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'singleton'
3
+ require 'curb'
4
+ require 'CGI'
5
+ require 'json'
6
+ require 'sequel'
7
+ require 'memcache'
8
+ require 'ostruct'
9
+
10
+ lib_dirs = [ 'core_ext', File.join('osprey', 'backend'), 'osprey' ].map do |d|
11
+ File.join(File.dirname(__FILE__), d)
12
+ end
13
+
14
+ lib_dirs.each do |d|
15
+ Dir[File.join(d, "*.rb")].each {|file| require file }
16
+ end
@@ -0,0 +1,197 @@
1
+ --- !seq:Osprey::ResultSet
2
+ - !ruby/object:Osprey::Tweet
3
+ table:
4
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
5
+ :from_user_id: 14559662
6
+ :iso_language_code: en
7
+ :new_record?: false
8
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/189894422/IMG00701_normal.JPG
9
+ :twitter_id: 1701714354
10
+ :text: School is shut down! due to swine flu!! thats a good thing right?
11
+ :created_at: Tue, 05 May 2009 01:28:10 +0000
12
+ :to_user_id:
13
+ :id: 1701714354
14
+ :from_user: moonbrella
15
+ - !ruby/object:Osprey::Tweet
16
+ table:
17
+ :source: "&lt;a href=&quot;http://orangatame.com/products/twitterberry/&quot;&gt;TwitterBerry&lt;/a&gt;"
18
+ :from_user_id: 4513402
19
+ :iso_language_code: en
20
+ :new_record?: false
21
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/133607145/djeloowel_normal.jpg
22
+ :twitter_id: 1701714238
23
+ :text: Blame it on the flu got u feelin blue, blame it on the swine got u feelin blind, blame it on the mex mex mex mex mex mexicans....
24
+ :created_at: Tue, 05 May 2009 01:28:07 +0000
25
+ :to_user_id:
26
+ :id: 1701714238
27
+ :from_user: DJELOO
28
+ - !ruby/object:Osprey::Tweet
29
+ table:
30
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
31
+ :from_user_id: 14573136
32
+ :new_record?: false
33
+ :profile_image_url: http://static.twitter.com/images/default_profile_normal.png
34
+ :twitter_id: 1701713717
35
+ :text: Swine Flu
36
+ :created_at: Tue, 05 May 2009 01:28:04 +0000
37
+ :to_user_id:
38
+ :id: 1701713717
39
+ :from_user: friendbartrends
40
+ - !ruby/object:Osprey::Tweet
41
+ table:
42
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
43
+ :from_user_id: 15592670
44
+ :iso_language_code: da
45
+ :new_record?: false
46
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/197468025/images_normal.jpg
47
+ :twitter_id: 1701713665
48
+ :text: Destroying swine flu
49
+ :created_at: Tue, 05 May 2009 01:28:04 +0000
50
+ :to_user_id:
51
+ :id: 1701713665
52
+ :from_user: premierozone
53
+ - !ruby/object:Osprey::Tweet
54
+ table:
55
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
56
+ :from_user_id: 1983836
57
+ :iso_language_code: en
58
+ :new_record?: false
59
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/61959717/DD_Oaxaca_Flower_normal.jpg
60
+ :twitter_id: 1701712780
61
+ :text: "@M_tm uh, finally, something more disturbing than swine flu..."
62
+ :created_at: Tue, 05 May 2009 01:27:58 +0000
63
+ :to_user_id: 125590
64
+ :to_user: M_tm
65
+ :id: 1701712780
66
+ :from_user: Funchilde
67
+ - !ruby/object:Osprey::Tweet
68
+ table:
69
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
70
+ :from_user_id: 15567837
71
+ :iso_language_code: en
72
+ :new_record?: false
73
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/197027649/waiting_normal.jpg
74
+ :twitter_id: 1701712037
75
+ :text: Susie gave me a Lysol Hand Sanitizer that says &quot;Protects from Swine Flu&quot; (Haha) for the bracelet.
76
+ :created_at: Tue, 05 May 2009 01:27:53 +0000
77
+ :to_user_id:
78
+ :id: 1701712037
79
+ :from_user: StayPositive_
80
+ - !ruby/object:Osprey::Tweet
81
+ table:
82
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
83
+ :from_user_id: 13123935
84
+ :iso_language_code: eo
85
+ :new_record?: false
86
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/151854910/20070408041551_nivertia06churras07-01069_normal.jpg
87
+ :twitter_id: 1701711989
88
+ :text: "@danilogentili Swine Flu !!!!!"
89
+ :created_at: Tue, 05 May 2009 01:27:53 +0000
90
+ :to_user_id: 168227
91
+ :to_user: danilogentili
92
+ :id: 1701711989
93
+ :from_user: pannain
94
+ - !ruby/object:Osprey::Tweet
95
+ table:
96
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
97
+ :from_user_id: 1580999
98
+ :iso_language_code: en
99
+ :new_record?: false
100
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/59851479/n31700687_7014_normal.jpg
101
+ :twitter_id: 1701711830
102
+ :text: realized that after swine flu we'll get Bear pox, followed by beaver fever, and ending with Mongoose Malaria.... Be on your toes!
103
+ :created_at: Tue, 05 May 2009 01:27:52 +0000
104
+ :to_user_id:
105
+ :id: 1701711830
106
+ :from_user: BubbaStoned85
107
+ - !ruby/object:Osprey::Tweet
108
+ table:
109
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
110
+ :from_user_id: 166576
111
+ :iso_language_code: en
112
+ :new_record?: false
113
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/88556721/profpic2_lg_normal.png
114
+ :twitter_id: 1701711644
115
+ :text: + &quot;90 people get the swine flu and everyone wants to wear a face mask. Millions get AIDS and nobody wears a condom. Go figure.&quot;
116
+ :created_at: Tue, 05 May 2009 01:27:51 +0000
117
+ :to_user_id:
118
+ :id: 1701711644
119
+ :from_user: gemmerzz
120
+ - !ruby/object:Osprey::Tweet
121
+ table:
122
+ :source: "&lt;a href=&quot;http://twitterfeed.com&quot;&gt;twitterfeed&lt;/a&gt;"
123
+ :from_user_id: 14562545
124
+ :iso_language_code: en
125
+ :new_record?: false
126
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/189807824/1_normal.jpg
127
+ :twitter_id: 1701711527
128
+ :text: "#swineflu 6 More Swine Flu Cases Confirmed on LI - LongIslandPress.com http://ad.vu/bir4"
129
+ :created_at: Tue, 05 May 2009 01:27:50 +0000
130
+ :to_user_id:
131
+ :id: 1701711527
132
+ :from_user: News_SwineFlu
133
+ - !ruby/object:Osprey::Tweet
134
+ table:
135
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
136
+ :from_user_id: 8288437
137
+ :iso_language_code: en
138
+ :new_record?: false
139
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/193624367/100_1084_normal.jpg
140
+ :twitter_id: 1701710705
141
+ :text: That darn Swine Flu is getting a tad close for comfort! A school about twenty minutes from mine closed down for a few days tonight!
142
+ :created_at: Tue, 05 May 2009 01:27:45 +0000
143
+ :to_user_id:
144
+ :id: 1701710705
145
+ :from_user: tylergraves2009
146
+ - !ruby/object:Osprey::Tweet
147
+ table:
148
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
149
+ :from_user_id: 3617869
150
+ :iso_language_code: en
151
+ :new_record?: true
152
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/71116723/jojobo_normal.jpg
153
+ :twitter_id: 1701710268
154
+ :text: "Explaining to the children why they don't need to be terrified of swine flu, but why they really do need to wash their hands. #swineflu"
155
+ :created_at: Tue, 05 May 2009 01:27:42 +0000
156
+ :to_user_id:
157
+ :id: 1701710268
158
+ :from_user: jojobo
159
+ - !ruby/object:Osprey::Tweet
160
+ table:
161
+ :source: "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
162
+ :from_user_id: 3362361
163
+ :iso_language_code: en
164
+ :new_record?: true
165
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/76497810/picture_normal.jpg
166
+ :twitter_id: 1701709964
167
+ :text: Another day locked up with Swine Flu outside. Just went to HomeDepot and more then 50 percent of clients have masks there.
168
+ :created_at: Tue, 05 May 2009 01:27:40 +0000
169
+ :to_user_id:
170
+ :id: 1701709964
171
+ :from_user: dennisvdheijden
172
+ - !ruby/object:Osprey::Tweet
173
+ table:
174
+ :source: "&lt;a href=&quot;http://twitterfox.net/&quot;&gt;TwitterFox&lt;/a&gt;"
175
+ :from_user_id: 643746
176
+ :iso_language_code: nl
177
+ :new_record?: true
178
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/79399738/cami1_normal.jpg
179
+ :twitter_id: 1701709832
180
+ :text: "RT: @KYLking: Byron Center High School students celebrate 'swine break' after flu closes school for week http://tinyurl.com/d2b3yh"
181
+ :created_at: Tue, 05 May 2009 01:27:39 +0000
182
+ :to_user_id:
183
+ :id: 1701709832
184
+ :from_user: creister
185
+ - !ruby/object:Osprey::Tweet
186
+ table:
187
+ :source: "&lt;a href=&quot;http://iconfactory.com/software/twitterrific&quot;&gt;twitterrific&lt;/a&gt;"
188
+ :from_user_id: 7400045
189
+ :iso_language_code: en
190
+ :new_record?: true
191
+ :profile_image_url: http://s3.amazonaws.com/twitter_production/profile_images/150699579/for_my_baby_normal.jpg
192
+ :twitter_id: 1701709797
193
+ :text: I know I said I was going to sleep but who sleeps when you can twitter Itslike an epidemic this twitter stuff it spread like the swine flu
194
+ :created_at: Tue, 05 May 2009 01:27:39 +0000
195
+ :to_user_id:
196
+ :id: 1701709797
197
+ :from_user: chefjay1121
@@ -0,0 +1 @@
1
+ {"results":[{"text":"RT @nicfish: Tracking Swine Flu with Microsoft Virtual Earth and Live Search http:\/\/bit.ly\/YqdDC --&gt; sweet!","to_user_id":null,"from_user":"tameraclark","id":1701715308,"from_user_id":386286,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/www.tweetdeck.com\/&quot;&gt;TweetDeck&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/188156012\/eye2_normal.jpg","created_at":"Tue, 05 May 2009 01:28:14 +0000"},{"text":"@keepsgettinbetr when did i worry about the swine flu","to_user_id":9477722,"to_user":"keepsgettinbetr","from_user":"Bayleeee","id":1701714956,"from_user_id":7978237,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitterfon.net\/&quot;&gt;TwitterFon&lt;\/a&gt;","profile_image_url":"http:\/\/static.twitter.com\/images\/default_profile_normal.png","created_at":"Tue, 05 May 2009 01:28:12 +0000"},{"text":"RT @TelegraphNews: Swine flu: Mexico starts to ease virus restrictions http:\/\/tinyurl.com\/db6jff","to_user_id":null,"from_user":"lili_one","id":1701714620,"from_user_id":4481131,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/www.tweetdeck.com\/&quot;&gt;TweetDeck&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/181901124\/31-01-07_1330_normal.jpg","created_at":"Tue, 05 May 2009 01:28:10 +0000"},{"text":"Up to the minute swine flu origins: the visual explanation!\nRT @tweetmeme Coconut Chutney: When pigs fly http:\/\/tinyurl.com\/d8cobj","to_user_id":null,"from_user":"hiddenground","id":1701714439,"from_user_id":4158379,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/66627032\/frau-freide_normal.jpg","created_at":"Tue, 05 May 2009 01:28:09 +0000"},{"text":"School is shut down! due to swine flu!! thats a good thing right?","to_user_id":null,"from_user":"moonbrella","id":1701714354,"from_user_id":14559662,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/189894422\/IMG00701_normal.JPG","created_at":"Tue, 05 May 2009 01:28:10 +0000"},{"text":"Blame it on the flu got u feelin blue, blame it on the swine got u feelin blind, blame it on the mex mex mex mex mex mexicans....","to_user_id":null,"from_user":"DJELOO","id":1701714238,"from_user_id":4513402,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/orangatame.com\/products\/twitterberry\/&quot;&gt;TwitterBerry&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/133607145\/djeloowel_normal.jpg","created_at":"Tue, 05 May 2009 01:28:07 +0000"},{"text":"Swine Flu","to_user_id":null,"from_user":"friendbartrends","id":1701713717,"from_user_id":14573136,"source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/static.twitter.com\/images\/default_profile_normal.png","created_at":"Tue, 05 May 2009 01:28:04 +0000"},{"text":"Destroying swine flu","to_user_id":null,"from_user":"premierozone","id":1701713665,"from_user_id":15592670,"iso_language_code":"da","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/197468025\/images_normal.jpg","created_at":"Tue, 05 May 2009 01:28:04 +0000"},{"text":"@M_tm uh, finally, something more disturbing than swine flu...","to_user_id":125590,"to_user":"M_tm","from_user":"Funchilde","id":1701712780,"from_user_id":1983836,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/61959717\/DD_Oaxaca_Flower_normal.jpg","created_at":"Tue, 05 May 2009 01:27:58 +0000"},{"text":"Susie gave me a Lysol Hand Sanitizer that says &quot;Protects from Swine Flu&quot; (Haha) for the bracelet.","to_user_id":null,"from_user":"StayPositive_","id":1701712037,"from_user_id":15567837,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/197027649\/waiting_normal.jpg","created_at":"Tue, 05 May 2009 01:27:53 +0000"},{"text":"@danilogentili Swine Flu !!!!!","to_user_id":168227,"to_user":"danilogentili","from_user":"pannain","id":1701711989,"from_user_id":13123935,"iso_language_code":"eo","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/151854910\/20070408041551_nivertia06churras07-01069_normal.jpg","created_at":"Tue, 05 May 2009 01:27:53 +0000"},{"text":"realized that after swine flu we'll get Bear pox, followed by beaver fever, and ending with Mongoose Malaria.... Be on your toes!","to_user_id":null,"from_user":"BubbaStoned85","id":1701711830,"from_user_id":1580999,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/59851479\/n31700687_7014_normal.jpg","created_at":"Tue, 05 May 2009 01:27:52 +0000"},{"text":"+ &quot;90 people get the swine flu and everyone wants to wear a face mask. Millions get AIDS and nobody wears a condom. Go figure.&quot;","to_user_id":null,"from_user":"gemmerzz","id":1701711644,"from_user_id":166576,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/88556721\/profpic2_lg_normal.png","created_at":"Tue, 05 May 2009 01:27:51 +0000"},{"text":"#swineflu 6 More Swine Flu Cases Confirmed on LI - LongIslandPress.com http:\/\/ad.vu\/bir4","to_user_id":null,"from_user":"News_SwineFlu","id":1701711527,"from_user_id":14562545,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitterfeed.com&quot;&gt;twitterfeed&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/189807824\/1_normal.jpg","created_at":"Tue, 05 May 2009 01:27:50 +0000"},{"text":"That darn Swine Flu is getting a tad close for comfort! A school about twenty minutes from mine closed down for a few days tonight!","to_user_id":null,"from_user":"tylergraves2009","id":1701710705,"from_user_id":8288437,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/193624367\/100_1084_normal.jpg","created_at":"Tue, 05 May 2009 01:27:45 +0000"}],"since_id":0,"max_id":1701715308,"refresh_url":"?since_id=1701715308&q=swine+flu","results_per_page":15,"next_page":"?page=2&max_id=1701715308&q=swine+flu","completed_in":0.032499,"page":1,"query":"swine+flu"}
@@ -0,0 +1 @@
1
+ {"results":[{"text":"School is shut down! due to swine flu!! thats a good thing right?","to_user_id":null,"from_user":"moonbrella","id":1701714354,"from_user_id":14559662,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/189894422\/IMG00701_normal.JPG","created_at":"Tue, 05 May 2009 01:28:10 +0000"},{"text":"Blame it on the flu got u feelin blue, blame it on the swine got u feelin blind, blame it on the mex mex mex mex mex mexicans....","to_user_id":null,"from_user":"DJELOO","id":1701714238,"from_user_id":4513402,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/orangatame.com\/products\/twitterberry\/&quot;&gt;TwitterBerry&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/133607145\/djeloowel_normal.jpg","created_at":"Tue, 05 May 2009 01:28:07 +0000"},{"text":"Swine Flu","to_user_id":null,"from_user":"friendbartrends","id":1701713717,"from_user_id":14573136,"source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/static.twitter.com\/images\/default_profile_normal.png","created_at":"Tue, 05 May 2009 01:28:04 +0000"},{"text":"Destroying swine flu","to_user_id":null,"from_user":"premierozone","id":1701713665,"from_user_id":15592670,"iso_language_code":"da","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/197468025\/images_normal.jpg","created_at":"Tue, 05 May 2009 01:28:04 +0000"},{"text":"@M_tm uh, finally, something more disturbing than swine flu...","to_user_id":125590,"to_user":"M_tm","from_user":"Funchilde","id":1701712780,"from_user_id":1983836,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/61959717\/DD_Oaxaca_Flower_normal.jpg","created_at":"Tue, 05 May 2009 01:27:58 +0000"},{"text":"Susie gave me a Lysol Hand Sanitizer that says &quot;Protects from Swine Flu&quot; (Haha) for the bracelet.","to_user_id":null,"from_user":"StayPositive_","id":1701712037,"from_user_id":15567837,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/197027649\/waiting_normal.jpg","created_at":"Tue, 05 May 2009 01:27:53 +0000"},{"text":"@danilogentili Swine Flu !!!!!","to_user_id":168227,"to_user":"danilogentili","from_user":"pannain","id":1701711989,"from_user_id":13123935,"iso_language_code":"eo","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/151854910\/20070408041551_nivertia06churras07-01069_normal.jpg","created_at":"Tue, 05 May 2009 01:27:53 +0000"},{"text":"realized that after swine flu we'll get Bear pox, followed by beaver fever, and ending with Mongoose Malaria.... Be on your toes!","to_user_id":null,"from_user":"BubbaStoned85","id":1701711830,"from_user_id":1580999,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/59851479\/n31700687_7014_normal.jpg","created_at":"Tue, 05 May 2009 01:27:52 +0000"},{"text":"+ &quot;90 people get the swine flu and everyone wants to wear a face mask. Millions get AIDS and nobody wears a condom. Go figure.&quot;","to_user_id":null,"from_user":"gemmerzz","id":1701711644,"from_user_id":166576,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/88556721\/profpic2_lg_normal.png","created_at":"Tue, 05 May 2009 01:27:51 +0000"},{"text":"#swineflu 6 More Swine Flu Cases Confirmed on LI - LongIslandPress.com http:\/\/ad.vu\/bir4","to_user_id":null,"from_user":"News_SwineFlu","id":1701711527,"from_user_id":14562545,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitterfeed.com&quot;&gt;twitterfeed&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/189807824\/1_normal.jpg","created_at":"Tue, 05 May 2009 01:27:50 +0000"},{"text":"That darn Swine Flu is getting a tad close for comfort! A school about twenty minutes from mine closed down for a few days tonight!","to_user_id":null,"from_user":"tylergraves2009","id":1701710705,"from_user_id":8288437,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/193624367\/100_1084_normal.jpg","created_at":"Tue, 05 May 2009 01:27:45 +0000"},{"text":"Explaining to the children why they don't need to be terrified of swine flu, but why they really do need to wash their hands. #swineflu","to_user_id":null,"from_user":"jojobo","id":1701710268,"from_user_id":3617869,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/71116723\/jojobo_normal.jpg","created_at":"Tue, 05 May 2009 01:27:42 +0000"},{"text":"Another day locked up with Swine Flu outside. Just went to HomeDepot and more then 50 percent of clients have masks there.","to_user_id":null,"from_user":"dennisvdheijden","id":1701709964,"from_user_id":3362361,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/twitter.com\/&quot;&gt;web&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/76497810\/picture_normal.jpg","created_at":"Tue, 05 May 2009 01:27:40 +0000"},{"text":"RT: @KYLking: Byron Center High School students celebrate 'swine break' after flu closes school for week http:\/\/tinyurl.com\/d2b3yh","to_user_id":null,"from_user":"creister","id":1701709832,"from_user_id":643746,"iso_language_code":"nl","source":"&lt;a href=&quot;http:\/\/twitterfox.net\/&quot;&gt;TwitterFox&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/79399738\/cami1_normal.jpg","created_at":"Tue, 05 May 2009 01:27:39 +0000"},{"text":"I know I said I was going to sleep but who sleeps when you can twitter Itslike an epidemic this twitter stuff it spread like the swine flu","to_user_id":null,"from_user":"chefjay1121","id":1701709797,"from_user_id":7400045,"iso_language_code":"en","source":"&lt;a href=&quot;http:\/\/iconfactory.com\/software\/twitterrific&quot;&gt;twitterrific&lt;\/a&gt;","profile_image_url":"http:\/\/s3.amazonaws.com\/twitter_production\/profile_images\/150699579\/for_my_baby_normal.jpg","created_at":"Tue, 05 May 2009 01:27:39 +0000"}],"since_id":0,"max_id":1701714354,"refresh_url":"?since_id=1701714354&q=swine+flu","results_per_page":15,"next_page":"?page=2&max_id=1701714354&q=swine+flu","completed_in":0.023306,"page":1,"query":"swine+flu"}
@@ -0,0 +1,35 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+
3
+ describe Osprey::Backend::DevNull do
4
+ before do
5
+ @backend = Osprey::Backend::DevNull.new
6
+ end
7
+
8
+ it_should_behave_like "all backends"
9
+
10
+ it "should initialize properly" do
11
+ Osprey::Backend::DevNull.new
12
+ end
13
+
14
+ describe "#set" do
15
+ it "should accept two arguments" do
16
+ lambda {
17
+ @backend.set('foo', 'nothing')
18
+ }.should_not raise_error
19
+ end
20
+ end
21
+
22
+ describe "#get" do
23
+ it "should accept one argument" do
24
+ lambda {
25
+ @backend.get('foo')
26
+ }.should_not raise_error
27
+ end
28
+
29
+ it "should return nil even after something is set" do
30
+ @backend.set('junk', 'something')
31
+ @backend.get('junk').should be_nil
32
+ end
33
+ end
34
+
35
+ end
@@ -0,0 +1,23 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+
3
+ describe Osprey::Backend::Filesystem do
4
+ before do
5
+ @backend = Osprey::Backend::Filesystem.new({:path => '/tmp'})
6
+ end
7
+
8
+ it_should_behave_like "all backends"
9
+
10
+ it "should accept a Hash of options for inialization" do
11
+ Osprey::Backend::Filesystem.new({:path => '/tmp'})
12
+ end
13
+
14
+ describe "#filename_for" do
15
+ it "should default to the users' home path plus the MD5 of the normalized URL" do
16
+ MD5.expects(:hexdigest).returns('bah')
17
+ uri = mock('uri')
18
+ uri.expects(:normalize).returns('foo')
19
+ URI.expects(:parse).returns(uri)
20
+ Osprey::Backend::Filesystem.new({:path => '/tmp'}).__send__(:filename_for, 'hey').should == '/tmp/bah'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,25 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+
3
+ describe Osprey::Backend::Memcache do
4
+ before do
5
+ @backend = Osprey::Backend::Memcache.new({:server => 'localhost', :port => 11211})
6
+ @cache = mock('cache')
7
+ @backend.instance_variable_set(:@cache, @cache)
8
+ end
9
+
10
+ it_should_behave_like "all backends"
11
+
12
+ describe "#get" do
13
+ it "should call #get on the cache" do
14
+ @cache.expects(:get)
15
+ @backend.get('foo')
16
+ end
17
+ end
18
+
19
+ describe "#set" do
20
+ it "should call #set on the cache" do
21
+ @cache.expects(:set)
22
+ @backend.set('foo', 'hey')
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ require File.join(File.dirname(__FILE__), %w[.. .. spec_helper])
2
+
3
+ describe Osprey::Backend::Memory do
4
+ before do
5
+ @backend = Osprey::Backend::Memory.new
6
+ end
7
+
8
+ it_should_behave_like "all backends"
9
+
10
+ it "should initialize properly" do
11
+ Osprey::Backend::Memory.new
12
+ end
13
+
14
+ end
@@ -0,0 +1,15 @@
1
+ require File.join(File.dirname(__FILE__), %w[ .. spec_helper ])
2
+
3
+ describe Osprey::ResultSet do
4
+ before do
5
+ @rs = YAML.load_file(File.join(File.dirname(__FILE__), %w[.. fixtures result_set.yml]))
6
+ end
7
+
8
+ it "should return 4 results for #new_records" do
9
+ @rs.new_records.size.should == 4
10
+ end
11
+
12
+ it "should return 15 results for #records" do
13
+ @rs.records.size.should == 15
14
+ end
15
+ end
@@ -0,0 +1,63 @@
1
+ require File.join(File.dirname(__FILE__), %w[ .. spec_helper ])
2
+
3
+ describe Osprey::Search do
4
+ describe "initialization" do
5
+ it "should initialize without error" do
6
+ Osprey::Search.new('bar')
7
+ end
8
+
9
+ it "should create a new instance of the default backend" do
10
+ Osprey::Search.new('bar').instance_variable_get(:@backend).should be_a(Osprey::Backend::Memory)
11
+ end
12
+ end
13
+
14
+ it "should set the attr accessor for the term" do
15
+ tr = Osprey::Search.new('foo')
16
+ tr.term.should == 'foo'
17
+ end
18
+
19
+ describe "#fetch" do
20
+ before do
21
+ @tr = Osprey::Search.new('swine flu')
22
+ @curl = mock('curl')
23
+ @curl.expects(:response_code).returns(200)
24
+ @swine_flu_json = File.read(File.join(File.dirname(__FILE__), %w[.. fixtures swine_flu1.json]))
25
+ @curl.expects(:body_str).returns(@swine_flu_json)
26
+ Curl::Easy.expects(:perform).with('http://search.twitter.com/search.json?q=swine+flu').returns(@curl)
27
+ @results = @tr.fetch
28
+ end
29
+
30
+ describe "on the initial request of a term" do
31
+ it "should return a Osprey::ResultSet" do
32
+ @results.should be_a(Osprey::ResultSet)
33
+ end
34
+
35
+ it "should contain 15 results" do
36
+ @results.size.should == 15
37
+ end
38
+
39
+ it "should contain all Osprey::Tweet items" do
40
+ @results.each{|t| t.should be_a(Osprey::Tweet) }
41
+ end
42
+
43
+ it "should have all results respond affirmatively to new_record?" do
44
+ @results.each{|t| t.new_record?.should be_true}
45
+ end
46
+ end
47
+
48
+ describe "when there are existing results for a term" do
49
+ before do
50
+ @curl.expects(:response_code).returns(200)
51
+
52
+ @swine_flu_json = File.read(File.join(File.dirname(__FILE__), %w[.. fixtures swine_flu2.json]))
53
+ @curl.expects(:body_str).returns(@swine_flu_json)
54
+ Curl::Easy.expects(:perform).with('http://search.twitter.com/search.json?q=swine+flu').returns(@curl)
55
+ @results = @tr.fetch
56
+ end
57
+
58
+ it "should have 4 records that are not marked as new" do
59
+ @results.select{|r| r.new_record? }.size.should == 4
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,7 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Osprey do
4
+ it "should have loaded the twitter reader" do
5
+ Osprey::Search
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'mocha'
3
+ require 'spec'
4
+
5
+ require File.join(File.dirname(__FILE__), %w[.. lib osprey])
6
+
7
+ Spec::Runner.configure do |config|
8
+ config.mock_with(:mocha)
9
+ end
10
+
11
+ shared_examples_for "all backends" do
12
+ it "should respond to #get" do
13
+ @backend.should respond_to(:get)
14
+ end
15
+
16
+ it "should respond to #set" do
17
+ @backend.should respond_to(:set)
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsl-osprey
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Leitgeb
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-04 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Osprey interfaces with the Twitter search API and keeps track of tweets you've seen
17
+ email: justin@phq.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - README.rdoc
26
+ - Rakefile
27
+ - lib/core_ext/array.rb
28
+ - lib/core_ext/hash.rb
29
+ - lib/osprey.rb
30
+ - lib/osprey/backend/dev_null.rb
31
+ - lib/osprey/backend/filesystem.rb
32
+ - lib/osprey/backend/memcache.rb
33
+ - lib/osprey/backend/memory.rb
34
+ - lib/osprey/result_set.rb
35
+ - lib/osprey/tweet.rb
36
+ - lib/osprey/search.rb
37
+ - spec/fixtures/result_set.yml
38
+ - spec/fixtures/swine_flu1.json
39
+ - spec/fixtures/swine_flu2.json
40
+ - spec/osprey/backend/dev_null_spec.rb
41
+ - spec/osprey/backend/filesystem_spec.rb
42
+ - spec/osprey/backend/memcache_spec.rb
43
+ - spec/osprey/backend/memory_spec.rb
44
+ - spec/osprey/result_set_spec.rb
45
+ - spec/osprey/search_spec.rb
46
+ - spec/osprey_spec.rb
47
+ - spec/spec_helper.rb
48
+ has_rdoc: true
49
+ homepage: http://github.com/jsl/osprey
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.2.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: A Twitter API that keeps track of tweets you've seen
74
+ test_files:
75
+ - spec/osprey/backend/dev_null_spec.rb
76
+ - spec/osprey/backend/filesystem_spec.rb
77
+ - spec/osprey/backend/memcache_spec.rb
78
+ - spec/osprey/backend/memory_spec.rb
79
+ - spec/osprey/result_set_spec.rb
80
+ - spec/osprey/search_spec.rb
81
+ - spec/osprey_spec.rb
82
+ - spec/spec_helper.rb