oii_twitter_goodies 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +17 -0
  4. data/Gemfile +12 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +37 -0
  7. data/Rakefile +1 -0
  8. data/lib/oii_twitter_goodies/.DS_Store +0 -0
  9. data/lib/oii_twitter_goodies/extensions/twitter_api_utils.rb +30 -0
  10. data/lib/oii_twitter_goodies/extensions/twitter_client.rb +17 -0
  11. data/lib/oii_twitter_goodies/lib/followers_for_users.rb +64 -0
  12. data/lib/oii_twitter_goodies/lib/streamer.rb +64 -0
  13. data/lib/oii_twitter_goodies/model/.DS_Store +0 -0
  14. data/lib/oii_twitter_goodies/model/bounding_box.rb +22 -0
  15. data/lib/oii_twitter_goodies/model/coordinate.rb +23 -0
  16. data/lib/oii_twitter_goodies/model/edge.rb +9 -0
  17. data/lib/oii_twitter_goodies/model/egonet.rb +8 -0
  18. data/lib/oii_twitter_goodies/model/geo.rb +23 -0
  19. data/lib/oii_twitter_goodies/model/hashtag.rb +21 -0
  20. data/lib/oii_twitter_goodies/model/location.rb +44 -0
  21. data/lib/oii_twitter_goodies/model/media.rb +43 -0
  22. data/lib/oii_twitter_goodies/model/place.rb +48 -0
  23. data/lib/oii_twitter_goodies/model/place_attribute.rb +20 -0
  24. data/lib/oii_twitter_goodies/model/result.rb +8 -0
  25. data/lib/oii_twitter_goodies/model/sample.rb +4 -0
  26. data/lib/oii_twitter_goodies/model/size.rb +25 -0
  27. data/lib/oii_twitter_goodies/model/tweet.rb +112 -0
  28. data/lib/oii_twitter_goodies/model/twitter_api_call.rb +7 -0
  29. data/lib/oii_twitter_goodies/model/url.rb +25 -0
  30. data/lib/oii_twitter_goodies/model/user.rb +106 -0
  31. data/lib/oii_twitter_goodies/model/user_mention.rb +28 -0
  32. data/lib/oii_twitter_goodies/version.rb +3 -0
  33. data/lib/oii_twitter_goodies.rb +20 -0
  34. data/oii_twitter_goodies.gemspec +29 -0
  35. metadata +231 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 344af42c7d494fcbf3126d96dbf716f7454790b0
4
+ data.tar.gz: 318f52d851942f33c8893a9b3c1a0c6438274be5
5
+ SHA512:
6
+ metadata.gz: 35908cc4716bcceded07b1b58ead4994ee7b2357df66f16f48d6d174df3977611d309757de7cac6fdece9e5fa7927f338da8141f757b7b6327731c6e2fef9a3b
7
+ data.tar.gz: b679c6c0ed1d69859217321e807e05600e262fba553f0cc0af7fb68663755b98b4fb29e34e71fb002714f6fba39a523835a6a080d79b6e4aeaf69b58bbb9776f
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in oii_twitter_goodies.gemspec
4
+ gem 'bundler'
5
+ gem 'mongo_mapper'
6
+ gem 'oauth'
7
+ gem 'twitter'
8
+ gem 'tweetstream'
9
+ gem 'hashie'
10
+ gem 'typhoeus'
11
+ gem 'json'
12
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Devin Gaffney
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # OII Twitter Goodies
2
+
3
+ This set of libraries, functions, and monkey patches to existing Twitter gems aims to make the work that Oxford Internet Institute's various faculty and staff undertakes as simple as possible. Have fun.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'oii_twitter_goodies'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install oii_twitter_goodies
18
+
19
+ ## Usage
20
+
21
+ Although this is *just* a gem, it has a few notable dependencies depending on what you're playing with:
22
+
23
+ 1. If you're using the OIITwitter class to collect REST API data from Twitter, you're going to need:
24
+ a. MongoDB installed locally, running on port 27017 (this is the default port). Thankfully, MongoDB is relatively easy to install
25
+ b. MongoMapper, the Ruby Gem that is responsible for tying between MongoDB's internals and Ruby code (the term for this type of Gem is ORM - may want to read up on that if you're ever bored).
26
+ c. Twitter and TweetStream (these are the two principle Gems used for Twitter data collection).
27
+ 2. What you get:
28
+ a. OIITwitter: A library of several functions that cover most of the bases of data collection that any of you guys are going to need (currently supports grabbing sets of tweets, users, follower/friend IDs, and egonets (and caches it!))
29
+ b. OIITweetStream: A library that wraps TweetStream's streaming API functionality, and sets up a best-practices database for storing data from the stream.
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
Binary file
@@ -0,0 +1,30 @@
1
+ module Twitter
2
+ module API
3
+ module Utils
4
+ def objects_from_response(klass, request_method, path, options={})
5
+ response = send(request_method.to_sym, path, options)
6
+ if response.class == Hash && response[:body]
7
+ response = response[:body]
8
+ objects_from_array(klass, response)
9
+ else
10
+ return response
11
+ end
12
+ end
13
+
14
+ def object_from_response(klass, request_method, path, options={})
15
+ response = send(request_method.to_sym, path, options)
16
+ return klass.from_response(response) || response
17
+ end
18
+
19
+ def cursor_from_response(collection_name, klass, request_method, path, options, method_name)
20
+ merge_default_cursor!(options)
21
+ response = send(request_method.to_sym, path, options)
22
+ if (response.class == Hash || response.class == BSON::OrderedHash) && !response.keys.include?(:body)
23
+ Twitter::Cursor.from_response({:body => response}, collection_name.to_sym, klass, self, method_name, options)
24
+ else
25
+ Twitter::Cursor.from_response(response, collection_name.to_sym, klass, self, method_name, options)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Twitter
2
+ class Client
3
+ def get(path, params={})
4
+ lookup = TwitterAPICall.first(:url => path.to_s, :params => params)
5
+ if lookup
6
+ return lookup.data
7
+ else
8
+ response = request(:get, path, params)
9
+ if response[:status] && response[:status] == 200
10
+ lookup = TwitterAPICall.new(:url => path.to_s, :params => params, :data => response[:body])
11
+ lookup.save!
12
+ end
13
+ return response
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,64 @@
1
+ class FollowersForUsers
2
+
3
+ attr_accessor :screen_names, :oauth_token, :oauth_token_secret, :consumer_key, :consumer_secret, :client
4
+
5
+ def initialize(opts={})
6
+ opts = Hashie::Mash[opts]
7
+ @screen_names = opts[:screen_names] || []
8
+ @oauth_token = opts[:oauth_token]
9
+ @oauth_token_secret = opts[:oauth_token_secret]
10
+ @consumer_key = opts[:consumer_key]
11
+ @consumer_secret = opts[:consumer_secret]
12
+ @screen_names = [@screen_names].flatten
13
+ Twitter.configure do |config|
14
+ config.consumer_key = @consumer_key
15
+ config.consumer_secret = @consumer_secret
16
+ config.oauth_token = @oauth_token
17
+ config.oauth_token_secret = @oauth_token_secret
18
+ end
19
+ @client = Twitter::Client.new
20
+ end
21
+
22
+ def grab_followers
23
+ results = {}
24
+ @screen_names.each do |screen_name|
25
+ puts "Current progress: #{@screen_names.index(screen_name)}/#{@screen_names.length} started"
26
+ user = user_data_for(screen_name)
27
+ follower_ids = follower_ids_for(screen_name)
28
+ user_data = user_data_for(follower_ids)
29
+ results[user.first.attrs] = user_data
30
+ puts "Current progress: ##{@screen_names.index(screen_name)}/#{@screen_names.length} complete"
31
+ end
32
+ puts "Done!"
33
+ return results
34
+ end
35
+
36
+ def follower_ids_for(screen_name)
37
+ return direction_ids_for screen_name, "follower"
38
+ end
39
+
40
+ def friend_ids_for(screen_name)
41
+ return direction_ids_for screen_name, "friend"
42
+ end
43
+
44
+ def direction_ids_for(screen_name, direction)
45
+ cursor = -1
46
+ ids = []
47
+ while cursor != 0
48
+ puts cursor
49
+ data = Hashie::Mash[@client.send(direction+"_ids", screen_name, :cursor => cursor).attrs]
50
+ ids = ids|data.ids
51
+ cursor = data["next_cursor"]
52
+ end
53
+ return ids
54
+ end
55
+
56
+ def user_data_for(screen_names)
57
+ screen_names = [screen_names].flatten
58
+ user_data = []
59
+ screen_names.each_slice(100) do |screen_name_set|
60
+ user_data = [user_data|@client.users(screen_name_set)].flatten
61
+ end
62
+ user_data
63
+ end
64
+ end
@@ -0,0 +1,64 @@
1
+ class Streamer
2
+ attr_accessor :stream_type, :stream_conditions, :oauth_token, :oauth_token_secret, :consumer_key, :consumer_secret
3
+
4
+ def initialize(opts={})
5
+ opts = Hashie::Mash[opts]
6
+ @stream_type = opts[:stream_type] || "track"
7
+ @stream_conditions = opts[:stream_conditions] || "lol"
8
+ @oauth_token = opts[:oauth_token]
9
+ @oauth_token_secret = opts[:oauth_token_secret]
10
+ @consumer_key = opts[:consumer_key]
11
+ @consumer_secret = opts[:consumer_secret]
12
+ TweetStream.configure do |config|
13
+ config.consumer_key = @consumer_key
14
+ config.consumer_secret = @consumer_secret
15
+ config.oauth_token = @oauth_token
16
+ config.oauth_token_secret = @oauth_token_secret
17
+ end
18
+ @client = TweetStream::Client.new
19
+ self.send("prepare_#{@stream_type}_variables")
20
+ end
21
+
22
+ def prepare_follow_variables
23
+ raise "Stream Conditions can't be nil!" if @stream_conditions.nil?
24
+ raise "Stream Conditions must be a comma-separated string or array!" if ![String, Array].include?(@stream_conditions.class)
25
+ @stream_conditions = @stream_conditions.split(",") if @stream_conditions.class == String
26
+ sc_count = @stream_conditions.count
27
+ @stream_conditions = @stream_conditions.collect(&:to_i)
28
+ @stream_conditions = @stream_conditions.select{|sc| sc != 0}
29
+ raise "#{sc_count-@stream_conditions.length} values were dropped from stream conditions as they were not integers. You must specify integers (representing valid user IDs on Twitter) for this to work" if sc_count != @stream_conditions.length
30
+ raise "Stream conditions are empty!" if @stream_conditions.length == 0
31
+ @stream_conditions
32
+ end
33
+
34
+ def prepare_track_variables
35
+ raise "Stream Conditions can't be nil!" if @stream_conditions.nil?
36
+ raise "Stream Conditions must be a comma-separated string or array!" if ![String, Array].include?(@stream_conditions.class)
37
+ raise "Stream conditions are empty!" if @stream_conditions.length == 0
38
+ @stream_conditions = @stream_conditions.split(",") if @stream_conditions.class == String
39
+ @stream_conditions
40
+ end
41
+
42
+ def prepare_locations_variables
43
+ new_value = []
44
+ @stream_conditions.split(",").each_slice(4) do |lat_lon_pair|
45
+ boundings = lat_lon_pair.collect{|b| b.to_f}
46
+ raise "Must input two pairs of numbers, separated by commas." if boundings.length!=4
47
+ raise "Latitudes are out of range (max 90 degrees)" if boundings[1].abs>90 || boundings[3].abs>90
48
+ raise "Longitudes are out of range (max 180 degrees)" if boundings[0].abs>180 || boundings[2].abs>180
49
+ new_value << boundings
50
+ end
51
+ @stream_conditions = new_value.flatten.collect(&:to_f )
52
+
53
+ end
54
+
55
+ def stream(&block)
56
+ EM.run do
57
+ @client.on_error {|msg| puts msg }
58
+ @client.on_limit {|skip_count| puts "limit reached: #{skip_count} skipped"}
59
+ @client.send(@stream_type, @stream_conditions) do |status|
60
+ block.call status
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,22 @@
1
+ class BoundingBox
2
+ include MongoMapper::Document
3
+ key :type, String, :required => true
4
+ key :coordinates, Array, :required => true
5
+ key :place_id, ObjectId
6
+ belongs_to :place
7
+
8
+ def self.example
9
+ {"type"=>"Polygon", "coordinates"=>[[[4.7289, 52.278227], [5.079207, 52.278227], [5.079207, 52.431229], [4.7289, 52.431229]]]}
10
+ end
11
+
12
+ def self.new_from_raw(bounding_box, place_id)
13
+ return nil if bounding_box.nil?
14
+ bounding_box = Hashie::Mash[bounding_box]
15
+ obj = self.new
16
+ obj.type = bounding_box["type"]
17
+ obj.coordinates = bounding_box["coordinates"]
18
+ obj.place_id = place_id
19
+ obj.save!
20
+ obj
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ class Coordinate
2
+ include MongoMapper::Document
3
+ key :type, String
4
+ #coordinate coordinates are lon lat - geo coordinates are lat lon.
5
+ key :coordinates, Array
6
+ key :tweet_id, ObjectId
7
+ belongs_to :tweet
8
+
9
+ def self.example
10
+ {"type"=>"Point", "coordinates"=>[4.8930892, 52.37051453]}
11
+ end
12
+
13
+ def self.new_from_raw(coordinate, tweet_id)
14
+ return if coordinate.nil?
15
+ coordinate = Hashie::Mash[coordinate]
16
+ obj = self.new
17
+ obj.type = coordinate["type"]
18
+ obj.coordinates = coordinate["coordinates"]
19
+ obj.tweet_id = tweet_id
20
+ obj.save!
21
+ obj
22
+ end
23
+ end
@@ -0,0 +1,9 @@
1
+ class Edge
2
+ include MongoMapper::Document
3
+ key :source
4
+ key :target
5
+ key :edge_type
6
+ key :metadata
7
+ key :ended_at
8
+ timestamps!
9
+ end
@@ -0,0 +1,8 @@
1
+ class Egonet
2
+ include MongoMapper::Document
3
+ key :alter_ids, Array
4
+ key :ego_id, Integer
5
+ key :alter_user_ids, Array
6
+ key :user_id, ObjectId
7
+ belongs_to :user
8
+ end
@@ -0,0 +1,23 @@
1
+ class Geo
2
+ include MongoMapper::Document
3
+ key :type, String
4
+ #coordinate coordinates are lon lat - geo coordinates are lat lon.
5
+ key :coordinates, Array
6
+ key :tweet_id, ObjectId
7
+ belongs_to :tweet
8
+
9
+ def self.example
10
+ {"type"=>"Point", "coordinates"=>[52.37051453, 4.8930892]}
11
+ end
12
+
13
+ def self.new_from_raw(geo, tweet_id)
14
+ return if geo.nil?
15
+ geo = Hashie::Mash[geo]
16
+ obj = self.new
17
+ obj.type = geo["type"]
18
+ obj.coordinates = geo["coordinates"]
19
+ obj.tweet_id = tweet_id
20
+ obj.save!
21
+ obj
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ class Hashtag
2
+ include MongoMapper::Document
3
+ key :text, String
4
+ key :indices, Array
5
+ key :tweet_id, ObjectId
6
+ belongs_to :tweet
7
+
8
+ def self.example
9
+ {"text"=>"IDMA", "indices"=>[27, 32]}
10
+ end
11
+
12
+ def self.new_from_raw(entity, tweet_id)
13
+ entity = Hashie::Mash[entity]
14
+ obj = self.new
15
+ obj.text = entity["text"]
16
+ obj.indices = entity["indices"]
17
+ obj.tweet_id = tweet_id
18
+ obj.save!
19
+ obj
20
+ end
21
+ end
@@ -0,0 +1,44 @@
1
+ require 'open-uri'
2
+ class Location
3
+ include MongoMapper::Document
4
+ key :term, String
5
+ key :coordinate_data_yahoo, String
6
+ key :coordinate_data_google, Hash
7
+
8
+ def self.grab(term)
9
+ term = term || ""
10
+ if (loc = Location.first(:term => term.downcase))
11
+ return loc
12
+ else
13
+ loc = Location.new(:term => term.downcase)
14
+ loc.coordinate_data_yahoo = Location.coordinate_data_yahoo(term)
15
+ loc.coordinate_data_google = Location.coordinate_data_google(term)
16
+ loc.save!
17
+ return loc
18
+ end
19
+ end
20
+
21
+ def self.coordinate_data_yahoo(term)
22
+ return Yahoo.locate(term)
23
+ end
24
+
25
+ def self.coordinate_data_google(term)
26
+ return Google.locate(term).to_hash
27
+ end
28
+
29
+ def google
30
+ return self.coordinate_data_google if self.coordinate_data_google.nil?
31
+ if self.coordinate_data_google["success"] && self.coordinate_data_google["success"] == true
32
+ return {:lat => self.coordinate_data_google["lat"], :lng => self.coordinate_data_google["lng"]}
33
+ else
34
+ return nil
35
+ end
36
+ end
37
+
38
+ def yahoo
39
+ return self.coordinate_data_yahoo if self.coordinate_data_yahoo.nil? || self.coordinate_data_yahoo.empty?
40
+ lat = self.coordinate_data_yahoo.split(",").first.to_f
41
+ lng = self.coordinate_data_yahoo.split(",").last.to_f
42
+ return {:lat => lat, :lng => lng}
43
+ end
44
+ end
@@ -0,0 +1,43 @@
1
+ require_relative 'size'
2
+
3
+ class Medium
4
+ include MongoMapper::Document
5
+ key :display_url, String
6
+ key :media_url, String
7
+ key :id_str, String
8
+ key :media_url_https, String
9
+ key :expanded_url, String
10
+ key :url, String
11
+ key :type, String
12
+ key :id, Integer
13
+ key :indices, Array
14
+ key :size_ids, Array
15
+ many :sizes, :in => :size_ids
16
+ key :tweet_id, ObjectId
17
+ belongs_to :tweet
18
+
19
+ def self.example
20
+ {"display_url"=>"pic.twitter.com/I8kPMVvy", "media_url"=>"http://pbs.twimg.com/media/A9TiOUSCYAArxdM.jpg", "sizes"=>{"thumb"=>{"resize"=>"crop", "h"=>150, "w"=>150}, "large"=>{"resize"=>"fit", "h"=>230, "w"=>632}, "small"=>{"resize"=>"fit", "h"=>124, "w"=>340}, "medium"=>{"resize"=>"fit", "h"=>218, "w"=>600}}, "id_str"=>"276094212766851072", "media_url_https"=>"https://pbs.twimg.com/media/A9TiOUSCYAArxdM.jpg", "expanded_url"=>"http://twitter.com/insomniacevents/status/276094212758462465/photo/1", "url"=>"http://t.co/I8kPMVvy", "type"=>"photo", "id"=>276094212766851072, "indices"=>[118, 138]}
21
+ end
22
+
23
+ def self.new_from_raw(media, tweet_id)
24
+ media = Hashie::Mash[media]
25
+ obj = self.new
26
+ obj.display_url = media["display_url"]
27
+ obj.media_url = media["media_url"]
28
+ obj.id_str = media["id_str"]
29
+ obj.media_url_https = media["media_url_https"]
30
+ obj.expanded_url = media["expanded_url"]
31
+ obj.url = media["url"]
32
+ obj.type = media["type"]
33
+ obj.id = media["id"]
34
+ obj.indices = media["indices"]
35
+ media["sizes"].each_pair do |size_type, size_data|
36
+ size = Size.new_from_raw(size_type, size_data, obj._id)
37
+ obj.sizes << size
38
+ end
39
+ obj.tweet_id = tweet_id
40
+ obj.save!
41
+ obj
42
+ end
43
+ end
@@ -0,0 +1,48 @@
1
+ require_relative 'place_attribute'
2
+ require_relative 'bounding_box'
3
+
4
+ class Place
5
+ include MongoMapper::Document
6
+ key :twitter_id, Integer
7
+ key :url, String
8
+ key :place_type, String
9
+ key :name, String
10
+ key :full_name, String
11
+ key :country_code, String
12
+ key :country, String
13
+ key :bounding_box_id, ObjectId
14
+ key :place_attribute_ids, Array
15
+ many :place_attributes, :in => :place_attribute_ids
16
+ key :tweet_id, ObjectId
17
+ belongs_to :tweet
18
+ belongs_to :bounding_box
19
+
20
+ def self.example
21
+ {"id"=>"99cdab25eddd6bce", "url"=>"http://api.twitter.com/1/geo/id/99cdab25eddd6bce.json", "place_type"=>"city", "name"=>"Amsterdam", "full_name"=>"Amsterdam, North Holland", "country_code"=>"NL", "country"=>"The Netherlands", "bounding_box"=>{"type"=>"Polygon", "coordinates"=>[[[4.7289, 52.278227], [5.079207, 52.278227], [5.079207, 52.431229], [4.7289, 52.431229]]]}, "attributes"=>{}}
22
+ end
23
+
24
+ def self.new_from_raw(place, tweet_id)
25
+ return if place.nil?
26
+ place = Hashie::Mash[place]
27
+ obj = self.new
28
+ obj.twitter_id = place["twitter_id"]
29
+ obj.url = place["url"]
30
+ obj.place_type = place["place_type"]
31
+ obj.name = place["name"]
32
+ obj.full_name = place["full_name"]
33
+ obj.country_code = place["country_code"]
34
+ obj.country = place["country"]
35
+ bounding_box = BoundingBox.new_from_raw(place["bounding_box"], obj._id)
36
+ if bounding_box
37
+ obj.bounding_box = bounding_box
38
+ end
39
+
40
+ place["attributes"].each_pair do |key, value|
41
+ place = PlaceAttribute.new_from_raw(key, value, obj._id)
42
+ obj.place_attributes << place
43
+ end
44
+ obj.tweet_id = tweet_id
45
+ obj.save!
46
+ obj
47
+ end
48
+ end
@@ -0,0 +1,20 @@
1
+ class PlaceAttribute
2
+ include MongoMapper::Document
3
+ key :name, String
4
+ key :value, String
5
+ key :place_id, ObjectId
6
+ belongs_to :place
7
+
8
+ def self.example
9
+ {"street_address"=>"5403 Stevens Creek Blvd"}
10
+ end
11
+
12
+ def self.new_from_raw(key, value, place_id)
13
+ obj = self.new
14
+ obj.name = key
15
+ obj.value = value
16
+ obj.place_id = place_id
17
+ obj.save!
18
+ obj
19
+ end
20
+ end
@@ -0,0 +1,8 @@
1
+ class Result
2
+ include MongoMapper::Document
3
+ key :type, String, :required => true, :default => "export_processes"
4
+ key :data
5
+ key :user_id, ObjectId
6
+ belongs_to :user
7
+ end
8
+ # Result.ensure_index([[:user_id, 1], [:type, 1]], :unique => true)
@@ -0,0 +1,4 @@
1
+ class Sample
2
+ include MongoMapper::Document
3
+ key :user_id
4
+ end
@@ -0,0 +1,25 @@
1
+ class Size
2
+ include MongoMapper::Document
3
+ key :size, String
4
+ key :resize, String
5
+ key :h, Integer
6
+ key :w, Integer
7
+ key :media_id, ObjectId
8
+ belongs_to :media
9
+
10
+ def self.example
11
+ {"thumb"=>{"resize"=>"crop", "h"=>150, "w"=>150}}
12
+ end
13
+
14
+ def self.new_from_raw(size_type, size_data, media_id)
15
+ size_data = Hashie::Mash[size_data]
16
+ obj = self.new
17
+ obj.size = size_type
18
+ obj.resize = size_data["resize"]
19
+ obj.h = size_data["h"]
20
+ obj.w = size_data["w"]
21
+ obj.media_id = media_id
22
+ obj.save!
23
+ obj
24
+ end
25
+ end
@@ -0,0 +1,112 @@
1
+ require_relative 'user_mention'
2
+ require_relative 'coordinate'
3
+ require_relative 'hashtag'
4
+ require_relative 'media'
5
+ require_relative 'place'
6
+ require_relative 'geo'
7
+ require_relative 'url'
8
+
9
+ class Tweet
10
+ include MongoMapper::Document
11
+ key :created_at, Time, :required => true
12
+ key :twitter_id, Integer, :required => true
13
+ key :twitter_id_str, String, :required => true
14
+ key :text, String, :required => true
15
+ key :source, String, :required => true
16
+ key :truncated, Boolean, :default => false, :required => true
17
+ key :in_reply_to_status_id, Integer
18
+ key :in_reply_to_status_id_str, String
19
+ key :in_reply_to_user_id, Integer
20
+ key :in_reply_to_user_id_str, String
21
+ key :in_reply_to_screen_name, String
22
+ key :contributors, Array, :default => []
23
+ key :retweet_count, Integer, :default => 0, :required => true
24
+ key :favorited, Boolean, :default => false, :required => true
25
+ key :retweeted, Boolean, :default => false, :required => true
26
+ key :possibly_sensitive, Boolean, :default => false, :required => true
27
+ key :geo_id, ObjectId
28
+ key :coordinate_id, ObjectId
29
+ key :place_id, ObjectId
30
+ key :user_id, ObjectId
31
+ key :hashtag_ids, Array
32
+ key :url_ids, Array
33
+ key :user_mention_ids, Array
34
+ key :media_ids, Array
35
+ many :hashtags, :in => :hashtag_ids
36
+ many :urls, :in => :url_ids
37
+ many :user_mentions, :in => :user_mention_ids
38
+ many :media, :in => :media_ids
39
+ key :user_id, ObjectId
40
+ belongs_to :geo
41
+ belongs_to :coordinate
42
+ belongs_to :place
43
+ belongs_to :user
44
+
45
+ def self.example
46
+ {"entities"=>{"user_mentions"=>[], "urls"=>[{"display_url"=>"tehelka.com/story_main54.a...", "expanded_url"=>"http://www.tehelka.com/story_main54.asp?filename=Ws041212SPORTS.asp", "url"=>"http://t.co/uZmKgdkO", "indices"=>[120, 140]}], "hashtags"=>[]}, "text"=>"On the day Indian Olympics Association was suspended by IOC, Rahul Mehra says the move gives an opportunity to clean up http://t.co/uZmKgdkO", "retweet_count"=>2, "coordinates"=>nil, "possibly_sensitive"=>false, "in_reply_to_status_id_str"=>nil, "contributors"=>nil, "in_reply_to_user_id_str"=>nil, "id_str"=>"276035961274638336", "in_reply_to_screen_name"=>nil, "retweeted"=>false, "truncated"=>false, "created_at"=>"Tue Dec 04 18:51:16 +0000 2012", "geo"=>nil, "place"=>nil, "in_reply_to_status_id"=>nil, "favorited"=>false, "source"=>"web", "id"=>276035961274638336, "in_reply_to_user_id"=>nil}
47
+ end
48
+
49
+ def self.new_from_raw(tweet, user_id)
50
+ return if tweet.nil?
51
+ tweet = Hashie::Mash[tweet]
52
+ obj = self.new
53
+ obj.created_at = tweet["created_at"]
54
+ obj.twitter_id = tweet["id"]
55
+ obj.twitter_id_str = tweet["id_str"]
56
+ obj.text = tweet["text"]
57
+ obj.source = tweet["source"]
58
+ obj.truncated = tweet["truncated"]
59
+ obj.in_reply_to_status_id = tweet["in_reply_to_status_id"]
60
+ obj.in_reply_to_status_id_str = tweet["in_reply_to_status_id_str"]
61
+ obj.in_reply_to_user_id = tweet["in_reply_to_user_id"]
62
+ obj.in_reply_to_user_id_str = tweet["in_reply_to_user_id_str"]
63
+ obj.in_reply_to_screen_name = tweet["in_reply_to_screen_name"]
64
+ obj.contributors = tweet["contributors"]
65
+ obj.retweet_count = tweet["retweet_count"]
66
+ obj.favorited = tweet["favorited"]
67
+ obj.retweeted = tweet["retweeted"]
68
+ obj.possibly_sensitive = tweet["possibly_sensitive"]
69
+ tweet["twitter_id"] = obj.twitter_id
70
+ if tweet["entities"]
71
+ tweet["entities"].each_pair do |entity_type, entities|
72
+ if entity_type == "media"
73
+ entities.each do |entity|
74
+ entity = Medium.new_from_raw(entity, obj._id)
75
+ obj.media << entity
76
+ end
77
+ elsif entity_type == "urls"
78
+ entities.each do |entity|
79
+ entity = Url.new_from_raw(entity, obj._id)
80
+ obj.urls << entity
81
+ end
82
+ elsif entity_type == "hashtags"
83
+ entities.each do |entity|
84
+ entity = Hashtag.new_from_raw(entity, obj._id)
85
+ obj.hashtags << entity
86
+ end
87
+ elsif entity_type == "user_mentions"
88
+ entities.each do |entity|
89
+ entity = UserMention.new_from_raw(entity, obj._id)
90
+ obj.user_mentions << entity
91
+ end
92
+ end
93
+ end
94
+ end
95
+ place = Place.new_from_raw(tweet["place"], obj._id)
96
+ if place
97
+ obj.place = place
98
+ end
99
+ geo = Geo.new_from_raw(tweet["geo"], obj._id)
100
+ if geo
101
+ obj.geo = geo
102
+ end
103
+ coordinate = Coordinate.new_from_raw(tweet["coordinate"], obj._id)
104
+ if coordinate
105
+ obj.coordinate = coordinate
106
+ end
107
+ obj.user_id = user_id
108
+ obj.save!
109
+ obj
110
+ end
111
+
112
+ end
@@ -0,0 +1,7 @@
1
+ class TwitterAPICall
2
+ include MongoMapper::Document
3
+ key :url, String
4
+ key :data
5
+ key :params
6
+ timestamps!
7
+ end
@@ -0,0 +1,25 @@
1
+ class Url
2
+ include MongoMapper::Document
3
+ key :url, String
4
+ key :expanded_url, String
5
+ key :display_url, String
6
+ key :indices, Array
7
+ key :tweet_id, ObjectId
8
+ belongs_to :tweet
9
+
10
+ def self.example
11
+ {"display_url"=>"bit.ly/SKHqYj", "expanded_url"=>"http://bit.ly/SKHqYj", "url"=>"http://t.co/ZDfXBGCk", "indices"=>[97, 117]}
12
+ end
13
+
14
+ def self.new_from_raw(url, tweet_id)
15
+ url = Hashie::Mash[url]
16
+ obj = self.new
17
+ obj.url = url["url"]
18
+ obj.expanded_url = url["expanded_url"]
19
+ obj.display_url = url["display_url"]
20
+ obj.indices = url["indices"]
21
+ obj.tweet_id = tweet_id
22
+ obj.save!
23
+ obj
24
+ end
25
+ end
@@ -0,0 +1,106 @@
1
+ require_relative 'tweet'
2
+
3
+ class User
4
+ include MongoMapper::Document
5
+ key :twitter_id, Integer, :required => true
6
+ key :twitter_id_str, String
7
+ key :name, String
8
+ key :screen_name, String, :required => true
9
+ key :location, String
10
+ key :url, String
11
+ key :description, String
12
+ key :protected, Boolean, :default => false, :required => true
13
+ key :followers_count, Integer, :default => 0, :required => true
14
+ key :friends_count, Integer, :default => 0, :required => true
15
+ key :listed_count, Integer, :default => 0, :required => true
16
+ key :created_at, Time, :required => true
17
+ key :favourites_count, Integer
18
+ key :utc_offset, Integer
19
+ key :time_zone, String
20
+ key :geo_enabled, Boolean, :default => false, :required => true
21
+ key :verified, Boolean, :default => false, :required => true
22
+ key :statuses_count, Integer, :default => 0, :required => true
23
+ key :lang, String
24
+ key :contributors_enabled, Boolean, :default => false, :required => true
25
+ key :is_translator, Boolean, :default => false, :required => true
26
+ key :profile_background_color, String, :default => "C0DEED", :required => true
27
+ key :profile_background_image_url, String
28
+ key :profile_background_image_url_https, String
29
+ key :profile_background_tile, Boolean, :default => false, :required => true
30
+ key :profile_image_url, String
31
+ key :profile_image_url_https, String
32
+ key :profile_link_color, String, :default => "0084B4", :required => true
33
+ key :profile_sidebar_border_color, String, :default => "C0DEED", :required => true
34
+ key :profile_sidebar_fill_color, String, :default => "DDEEF6", :required => true
35
+ key :profile_text_color, String, :default => "333333", :required => true
36
+ key :profile_use_background_image, Boolean, :default => true, :required => true
37
+ key :default_profile, Boolean, :default => false, :required => true
38
+ key :default_profile_image, Boolean, :default => false, :required => true
39
+ key :following, Boolean, :default => false, :required => true
40
+ key :follow_request_sent, Boolean, :default => false, :required => true
41
+ key :notifications, Boolean, :default => false, :required => true
42
+ key :tweet_ids, Array
43
+ many :tweets, :in => :tweet_ids
44
+
45
+ def self.example
46
+ {"created_at"=>"Sat Dec 01 11:01:18 +0000 2012", "id"=>274830526970474496, "id_str"=>"274830526970474496", "text"=>"Getting ready for the #kingsofcode hack battle in Amsterdam! Just got off the plane, running on 2hrs sleep, so will see how this goes!", "source"=>"<a href=\"http://twitter.com/download/iphone\" rel=\"nofollow\">Twitter for iPhone</a>", "truncated"=>false, "in_reply_to_status_id"=>nil, "in_reply_to_status_id_str"=>nil, "in_reply_to_user_id"=>nil, "in_reply_to_user_id_str"=>nil, "in_reply_to_screen_name"=>nil, "user"=>{"id"=>14447132, "id_str"=>"14447132", "name"=>"Aaron Parecki", "screen_name"=>"aaronpk", "location"=>"Portland, OR", "url"=>"http://aaronparecki.com/", "description"=>"CTO of @esri R&D Center, Portland. Creating my own reality.", "protected"=>false, "followers_count"=>1776, "friends_count"=>619, "listed_count"=>200, "created_at"=>"Sat Apr 19 22:38:15 +0000 2008", "favourites_count"=>1543, "utc_offset"=>-28800, "time_zone"=>"Pacific Time (US & Canada)", "geo_enabled"=>true, "verified"=>false, "statuses_count"=>4331, "lang"=>"en", "contributors_enabled"=>false, "is_translator"=>false, "profile_background_color"=>"7A9AAF", "profile_background_image_url"=>"http://a0.twimg.com/profile_background_images/185835062/4786064324_b7049fbec8_b.jpg", "profile_background_image_url_https"=>"https://si0.twimg.com/profile_background_images/185835062/4786064324_b7049fbec8_b.jpg", "profile_background_tile"=>true, "profile_image_url"=>"http://a0.twimg.com/profile_images/1767475493/aaronpk-glasses_normal.png", "profile_image_url_https"=>"https://si0.twimg.com/profile_images/1767475493/aaronpk-glasses_normal.png", "profile_link_color"=>"0000FF", "profile_sidebar_border_color"=>"87BC44", "profile_sidebar_fill_color"=>"94C8FF", "profile_text_color"=>"000000", "profile_use_background_image"=>true, "default_profile"=>false, "default_profile_image"=>false, "following"=>true, "follow_request_sent"=>false, "notifications"=>nil}, "geo"=>{"type"=>"Point", "coordinates"=>[52.37051453, 4.8930892]}, "coordinates"=>{"type"=>"Point", "coordinates"=>[4.8930892, 52.37051453]}, "place"=>{"id"=>"99cdab25eddd6bce", "url"=>"http://api.twitter.com/1/geo/id/99cdab25eddd6bce.json", "place_type"=>"city", "name"=>"Amsterdam", "full_name"=>"Amsterdam, North Holland", "country_code"=>"NL", "country"=>"The Netherlands", "bounding_box"=>{"type"=>"Polygon", "coordinates"=>[[[4.7289, 52.278227], [5.079207, 52.278227], [5.079207, 52.431229], [4.7289, 52.431229]]]}, "attributes"=>{}}, "contributors"=>nil, "retweet_count"=>0, "favorited"=>false, "retweeted"=>false}
47
+ end
48
+
49
+ def self.new_from_raw(status)
50
+ status = Hashie::Mash[status]
51
+ obj = self.new
52
+ obj.twitter_id = status["user"].nil? ? status["id"] : status["user"]["id"]
53
+ obj.twitter_id_str = status["user"].nil? ? status["id_str"] : status["user"]["id_str"]
54
+ obj.name = status["user"].nil? ? status["name"] : status["user"]["name"]
55
+ obj.screen_name = status["user"].nil? ? status["screen_name"] : status["user"]["screen_name"]
56
+ obj.location = status["user"].nil? ? status["location"] : status["user"]["location"]
57
+ obj.url = status["user"].nil? ? status["url"] : status["user"]["url"]
58
+ obj.description = status["user"].nil? ? status["description"] : status["user"]["description"]
59
+ obj.protected = status["user"].nil? ? status["protected"] : status["user"]["protected"]
60
+ obj.followers_count = status["user"].nil? ? status["followers_count"] : status["user"]["followers_count"]
61
+ obj.friends_count = status["user"].nil? ? status["friends_count"] : status["user"]["friends_count"]
62
+ obj.listed_count = status["user"].nil? ? status["listed_count"] : status["user"]["listed_count"]
63
+ obj.created_at = status["user"].nil? ? status["created_at"] : status["user"]["created_at"]
64
+ obj.favourites_count = status["user"].nil? ? status["favourites_count"] : status["user"]["favourites_count"]
65
+ obj.utc_offset = status["user"].nil? ? status["utc_offset"] : status["user"]["utc_offset"]
66
+ obj.time_zone = status["user"].nil? ? status["time_zone"] : status["user"]["time_zone"]
67
+ obj.geo_enabled = status["user"].nil? ? status["geo_enabled"] : status["user"]["geo_enabled"]
68
+ obj.verified = status["user"].nil? ? status["verified"] : status["user"]["verified"]
69
+ obj.statuses_count = status["user"].nil? ? status["statuses_count"] : status["user"]["statuses_count"]
70
+ obj.lang = status["user"].nil? ? status["lang"] : status["user"]["lang"]
71
+ obj.contributors_enabled = status["user"].nil? ? status["contributors_enabled"] : status["user"]["contributors_enabled"]
72
+ obj.is_translator = status["user"].nil? ? status["is_translator"] : status["user"]["is_translator"]
73
+ obj.profile_background_color = status["user"].nil? ? status["profile_background_color"] : status["user"]["profile_background_color"]
74
+ obj.profile_background_image_url = status["user"].nil? ? status["profile_background_image_url"] : status["user"]["profile_background_image_url"]
75
+ obj.profile_background_image_url_https = status["user"].nil? ? status["profile_background_image_url_https"] : status["user"]["profile_background_image_url_https"]
76
+ obj.profile_background_tile = status["user"].nil? ? status["profile_background_tile"] : status["user"]["profile_background_tile"]
77
+ obj.profile_image_url = status["user"].nil? ? status["profile_image_url"] : status["user"]["profile_image_url"]
78
+ obj.profile_image_url_https = status["user"].nil? ? status["profile_image_url_https"] : status["user"]["profile_image_url_https"]
79
+ obj.profile_link_color = status["user"].nil? ? status["profile_link_color"] : status["user"]["profile_link_color"]
80
+ obj.profile_sidebar_border_color = status["user"].nil? ? status["profile_sidebar_border_color"] : status["user"]["profile_sidebar_border_color"]
81
+ obj.profile_sidebar_fill_color = status["user"].nil? ? status["profile_sidebar_fill_color"] : status["user"]["profile_sidebar_fill_color"]
82
+ obj.profile_text_color = status["user"].nil? ? status["profile_text_color"] : status["user"]["profile_text_color"]
83
+ obj.profile_use_background_image = status["user"].nil? ? status["profile_use_background_image"] : status["user"]["profile_use_background_image"]
84
+ obj.default_profile = status["user"].nil? ? status["default_profile"] : status["user"]["default_profile"]
85
+ obj.default_profile_image = status["user"].nil? ? status["default_profile_image"] : status["user"]["default_profile_image"]
86
+ obj.following = status["user"].nil? ? status["following"] : status["user"]["following"]
87
+ obj.follow_request_sent = status["user"].nil? ? status["follow_request_sent"] : status["user"]["follow_request_sent"]
88
+ obj.notifications = status["user"].nil? ? status["notifications"] : status["user"]["notifications"]
89
+ if status["text"]
90
+ tweet = Tweet.new_from_raw(status, obj._id)
91
+ obj.tweets << tweet
92
+ end
93
+ if status["status"]
94
+ tweet = Tweet.new_from_raw(status["status"], obj._id)
95
+ obj.tweets << tweet
96
+ end
97
+ if status["statuses"]
98
+ status["statuses"].each do |status|
99
+ tweet = Tweet.new_from_raw(status, obj._id)
100
+ obj.tweets << tweet
101
+ end
102
+ end
103
+ obj.save!
104
+ obj
105
+ end
106
+ end
@@ -0,0 +1,28 @@
1
+ class UserMention
2
+ include MongoMapper::Document
3
+ key :screen_name, String
4
+ key :name, String
5
+ key :twitter_id, Integer
6
+ key :twitter_id_str, String
7
+ key :indices, Array
8
+ key :tweet_id, ObjectId
9
+ belongs_to :tweet
10
+
11
+ def self.example
12
+ {"name"=>"film.culture360", "id_str"=>"211760188", "id"=>211760188, "indices"=>[3, 19], "screen_name"=>"film_culture360"}
13
+ end
14
+
15
+ def self.new_from_raw(entity, tweet_id)
16
+ entity = Hashie::Mash[entity]
17
+ obj = self.new
18
+ obj.screen_name = entity["screen_name"]
19
+ obj.name = entity["name"]
20
+ obj.twitter_id = entity["id"]
21
+ obj.twitter_id_str = entity["id_str"]
22
+ obj.indices = entity["indices"]
23
+ obj.tweet_id = tweet_id
24
+ obj.save!
25
+ obj
26
+ end
27
+
28
+ end
@@ -0,0 +1,3 @@
1
+ module OIITwitterGoodies
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'bundler'
2
+ require 'mongo_mapper'
3
+ require 'oauth'
4
+ require 'twitter'
5
+ require 'tweetstream'
6
+ require 'hashie'
7
+ require 'typhoeus'
8
+ require 'json'
9
+
10
+ require "oii_twitter_goodies/version"
11
+
12
+ Dir[File.dirname(__FILE__) + '/oii_twitter_goodies/extensions/*.rb'].each {|file| require file }
13
+ Dir[File.dirname(__FILE__) + '/oii_twitter_goodies/lib/*.rb'].each {|file| require file }
14
+ Dir[File.dirname(__FILE__) + '/oii_twitter_goodies/model/*.rb'].each {|file| require file }
15
+
16
+
17
+
18
+ module OIITwitterGoodies
19
+ # Your code goes here...
20
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'oii_twitter_goodies/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "oii_twitter_goodies"
8
+ gem.version = OIITwitterGoodies::VERSION
9
+ gem.authors = ["Devin Gaffney"]
10
+ gem.email = ["itsme@devingaffney.com"]
11
+ gem.description = %q{OII Twitter Goodies!}
12
+ gem.summary = %q{Warm goopy Ruby code for people at OII (or anyone really) to quickly get their hands dirty with Twitter data}
13
+ gem.homepage = "http://oii.ox.ac.uk"
14
+ gem.add_development_dependency "bundler", ">= 1.0.0"
15
+ gem.add_development_dependency "mongo_mapper"
16
+ gem.add_development_dependency "bcrypt-ruby"
17
+ gem.add_development_dependency "bson_ext"
18
+ gem.add_development_dependency "oauth"
19
+ gem.add_development_dependency "twitter"
20
+ gem.add_development_dependency "tweetstream"
21
+ gem.add_development_dependency "minitest"
22
+ gem.add_development_dependency "hashie"
23
+ gem.add_development_dependency "typhoeus"
24
+ gem.add_development_dependency "json"
25
+ gem.files = `git ls-files`.split($/)
26
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
27
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
28
+ gem.require_paths = ["lib"]
29
+ end
metadata ADDED
@@ -0,0 +1,231 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oii_twitter_goodies
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Devin Gaffney
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.0
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: mongo_mapper
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bcrypt-ruby
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ! '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bson_ext
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: oauth
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: twitter
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ! '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: tweetstream
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: hashie
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ! '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: typhoeus
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ! '>='
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ! '>='
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: json
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ! '>='
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ! '>='
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: OII Twitter Goodies!
168
+ email:
169
+ - itsme@devingaffney.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - .DS_Store
175
+ - .gitignore
176
+ - Gemfile
177
+ - LICENSE.txt
178
+ - README.md
179
+ - Rakefile
180
+ - lib/oii_twitter_goodies.rb
181
+ - lib/oii_twitter_goodies/.DS_Store
182
+ - lib/oii_twitter_goodies/extensions/twitter_api_utils.rb
183
+ - lib/oii_twitter_goodies/extensions/twitter_client.rb
184
+ - lib/oii_twitter_goodies/lib/followers_for_users.rb
185
+ - lib/oii_twitter_goodies/lib/streamer.rb
186
+ - lib/oii_twitter_goodies/model/.DS_Store
187
+ - lib/oii_twitter_goodies/model/bounding_box.rb
188
+ - lib/oii_twitter_goodies/model/coordinate.rb
189
+ - lib/oii_twitter_goodies/model/edge.rb
190
+ - lib/oii_twitter_goodies/model/egonet.rb
191
+ - lib/oii_twitter_goodies/model/geo.rb
192
+ - lib/oii_twitter_goodies/model/hashtag.rb
193
+ - lib/oii_twitter_goodies/model/location.rb
194
+ - lib/oii_twitter_goodies/model/media.rb
195
+ - lib/oii_twitter_goodies/model/place.rb
196
+ - lib/oii_twitter_goodies/model/place_attribute.rb
197
+ - lib/oii_twitter_goodies/model/result.rb
198
+ - lib/oii_twitter_goodies/model/sample.rb
199
+ - lib/oii_twitter_goodies/model/size.rb
200
+ - lib/oii_twitter_goodies/model/tweet.rb
201
+ - lib/oii_twitter_goodies/model/twitter_api_call.rb
202
+ - lib/oii_twitter_goodies/model/url.rb
203
+ - lib/oii_twitter_goodies/model/user.rb
204
+ - lib/oii_twitter_goodies/model/user_mention.rb
205
+ - lib/oii_twitter_goodies/version.rb
206
+ - oii_twitter_goodies.gemspec
207
+ homepage: http://oii.ox.ac.uk
208
+ licenses: []
209
+ metadata: {}
210
+ post_install_message:
211
+ rdoc_options: []
212
+ require_paths:
213
+ - lib
214
+ required_ruby_version: !ruby/object:Gem::Requirement
215
+ requirements:
216
+ - - ! '>='
217
+ - !ruby/object:Gem::Version
218
+ version: '0'
219
+ required_rubygems_version: !ruby/object:Gem::Requirement
220
+ requirements:
221
+ - - ! '>='
222
+ - !ruby/object:Gem::Version
223
+ version: '0'
224
+ requirements: []
225
+ rubyforge_project:
226
+ rubygems_version: 2.0.3
227
+ signing_key:
228
+ specification_version: 4
229
+ summary: Warm goopy Ruby code for people at OII (or anyone really) to quickly get
230
+ their hands dirty with Twitter data
231
+ test_files: []