scrapbooker 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +21 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.md +79 -0
- data/Rakefile +6 -0
- data/init.rb +1 -0
- data/lib/generators/USAGE +10 -0
- data/lib/generators/scrapbooker.rb +55 -0
- data/lib/generators/templates/migration.rb +17 -0
- data/lib/generators/templates/model.rb +14 -0
- data/lib/scrapbooker.rb +11 -0
- data/lib/scrapbooker/base.rb +20 -0
- data/lib/scrapbooker/configuration.rb +29 -0
- data/lib/scrapbooker/helpers/helpers.rb +7 -0
- data/lib/scrapbooker/helpers/tweet_helpers.rb +32 -0
- data/lib/scrapbooker/railtie.rb +10 -0
- data/lib/scrapbooker/services/services.rb +11 -0
- data/lib/scrapbooker/services/twitter_feed.rb +117 -0
- data/lib/scrapbooker/utilities.rb +44 -0
- data/lib/scrapbooker/version.rb +3 -0
- data/scrapbooker.gemspec +34 -0
- data/spec/models.rb +4 -0
- data/spec/schema.rb +11 -0
- data/spec/scrapbooker/helpers/tweet_helpers_spec.rb +38 -0
- data/spec/scrapbooker/scrapbooker_spec.rb +20 -0
- data/spec/scrapbooker/services/twitter_feed_spec.rb +48 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/support/cassettes/twitter_feed.yml +183 -0
- metadata +162 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# Scrapbooker Changelog
|
2
|
+
|
3
|
+
## 1.0 (April 6th, 2012)
|
4
|
+
|
5
|
+
Changed gem name and releasing version 1.0
|
6
|
+
|
7
|
+
## 0.1.2.1 (March 6th, 2012)
|
8
|
+
|
9
|
+
Fix for live gem.
|
10
|
+
|
11
|
+
## 0.1.2 (March 6th, 2012)
|
12
|
+
|
13
|
+
Basic generator functionality added.
|
14
|
+
|
15
|
+
## 0.1.1 (March 5th, 2012)
|
16
|
+
|
17
|
+
ContrarySheep added to authors.
|
18
|
+
|
19
|
+
## 0.1 (March 3rd, 2012)
|
20
|
+
|
21
|
+
Initial project creation.
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Christopher Griffith Studio
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Scrapbooker
|
2
|
+
|
3
|
+
Scrapbooker is a gem for rails that allows you to pull information from social media feeds then store the information locally. This is handy for keeping an archive of activity, but also a good way to make that content available to your Rails app.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
The scrapbooker gem makes use of railties, and as such requires Rails version >= 3.0.
|
8
|
+
|
9
|
+
Installing the gem is as simple as adding it your gemfile.
|
10
|
+
|
11
|
+
gem "scrapbooker"
|
12
|
+
|
13
|
+
This will also install the latest version of the gem, plus any dependencies that you may need to connect to the various services including:
|
14
|
+
|
15
|
+
+ twitter
|
16
|
+
+ simple_oauth
|
17
|
+
+ addressable
|
18
|
+
+ multipart-post
|
19
|
+
+ faraday
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
The gem provides a generator for getting started quickly. Running the generator will create a model and migration in you app. As an example, I can to create a new class called `Scrapbook` by running the following command.
|
24
|
+
|
25
|
+
rails g scrapbooker Scrapbook
|
26
|
+
|
27
|
+
Once the new scrapbook migration has been created, run the migration.
|
28
|
+
|
29
|
+
rake db:migrate
|
30
|
+
|
31
|
+
The new `scrapbook.rb` model contains `extend_with_scrapbooker`. This extends the class with the scrapbooker functionality. The scrapbooker configuration can be set by passing the settings in a `configure` block.
|
32
|
+
|
33
|
+
In the example, the Scrapbook class is going to store tweets from [@GriffithStudio](http://twitter.com/GriffithStudio) and check for new tweets every hour. In the `scrapbook.rb` model, the following should be added.
|
34
|
+
|
35
|
+
self.configure do |c|
|
36
|
+
c.update_interval = 60
|
37
|
+
c.twitter_feed = { :username => 'GriffithStudio' }
|
38
|
+
end
|
39
|
+
|
40
|
+
Now, the method `update_scrapbook` can be called on the Scrapbook class to pull any new tweets into the application. A before_filter can also be setup in the controller to perform the update at the specified interval. For this, the following can be added to the application controller.
|
41
|
+
|
42
|
+
before_filter :update_scrapbook
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def update_scrapbook
|
47
|
+
Scrapbook.update_scrapbook?
|
48
|
+
end
|
49
|
+
|
50
|
+
The Scrapbook class can be used anywhere in the application, just as any other class. for instance the following calls the first Scrapbook entry.
|
51
|
+
|
52
|
+
Scrapbook.first
|
53
|
+
|
54
|
+
Scrapbooker also extends the Scrapbook class with the a few unique attributes.
|
55
|
+
|
56
|
+
Scrapbook.first.original_tweet
|
57
|
+
Scrapbook.first.is_retweet
|
58
|
+
Scrapbook.first.links
|
59
|
+
Scrapbook.first.media
|
60
|
+
Scrapbook.first.mentions
|
61
|
+
Scrapbook.first.hashtags
|
62
|
+
|
63
|
+
In the application views, scrapbooker adds a few helper methods.
|
64
|
+
|
65
|
+
render_tweet Scrapbook.first
|
66
|
+
|
67
|
+
The helper methods can all be overridden in the application helpers.
|
68
|
+
|
69
|
+
## Testing
|
70
|
+
|
71
|
+
Testing the gem makes use of the [`vcr`](https://github.com/myronmarston/vcr) gem from Myron Marston in order to playback specific HTTP responses. This is fantastic for testing, but be warned that the tests are very brittle.
|
72
|
+
|
73
|
+
## Credits
|
74
|
+
|
75
|
+
The gem is being developed as a collaboration between [@GriffithStudio](http://twitter.com/GriffithStudio) and [@ContrarySheep](http://twitter.com/ContrarySheep).
|
76
|
+
|
77
|
+
##License
|
78
|
+
|
79
|
+
Scrapbooker is (c) 2012 Christopher Griffith Studio. It is free software, and may be redistributed under the terms specified in the LICENSE file.
|
data/Rakefile
ADDED
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "scrapbooker"
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Description:
|
2
|
+
The scrapbooker generator creates a model and migration in your Rails app. Where the NAME argument is the name of your model.
|
3
|
+
|
4
|
+
Example:
|
5
|
+
|
6
|
+
>> rails generate scrapbooker Scrapbook
|
7
|
+
|
8
|
+
Creates a new model with the class "Scrapbook" and a migration for a table called "scrapbooks".
|
9
|
+
|
10
|
+
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class ScrapbookerGenerator < Rails::Generators::NamedBase
|
4
|
+
include Rails::Generators::Migration
|
5
|
+
|
6
|
+
source_root File.expand_path("../templates",__FILE__)
|
7
|
+
|
8
|
+
attr_accessor :class_name
|
9
|
+
|
10
|
+
def initialize(args,*)
|
11
|
+
super
|
12
|
+
given_name = args.first
|
13
|
+
@class_name = test_singularity(given_name) ? given_name.underscore : given_name.singularize.underscore
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_model
|
17
|
+
template "model.rb", "app/models/#{@class_name}.rb"
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_migration
|
21
|
+
migration_template "migration.rb", "db/migrate/#{migration_file_name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
@@migrations = false
|
25
|
+
|
26
|
+
def self.next_migration_number(dirname)
|
27
|
+
if ActiveRecord::Base.timestamped_migrations
|
28
|
+
if @@migrations
|
29
|
+
(current_migration_number(dirname) + 1)
|
30
|
+
else
|
31
|
+
@@migrations = true
|
32
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
33
|
+
end
|
34
|
+
else
|
35
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def migration_name
|
43
|
+
"create_#{@class_name.pluralize}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def migration_file_name
|
47
|
+
"#{migration_name}.rb"
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_singularity(str)
|
51
|
+
str.pluralize != str and str.singularize == str
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Create<%= @class_name.pluralize.camelize -%> < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :<%= @class_name.pluralize -%> do |t|
|
4
|
+
t.boolean :display, :default => true
|
5
|
+
t.text :entry
|
6
|
+
t.datetime :posted_at
|
7
|
+
t.text :raw_feed
|
8
|
+
t.string :source
|
9
|
+
t.integer :source_id, :limit => 8
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down
|
15
|
+
drop_table :<%= @class_name.pluralize -%>
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class <%= @class_name.camelize -%> < ActiveRecord::Base
|
2
|
+
|
3
|
+
extend_with_scrapbooker
|
4
|
+
|
5
|
+
#
|
6
|
+
# Example Scrapbooker configuration settings. Refer to documentation for other configuration options.
|
7
|
+
#
|
8
|
+
# self.configure do |c|
|
9
|
+
# c.update_interval = 60 # In minutes
|
10
|
+
# c.twitter_feed = { :username => 'username', :include_retweets => true }
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
|
14
|
+
end
|
data/lib/scrapbooker.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "generators/scrapbooker"
|
2
|
+
|
3
|
+
require "scrapbooker/railtie" if defined? Rails
|
4
|
+
require "scrapbooker/base"
|
5
|
+
require "scrapbooker/configuration"
|
6
|
+
require "scrapbooker/utilities"
|
7
|
+
require "scrapbooker/helpers/helpers"
|
8
|
+
require "scrapbooker/services/services"
|
9
|
+
|
10
|
+
module Scrapbooker
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Scrapbooker
|
2
|
+
module Base
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def extend_with_scrapbooker
|
9
|
+
|
10
|
+
include Scrapbooker::Services
|
11
|
+
|
12
|
+
extend Scrapbooker::Utilities
|
13
|
+
extend Scrapbooker::Services
|
14
|
+
|
15
|
+
validates_uniqueness_of :source_id, :scope => :source
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Scrapbooker
|
2
|
+
class Configuration
|
3
|
+
|
4
|
+
def update_interval=(minutes)
|
5
|
+
@interval = minutes
|
6
|
+
end
|
7
|
+
|
8
|
+
def update_interval
|
9
|
+
@interval = @interval ? @interval : 1440 #The default interval in minutes (24 hours)
|
10
|
+
end
|
11
|
+
|
12
|
+
def timestamp_storage=(path)
|
13
|
+
@timestamp_storage = path
|
14
|
+
end
|
15
|
+
|
16
|
+
def timestamp_storage
|
17
|
+
@timestamp_storage = @timestamp_storage ? @timestamp_storage : "#{Rails.root}/tmp"
|
18
|
+
end
|
19
|
+
|
20
|
+
def twitter_feed=(options)
|
21
|
+
@twitter_feed = options
|
22
|
+
end
|
23
|
+
|
24
|
+
def twitter_feed
|
25
|
+
@twitter_feed
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Scrapbooker
|
2
|
+
module Helpers
|
3
|
+
module TweetHelpers
|
4
|
+
|
5
|
+
def render_tweet(tweet)
|
6
|
+
rendered_tweet = tweet.entry
|
7
|
+
tweet.hashtags.each { |hashtag| expand_hashtag(hashtag,rendered_tweet) }
|
8
|
+
tweet.mentions.each { |mention| expand_mention(mention,rendered_tweet) }
|
9
|
+
tweet.links.each { |link| expand_link(link,rendered_tweet) }
|
10
|
+
tweet.media.each { |media| expand_media(media,rendered_tweet) }
|
11
|
+
return raw rendered_tweet
|
12
|
+
end
|
13
|
+
|
14
|
+
def expand_hashtag(hashtag,tweet)
|
15
|
+
tweet.sub!("#{hashtag}", link_to(hashtag,"http://twitter.com/search/#{URI.escape(hashtag)}",:target => :blank))
|
16
|
+
end
|
17
|
+
|
18
|
+
def expand_link(link,tweet)
|
19
|
+
tweet.sub!(link['display'], link_to(link['source'],link['source'],:target => :blank))
|
20
|
+
end
|
21
|
+
|
22
|
+
def expand_media(media,tweet)
|
23
|
+
tweet.sub!(media['display'], link_to(media['source'],media['source'],:target => :blank))
|
24
|
+
end
|
25
|
+
|
26
|
+
def expand_mention(mention,tweet)
|
27
|
+
tweet.sub!(/@#{mention}/i, link_to("@#{mention}","http://twitter.com/#{mention}",:target => :blank))
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Scrapbooker
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer 'scrapbooker.initialize' do
|
4
|
+
ActiveSupport.on_load(:active_record) do
|
5
|
+
ActiveRecord::Base.send :include, Scrapbooker::Base
|
6
|
+
ActionView::Base.send :include, Scrapbooker::Helpers
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'twitter'
|
2
|
+
|
3
|
+
module Scrapbooker
|
4
|
+
module Services
|
5
|
+
|
6
|
+
class TwitterFeed
|
7
|
+
def self.default_options
|
8
|
+
@default_options ||= {
|
9
|
+
:username => nil,
|
10
|
+
:include_retweets => true,
|
11
|
+
:exclude_replies => true,
|
12
|
+
:include_entities => true,
|
13
|
+
:count => 20,
|
14
|
+
:trim_user => true
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :username, :include_retweets, :exclude_replies, :include_entities, :count, :trim_user, :active
|
19
|
+
|
20
|
+
def initialize(options)
|
21
|
+
@active = options ? true : false
|
22
|
+
options ||= {}
|
23
|
+
options = self.class.default_options.merge(options)
|
24
|
+
@username = options[:username]
|
25
|
+
@include_retweets = options[:include_retweets]
|
26
|
+
@exclude_replies = options[:exclude_replies]
|
27
|
+
@include_entities = options[:include_entities]
|
28
|
+
@count = options[:count]
|
29
|
+
@trim_user = options[:trim_user]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def poll_twitter?
|
34
|
+
poll_twitter if self.twitter_feed.active
|
35
|
+
end
|
36
|
+
|
37
|
+
def poll_twitter
|
38
|
+
results = Twitter.user_timeline(self.twitter_feed.username, {
|
39
|
+
:include_rts => self.twitter_feed.include_retweets,
|
40
|
+
:include_replies => self.twitter_feed.exclude_replies,
|
41
|
+
:include_entities => self.twitter_feed.include_entities,
|
42
|
+
:count => self.twitter_feed.count,
|
43
|
+
:trim_user => self.twitter_feed.trim_user
|
44
|
+
})
|
45
|
+
for tweet in results do
|
46
|
+
store_tweet(tweet)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def store_tweet(tweet)
|
51
|
+
self.create(
|
52
|
+
:source => 'twitter',
|
53
|
+
:source_id => tweet.id,
|
54
|
+
:raw_feed => tweet.instance_variable_get('@attrs').to_yaml,
|
55
|
+
:posted_at => tweet.created_at,
|
56
|
+
:entry => tweet.text
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
module TweetMethods
|
61
|
+
|
62
|
+
def original_tweet
|
63
|
+
YAML::load(self.raw_feed)
|
64
|
+
end
|
65
|
+
|
66
|
+
def is_retweet
|
67
|
+
self.original_tweet['retweeted_status'] ? true : false
|
68
|
+
end
|
69
|
+
|
70
|
+
def links
|
71
|
+
links = Array.new
|
72
|
+
if self.original_tweet['entities'] && self.original_tweet['entities']['urls']
|
73
|
+
for link in self.original_tweet['entities']['urls'] do
|
74
|
+
link_hash = Hash['source', link['expanded_url'], 'display', link['url']]
|
75
|
+
links << link_hash
|
76
|
+
end
|
77
|
+
end
|
78
|
+
return links
|
79
|
+
end
|
80
|
+
|
81
|
+
def media
|
82
|
+
media = Array.new
|
83
|
+
if self.original_tweet['entities'] && self.original_tweet['entities']['media']
|
84
|
+
for entity in self.original_tweet['entities']['media'] do
|
85
|
+
media_hash = Hash['source', entity['media_url'], 'display', entity['url'], 'type', entity['type']]
|
86
|
+
media << media_hash
|
87
|
+
end
|
88
|
+
end
|
89
|
+
return media
|
90
|
+
end
|
91
|
+
|
92
|
+
def mentions
|
93
|
+
mentions = Array.new
|
94
|
+
if self.original_tweet['entities'] && self.original_tweet['entities']['user_mentions']
|
95
|
+
for mention in self.original_tweet['entities']['user_mentions'] do
|
96
|
+
mentions << "#{mention['screen_name']}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
return mentions
|
100
|
+
end
|
101
|
+
|
102
|
+
def hashtags
|
103
|
+
hashtags = Array.new
|
104
|
+
if self.original_tweet['entities'] && self.original_tweet['entities']['hashtags']
|
105
|
+
for hashtag in self.original_tweet['entities']['hashtags'] do
|
106
|
+
hashtags << "##{hashtag['text']}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
return hashtags
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
include TweetMethods
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Scrapbooker
|
2
|
+
module Utilities
|
3
|
+
|
4
|
+
def configure
|
5
|
+
yield configuration
|
6
|
+
end
|
7
|
+
|
8
|
+
def configuration
|
9
|
+
@configuration ||= Configuration.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def update_scrapbook?
|
13
|
+
update_scrapbook if !timestamp_exists?
|
14
|
+
update_scrapbook if !timestamp_is_current?
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_scrapbook
|
18
|
+
#Get results from active services
|
19
|
+
poll_twitter?
|
20
|
+
update_timestamp
|
21
|
+
end
|
22
|
+
|
23
|
+
def timestamp_is_current?
|
24
|
+
last_update > self.configuration.update_interval.minutes.ago
|
25
|
+
end
|
26
|
+
|
27
|
+
def timestamp_exists?
|
28
|
+
File.exists?(timestamp_file) ? true : false
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_timestamp
|
32
|
+
File.open(timestamp_file, "w+") {|f| f.write(Time.now)}
|
33
|
+
end
|
34
|
+
|
35
|
+
def timestamp_file
|
36
|
+
"#{self.configuration.timestamp_storage}/#{self.name.underscore}_last_update"
|
37
|
+
end
|
38
|
+
|
39
|
+
def last_update
|
40
|
+
Time.parse(File.read(timestamp_file))
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
data/scrapbooker.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "scrapbooker/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "scrapbooker"
|
7
|
+
s.version = Scrapbooker::VERSION
|
8
|
+
s.authors = ["Christopher Griffith Studio","ContrarySheep"]
|
9
|
+
s.email = ["studio@christophergriffith.com","contrarysheep@gmail.com"]
|
10
|
+
s.homepage = "http://griffithstudio.github.com/scrapbooker"
|
11
|
+
s.summary = %q{Scrapbooker saves social media feeds locally.}
|
12
|
+
s.description = %q{Scrapbooker is a gem for rails that allows you to pull information from social media feeds then store the information locally.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "scrapbooker"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here;
|
22
|
+
s.add_dependency 'rails', '>= 3.0'
|
23
|
+
s.add_dependency 'json'
|
24
|
+
s.add_dependency 'twitter'
|
25
|
+
|
26
|
+
# specify development depencies here;
|
27
|
+
s.add_development_dependency 'rspec'
|
28
|
+
s.add_development_dependency 'vcr', '~> 2.0.0'
|
29
|
+
s.add_development_dependency 'fakeweb'
|
30
|
+
s.add_development_dependency 'sqlite3'
|
31
|
+
|
32
|
+
# specify any runtime dependencies here;
|
33
|
+
|
34
|
+
end
|
data/spec/models.rb
ADDED
data/spec/schema.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
ActiveRecord::Schema.define :version => 0 do
|
2
|
+
create_table "scrapbooks", :force => true do |t|
|
3
|
+
t.boolean :display, :default => true
|
4
|
+
t.text :entry
|
5
|
+
t.datetime :posted_at
|
6
|
+
t.text :raw_feed
|
7
|
+
t.string :source
|
8
|
+
t.integer :source_id, :limit => 8
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Scrapbooker::Helpers::TweetHelpers do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
@helper = class Helper
|
7
|
+
include ActionView::Helpers
|
8
|
+
include Scrapbooker::Helpers
|
9
|
+
end.new
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should return the tweet with hashtags linked" do
|
13
|
+
test = @helper.expand_hashtag('#testing','This is a test #testing')
|
14
|
+
result = "This is a test <a href=\"http://twitter.com/search/%23testing\" target=\"blank\">#testing</a>"
|
15
|
+
test.should eq result
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return the tweet with mentions linked" do
|
19
|
+
test = @helper.expand_mention('test','This is a test for @test')
|
20
|
+
result = "This is a test for <a href=\"http://twitter.com/test\" target=\"blank\">@test</a>"
|
21
|
+
test.should eq result
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should return the tweet with links expanded and linked" do
|
25
|
+
link = { 'source' => 'http://test.com', 'display' => 'http://test.com'}
|
26
|
+
test = @helper.expand_link(link,'This is a test for http://test.com')
|
27
|
+
result = "This is a test for <a href=\"http://test.com\" target=\"blank\">http://test.com</a>"
|
28
|
+
test.should eq result
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return the tweet with media expanded and linked" do
|
32
|
+
media = { 'source' => 'http://test.com/test.jpg', 'display' => 'http://test.com/test.jpg'}
|
33
|
+
test = @helper.expand_media(media,'This is a test for http://test.com/test.jpg')
|
34
|
+
result = "This is a test for <a href=\"http://test.com/test.jpg\" target=\"blank\">http://test.com/test.jpg</a>"
|
35
|
+
test.should eq result
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Scrapbooker do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
clean_database!
|
7
|
+
reset_scrapbook
|
8
|
+
end
|
9
|
+
|
10
|
+
it "tests the timestamp file" do
|
11
|
+
Scrapbook.update_timestamp
|
12
|
+
Scrapbook.last_update.strftime("%H:%M:%S").should eq Time.now.strftime("%H:%M:%S")
|
13
|
+
end
|
14
|
+
|
15
|
+
it "tests the ability to add a database entry" do
|
16
|
+
scrapbook = Scrapbook.create(:source => "test")
|
17
|
+
scrapbook.source.should eq "test"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Scrapbooker::Services::TwitterFeed do
|
4
|
+
|
5
|
+
use_vcr_cassette "twitter_feed"
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
clean_database!
|
9
|
+
reset_scrapbook
|
10
|
+
Scrapbook.configuration.twitter_feed = { :username => 'GriffithStudio',
|
11
|
+
:include_entities => true,
|
12
|
+
:exclude_replies => false,
|
13
|
+
:count => 50,
|
14
|
+
:trim_user => false }
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
it "polls twitter and stores tweets in the database" do
|
19
|
+
Scrapbook.poll_twitter?
|
20
|
+
Scrapbook.count.should > 0
|
21
|
+
end
|
22
|
+
|
23
|
+
it "checks for the retweet status" do
|
24
|
+
tweet = Scrapbook.find_by_source_id(169118526404165632)
|
25
|
+
tweet.is_retweet.should eq true
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns the links from a tweet" do
|
29
|
+
tweet = Scrapbook.find_by_source_id(187542378033332224)
|
30
|
+
tweet.links.first['display'].should eq "http://t.co/UAWQ5u27"
|
31
|
+
end
|
32
|
+
|
33
|
+
it "returns the media from a tweet" do
|
34
|
+
tweet = Scrapbook.find_by_source_id(185131786605563904)
|
35
|
+
tweet.media.first['display'].should eq "http://t.co/XiIWPrJn"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns the mentions from a tweet" do
|
39
|
+
tweet = Scrapbook.find_by_source_id(181838740447182848)
|
40
|
+
tweet.mentions.first.should eq "mrwhite_"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "returns the hashtags from a tweet" do
|
44
|
+
tweet = Scrapbook.find_by_source_id(172399167614881792)
|
45
|
+
tweet.hashtags.last.should eq "#desert"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
$LOAD_PATH << "." unless $LOAD_PATH.include?(".")
|
2
|
+
|
3
|
+
require "rubygems"
|
4
|
+
require "rails/all"
|
5
|
+
require "scrapbooker"
|
6
|
+
require "twitter"
|
7
|
+
require "vcr"
|
8
|
+
|
9
|
+
VCR.configure do |config|
|
10
|
+
config.cassette_library_dir = 'spec/support/cassettes'
|
11
|
+
config.hook_into :fakeweb
|
12
|
+
config.default_cassette_options = { :record => :new_episodes }
|
13
|
+
end
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
config.mock_with :rspec
|
17
|
+
config.extend VCR::RSpec::Macros
|
18
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
19
|
+
end
|
20
|
+
|
21
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ":memory:")
|
22
|
+
|
23
|
+
ActiveRecord::Base.silence do
|
24
|
+
ActiveRecord::Migration.verbose = false
|
25
|
+
load(File.dirname(__FILE__) + '/schema.rb')
|
26
|
+
load(File.dirname(__FILE__) + '/models.rb')
|
27
|
+
end
|
28
|
+
|
29
|
+
def clean_database!
|
30
|
+
ActiveRecord::Base.connection.execute "DELETE FROM #{Scrapbook.table_name}"
|
31
|
+
end
|
32
|
+
|
33
|
+
clean_database!
|
34
|
+
|
35
|
+
def reset_scrapbook
|
36
|
+
Scrapbook.configuration.update_interval = nil
|
37
|
+
Scrapbook.configuration.twitter_feed = nil
|
38
|
+
# Set the timestamp_storage to the tmp directory in support.
|
39
|
+
Scrapbook.configuration.timestamp_storage = File.dirname(__FILE__) + '/support/tmp'
|
40
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: get
|
5
|
+
uri: https://api.twitter.com/1/statuses/user_timeline.json?include_rts=true&include_replies=false&include_entities=true&count=50&trim_user=false&screen_name=GriffithStudio
|
6
|
+
body:
|
7
|
+
encoding: US-ASCII
|
8
|
+
string: ''
|
9
|
+
headers:
|
10
|
+
accept:
|
11
|
+
- application/json
|
12
|
+
user-agent:
|
13
|
+
- Twitter Ruby Gem 2.2.0
|
14
|
+
accept-encoding:
|
15
|
+
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
16
|
+
connection:
|
17
|
+
- close
|
18
|
+
response:
|
19
|
+
status:
|
20
|
+
code: 200
|
21
|
+
message: OK
|
22
|
+
headers:
|
23
|
+
date:
|
24
|
+
- Thu, 05 Apr 2012 21:53:30 GMT
|
25
|
+
status:
|
26
|
+
- 200 OK
|
27
|
+
x-ratelimit-class:
|
28
|
+
- api
|
29
|
+
etag:
|
30
|
+
- ! '"6f115a89394add2aedb4bb17c85c6d13"'
|
31
|
+
x-ratelimit-reset:
|
32
|
+
- '1333665736'
|
33
|
+
x-transaction-mask:
|
34
|
+
- a6183ffa5f8ca943ff1b53b5644ef1140428d3cb
|
35
|
+
pragma:
|
36
|
+
- no-cache
|
37
|
+
cache-control:
|
38
|
+
- no-cache, no-store, must-revalidate, pre-check=0, post-check=0
|
39
|
+
x-mid:
|
40
|
+
- 02e9a379d6b522f6a97a71043493c8deb57784d0
|
41
|
+
x-ratelimit-limit:
|
42
|
+
- '150'
|
43
|
+
expires:
|
44
|
+
- Tue, 31 Mar 1981 05:00:00 GMT
|
45
|
+
last-modified:
|
46
|
+
- Thu, 05 Apr 2012 21:53:30 GMT
|
47
|
+
content-type:
|
48
|
+
- application/json; charset=utf-8
|
49
|
+
x-ratelimit-remaining:
|
50
|
+
- '142'
|
51
|
+
x-runtime:
|
52
|
+
- '0.14167'
|
53
|
+
x-frame-options:
|
54
|
+
- SAMEORIGIN
|
55
|
+
x-transaction:
|
56
|
+
- 6b11aaef91832139
|
57
|
+
set-cookie:
|
58
|
+
- k=10.35.55.138.1333662810682999; path=/; expires=Thu, 12-Apr-12 21:53:30 GMT;
|
59
|
+
domain=.twitter.com
|
60
|
+
- guest_id=v1%3A133366281068694086; domain=.twitter.com; path=/; expires=Sun,
|
61
|
+
06-Apr-2014 09:53:30 GMT
|
62
|
+
- _twitter_sess=BAh7CCIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%250ASGFzaHsABjoKQHVzZWR7ADoPY3JlYXRlZF9hdGwrCD%252BCf4Q2AToHaWQiJTc3%250AMTY3NGI5YjlhZTEwYTZlMTUwZTQ1OWI1NTk0Yjdi--a1704c6d223e9bfd66a3ae316c2fc45712beb2bd;
|
63
|
+
domain=.twitter.com; path=/; HttpOnly
|
64
|
+
vary:
|
65
|
+
- Accept-Encoding
|
66
|
+
content-encoding:
|
67
|
+
- gzip
|
68
|
+
content-length:
|
69
|
+
- '4743'
|
70
|
+
server:
|
71
|
+
- tfe
|
72
|
+
body:
|
73
|
+
encoding: ASCII-8BIT
|
74
|
+
string: !binary |-
|
75
|
+
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
|
180
|
+
J2gY05B3/8f/ARsDraD46AAA
|
181
|
+
http_version: '1.1'
|
182
|
+
recorded_at: Thu, 05 Apr 2012 21:53:29 GMT
|
183
|
+
recorded_with: VCR 2.0.1
|
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scrapbooker
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '1.0'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Christopher Griffith Studio
|
9
|
+
- ContrarySheep
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-04-06 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rails
|
17
|
+
requirement: &70168508446000 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *70168508446000
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: json
|
28
|
+
requirement: &70168508445580 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *70168508445580
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: twitter
|
39
|
+
requirement: &70168508445120 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *70168508445120
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rspec
|
50
|
+
requirement: &70168508444700 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *70168508444700
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: vcr
|
61
|
+
requirement: &70168500181280 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ~>
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 2.0.0
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *70168500181280
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: fakeweb
|
72
|
+
requirement: &70168500180860 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
type: :development
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: *70168500180860
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: sqlite3
|
83
|
+
requirement: &70168500180400 !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: *70168500180400
|
92
|
+
description: Scrapbooker is a gem for rails that allows you to pull information from
|
93
|
+
social media feeds then store the information locally.
|
94
|
+
email:
|
95
|
+
- studio@christophergriffith.com
|
96
|
+
- contrarysheep@gmail.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- .gitignore
|
102
|
+
- .rspec
|
103
|
+
- CHANGELOG.md
|
104
|
+
- Gemfile
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- Rakefile
|
108
|
+
- init.rb
|
109
|
+
- lib/generators/USAGE
|
110
|
+
- lib/generators/scrapbooker.rb
|
111
|
+
- lib/generators/templates/migration.rb
|
112
|
+
- lib/generators/templates/model.rb
|
113
|
+
- lib/scrapbooker.rb
|
114
|
+
- lib/scrapbooker/base.rb
|
115
|
+
- lib/scrapbooker/configuration.rb
|
116
|
+
- lib/scrapbooker/helpers/helpers.rb
|
117
|
+
- lib/scrapbooker/helpers/tweet_helpers.rb
|
118
|
+
- lib/scrapbooker/railtie.rb
|
119
|
+
- lib/scrapbooker/services/services.rb
|
120
|
+
- lib/scrapbooker/services/twitter_feed.rb
|
121
|
+
- lib/scrapbooker/utilities.rb
|
122
|
+
- lib/scrapbooker/version.rb
|
123
|
+
- scrapbooker.gemspec
|
124
|
+
- spec/models.rb
|
125
|
+
- spec/schema.rb
|
126
|
+
- spec/scrapbooker/helpers/tweet_helpers_spec.rb
|
127
|
+
- spec/scrapbooker/scrapbooker_spec.rb
|
128
|
+
- spec/scrapbooker/services/twitter_feed_spec.rb
|
129
|
+
- spec/spec_helper.rb
|
130
|
+
- spec/support/cassettes/twitter_feed.yml
|
131
|
+
homepage: http://griffithstudio.github.com/scrapbooker
|
132
|
+
licenses: []
|
133
|
+
post_install_message:
|
134
|
+
rdoc_options: []
|
135
|
+
require_paths:
|
136
|
+
- lib
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
|
+
none: false
|
145
|
+
requirements:
|
146
|
+
- - ! '>='
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
requirements: []
|
150
|
+
rubyforge_project: scrapbooker
|
151
|
+
rubygems_version: 1.8.15
|
152
|
+
signing_key:
|
153
|
+
specification_version: 3
|
154
|
+
summary: Scrapbooker saves social media feeds locally.
|
155
|
+
test_files:
|
156
|
+
- spec/models.rb
|
157
|
+
- spec/schema.rb
|
158
|
+
- spec/scrapbooker/helpers/tweet_helpers_spec.rb
|
159
|
+
- spec/scrapbooker/scrapbooker_spec.rb
|
160
|
+
- spec/scrapbooker/services/twitter_feed_spec.rb
|
161
|
+
- spec/spec_helper.rb
|
162
|
+
- spec/support/cassettes/twitter_feed.yml
|