jsl-osprey 0.0.0

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/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