ubiquitously 0.0.1.6 → 0.1.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.markdown +224 -19
- data/Rakefile +12 -4
- data/bin/u.me +4 -0
- data/lib/ubiquitously.rb +56 -7
- data/lib/ubiquitously/commands/base.rb +118 -0
- data/lib/ubiquitously/commands/post.rb +108 -0
- data/lib/ubiquitously/commands/user.rb +111 -0
- data/lib/ubiquitously/extensions/callbacks.rb +62 -0
- data/lib/ubiquitously/extensions/core.rb +88 -0
- data/lib/ubiquitously/extensions/mechanize.rb +35 -0
- data/lib/ubiquitously/models/base.rb +18 -0
- data/lib/ubiquitously/models/page.rb +28 -0
- data/lib/ubiquitously/models/post.rb +66 -0
- data/lib/ubiquitously/models/service.rb +1 -0
- data/lib/ubiquitously/models/service/account.rb +57 -0
- data/lib/ubiquitously/models/service/account/authorizable.rb +58 -0
- data/lib/ubiquitously/models/service/account/loggable.rb +50 -0
- data/lib/ubiquitously/models/service/account/restful.rb +21 -0
- data/lib/ubiquitously/models/service/post.rb +59 -0
- data/lib/ubiquitously/models/service/post/loggable.rb +64 -0
- data/lib/ubiquitously/models/service/post/ownable.rb +31 -0
- data/lib/ubiquitously/models/service/post/postable.rb +73 -0
- data/lib/ubiquitously/models/service/post/restful.rb +52 -0
- data/lib/ubiquitously/models/user.rb +128 -0
- data/lib/ubiquitously/services/blogger_den.rb +68 -0
- data/lib/ubiquitously/services/chetzit.rb +37 -0
- data/lib/ubiquitously/services/code_project.rb +21 -0
- data/lib/ubiquitously/services/delicious.rb +54 -0
- data/lib/ubiquitously/services/design_bump.rb +41 -0
- data/lib/ubiquitously/services/designgizer.rb +21 -0
- data/lib/ubiquitously/services/designr_fix.rb +21 -0
- data/lib/ubiquitously/services/digg.rb +151 -0
- data/lib/ubiquitously/services/digzign.rb +25 -0
- data/lib/ubiquitously/services/diigo.rb +68 -0
- data/lib/ubiquitously/services/dzone.rb +132 -0
- data/lib/ubiquitously/services/dzone_snippets.rb +39 -0
- data/lib/ubiquitously/services/facebook.rb +42 -0
- data/lib/ubiquitously/services/faves.rb +81 -0
- data/lib/ubiquitously/services/favshare.rb +78 -0
- data/lib/ubiquitously/services/flikode.rb +38 -0
- data/lib/ubiquitously/services/friendfeed.rb +21 -0
- data/lib/ubiquitously/services/gist.rb +52 -0
- data/lib/ubiquitously/services/google_buzz.rb +15 -0
- data/lib/ubiquitously/services/identica.rb +15 -0
- data/lib/ubiquitously/services/jump_tags.rb +21 -0
- data/lib/ubiquitously/services/kailoon.rb +27 -0
- data/lib/ubiquitously/services/meta_filter.rb +26 -0
- data/lib/ubiquitously/services/mixx.rb +94 -0
- data/lib/ubiquitously/services/mmo.rb +22 -0
- data/lib/ubiquitously/services/mvc_forge.rb +37 -0
- data/lib/ubiquitously/services/my_space.rb +25 -0
- data/lib/ubiquitously/services/nestdev.rb +36 -0
- data/lib/ubiquitously/services/newsvine.rb +76 -0
- data/lib/ubiquitously/services/pixel_groovy.rb +22 -0
- data/lib/ubiquitously/services/posterous.rb +24 -0
- data/lib/ubiquitously/services/programmers_heaven.rb +21 -0
- data/lib/ubiquitously/{propeller.rb → services/propeller.rb} +17 -26
- data/lib/ubiquitously/services/queness.rb +38 -0
- data/lib/ubiquitously/services/reddit.rb +62 -0
- data/lib/ubiquitously/services/scribd.rb +21 -0
- data/lib/ubiquitously/services/script_and_style.rb +23 -0
- data/lib/ubiquitously/services/sharebrain.rb +19 -0
- data/lib/ubiquitously/services/shoutwire.rb +34 -0
- data/lib/ubiquitously/services/slideshare.rb +70 -0
- data/lib/ubiquitously/services/smipple.rb +21 -0
- data/lib/ubiquitously/services/snipplr.rb +67 -0
- data/lib/ubiquitously/services/snipt.rb +49 -0
- data/lib/ubiquitously/services/sphinn.rb +24 -0
- data/lib/ubiquitously/services/stumble_upon.rb +67 -0
- data/lib/ubiquitously/services/stumpedia.rb +13 -0
- data/lib/ubiquitously/services/tumblr.rb +28 -0
- data/lib/ubiquitously/services/tweako.rb +53 -0
- data/lib/ubiquitously/services/twitter.rb +60 -0
- data/lib/ubiquitously/services/web_blend.rb +25 -0
- data/lib/ubiquitously/services/webdev5.rb +32 -0
- data/lib/ubiquitously/services/who_freelance.rb +21 -0
- data/lib/ubiquitously/services/wordtaps.rb +36 -0
- data/lib/ubiquitously/services/yahoo_buzz.rb +64 -0
- data/lib/ubiquitously/services/youblr.rb +15 -0
- data/lib/ubiquitously/services/zabox.rb +22 -0
- data/lib/ubiquitously/support/active_record.rb +25 -0
- data/lib/ubiquitously/support/storage.rb +71 -0
- data/test/config/cookies.yml +111 -0
- data/test/config/credentials.yml +7 -0
- data/test/config/secrets.yml +33 -0
- data/test/config/tokens.yml +30 -0
- data/test/meta.html +19 -0
- data/test/test_delicious.rb +37 -0
- data/test/test_digg.rb +57 -0
- data/test/test_dzone.rb +23 -18
- data/test/test_facebook.rb +40 -0
- data/test/test_faves.rb +18 -20
- data/test/test_helper.rb +8 -1
- data/test/test_newsvine.rb +3 -3
- data/test/test_page.rb +21 -0
- data/test/test_post.rb +55 -0
- data/test/test_propeller.rb +2 -2
- data/test/test_reddit.rb +26 -4
- data/test/test_stumble_upon.rb +23 -3
- data/test/test_twitter.rb +33 -0
- data/test/test_user.rb +22 -0
- metadata +199 -24
- data/lib/ext.rb +0 -37
- data/lib/ubiquitously/base.rb +0 -56
- data/lib/ubiquitously/dzone.rb +0 -111
- data/lib/ubiquitously/faves.rb +0 -43
- data/lib/ubiquitously/mister_wong.rb +0 -11
- data/lib/ubiquitously/mixins/resourceful.rb +0 -50
- data/lib/ubiquitously/mixx.rb +0 -66
- data/lib/ubiquitously/newsvine.rb +0 -54
- data/lib/ubiquitously/reddit.rb +0 -46
- data/lib/ubiquitously/sphinn.rb +0 -11
- data/lib/ubiquitously/stumble_upon.rb +0 -61
- data/test/config.yml +0 -18
@@ -0,0 +1,108 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Command
|
3
|
+
class Post < Ubiquitously::Command::Base
|
4
|
+
|
5
|
+
def run
|
6
|
+
user = Ubiquitously::User.new(:storage => main_folder)
|
7
|
+
user.accountables(services) do |account|
|
8
|
+
account = account.new(
|
9
|
+
:user => user,
|
10
|
+
:username => attributes[:username] || Ubiquitously.key("#{account.service}.key"),
|
11
|
+
:password => attributes[:password] || Ubiquitously.key("#{account.service}.secret")
|
12
|
+
)
|
13
|
+
end
|
14
|
+
Ubiquitously::Post.new(attributes.merge(:user => user)).save(services)
|
15
|
+
end
|
16
|
+
|
17
|
+
def required(attributes)
|
18
|
+
missing = %w(description).delete_if { |i| !attributes[i.to_sym].blank? }
|
19
|
+
unless missing.blank?
|
20
|
+
raise CommandInvalid.new("Missing arguments for post: #{missing.join(", ")}")
|
21
|
+
end
|
22
|
+
attributes
|
23
|
+
end
|
24
|
+
|
25
|
+
def parse_options(args, attributes = {})
|
26
|
+
|
27
|
+
option_parser = OptionParser.new do |o|
|
28
|
+
o.extend Ubiquitously::Command::Post::Opts
|
29
|
+
o.options(attributes)
|
30
|
+
|
31
|
+
o.banner = "Usage: u.me [command] [service(s)] [options]\n" +
|
32
|
+
"\n" +
|
33
|
+
"Supported Commands:\n#{Ubiquitously.services.sort.join(', ')}"
|
34
|
+
|
35
|
+
o.section "Post attributes:" do
|
36
|
+
url
|
37
|
+
title
|
38
|
+
description
|
39
|
+
tags
|
40
|
+
username
|
41
|
+
password
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
option_parser.parse!(args)
|
46
|
+
|
47
|
+
required attributes
|
48
|
+
end
|
49
|
+
|
50
|
+
module Opts
|
51
|
+
|
52
|
+
def options(value = nil)
|
53
|
+
@options = value if value
|
54
|
+
@options
|
55
|
+
end
|
56
|
+
|
57
|
+
def section(heading, &block)
|
58
|
+
separator ""
|
59
|
+
separator heading
|
60
|
+
|
61
|
+
instance_eval(&block)
|
62
|
+
end
|
63
|
+
|
64
|
+
def url
|
65
|
+
on('-u', '--url URL', "Post url (required)") do |url|
|
66
|
+
options[:url] = url
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def title
|
71
|
+
on('-l', '--title TITLE', "Post title (required)") do |title|
|
72
|
+
options[:title] = title
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def tags
|
77
|
+
on('-t', '--tags TAGS', "Post tags (required)") do |tags|
|
78
|
+
options[:tags] = tags.split(/,(?:\s+)?/)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def description
|
83
|
+
on('-d', '--description DESCRIPTION', "Post description (required)") do |description|
|
84
|
+
options[:description] = description
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def username
|
89
|
+
on('-U', '--username [username]', "Username for service") do |username|
|
90
|
+
options[:username] = username
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def password
|
95
|
+
on('-P', '--password [password]', "Password for service") do |password|
|
96
|
+
options[:password] = password
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def help
|
101
|
+
on_tail("-h", "--help", "Show this message") do
|
102
|
+
exit
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Command
|
3
|
+
class User < Ubiquitously::Command::Base
|
4
|
+
|
5
|
+
def run
|
6
|
+
Ubiquitously::User.new(attributes).login(services)
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse_options(args)
|
10
|
+
option_parser = OptionParser.new do |o|
|
11
|
+
o.extend Ubiquitously::Opts
|
12
|
+
|
13
|
+
o.banner = "Usage: u.me [command] [service(s)] [options]\n" +
|
14
|
+
"\n" +
|
15
|
+
"Supported Commands:\n#{SUPPORTED_COMMANDS.sort.join(', ')}"
|
16
|
+
|
17
|
+
o.section "Getting started:" do
|
18
|
+
tutorial
|
19
|
+
end
|
20
|
+
|
21
|
+
o.section "Authorization options:" do
|
22
|
+
username
|
23
|
+
password
|
24
|
+
consumer_key
|
25
|
+
consumer_secret
|
26
|
+
access_token
|
27
|
+
password
|
28
|
+
end
|
29
|
+
|
30
|
+
o.section "Common options:" do
|
31
|
+
trace
|
32
|
+
data
|
33
|
+
host
|
34
|
+
quiet
|
35
|
+
disable_ssl
|
36
|
+
request_method
|
37
|
+
help
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
option_parser.parse!(args)
|
42
|
+
end
|
43
|
+
|
44
|
+
module Opts
|
45
|
+
|
46
|
+
def section(heading, &block)
|
47
|
+
separator ""
|
48
|
+
separator heading
|
49
|
+
|
50
|
+
instance_eval(&block)
|
51
|
+
end
|
52
|
+
|
53
|
+
def tutorial
|
54
|
+
on('-T', '--tutorial', "Narrative overview of how to get started using Ubiquitously.me") do
|
55
|
+
CLI.puts IO.read("README.markdown")
|
56
|
+
exit
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def consumer_key
|
61
|
+
on('-c', '--consumer-key [key]', "Your consumer key (required)") do |key|
|
62
|
+
options.consumer_key = key ? key : CLI.prompt_for('Consumer key')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def consumer_secret
|
67
|
+
on('-s', '--consumer-secret [secret]', "Your consumer secret (required)") do |secret|
|
68
|
+
options.consumer_secret = secret ? secret : CLI.prompt_for('Consumer secret')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def username
|
73
|
+
on('-u', '--username [name]', 'Your access token') do |name|
|
74
|
+
options.username = name
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def password
|
79
|
+
on('-p', '--password', "Your token secret") do |secret|
|
80
|
+
options.password = secret
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def username
|
85
|
+
on('-u', '--username [username]', 'Username of account to authorize (required)') do |username|
|
86
|
+
options.username = username
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def password
|
91
|
+
on('-p', '--password [password]', 'Password of account to authorize (required)') do |password|
|
92
|
+
options.password = password ? password : CLI.prompt_for('Password')
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def quiet
|
97
|
+
on('-q', '--quiet', 'Suppress all output (default: output is printed to STDOUT)') do |quiet|
|
98
|
+
options.output = StringIO.new
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def help
|
103
|
+
on_tail("-h", "--help", "Show this message") do
|
104
|
+
CLI.puts self
|
105
|
+
exit
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_model'
|
3
|
+
|
4
|
+
# use this if you want to create before and after filters around a commonly used
|
5
|
+
# method, but still want to define the method in subclasses.
|
6
|
+
module SubclassableCallbacks
|
7
|
+
def self.included(base)
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
attr_accessor :subclassable_callbacks
|
13
|
+
|
14
|
+
def subclassable_callbacks(*methods)
|
15
|
+
@subclassable_callbacks ||= []
|
16
|
+
|
17
|
+
unless methods.empty?
|
18
|
+
@subclassable_callbacks = @subclassable_callbacks.concat(methods).flatten.compact.map(&:to_sym).uniq
|
19
|
+
define_model_callbacks *methods
|
20
|
+
class_eval do
|
21
|
+
methods.each do |variable|
|
22
|
+
define_method "implementation_#{variable.to_s}" do; end
|
23
|
+
define_method variable do
|
24
|
+
send("_run_#{variable.to_s}_callbacks") do
|
25
|
+
send("implementation_#{variable.to_s}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
alias_method "superclass_#{variable.to_s}", variable
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
@subclassable_callbacks
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# ObjectSpace.each_object(Class).to_a for all of them
|
38
|
+
def self.override(*classes)
|
39
|
+
classes = ObjectSpace.each_object(Class).to_a if classes.blank?
|
40
|
+
classes.flatten.each do |object|
|
41
|
+
if object.ancestors.include?(SubclassableCallbacks)
|
42
|
+
object.class_eval do
|
43
|
+
methods = object.subclassable_callbacks
|
44
|
+
last_index = object.superclass.ancestors.index(SubclassableCallbacks)
|
45
|
+
if last_index && last_index > 0
|
46
|
+
superclasses = object.superclass.ancestors[0..last_index - 1]
|
47
|
+
superclass_methods = superclasses.select do |clazz|
|
48
|
+
clazz.respond_to?(:subclassable_callbacks)
|
49
|
+
end.map(&:subclassable_callbacks)
|
50
|
+
methods = methods.concat(superclass_methods).flatten.uniq
|
51
|
+
end
|
52
|
+
methods.each do |name|
|
53
|
+
next unless object.overrides?(name)
|
54
|
+
alias_method "subclass_#{name}", "#{name}"
|
55
|
+
alias_method "implementation_#{name}", "subclass_#{name}"
|
56
|
+
alias_method "#{name}", "superclass_#{name}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# thanks to the answer here:
|
2
|
+
# http://stackoverflow.com/questions/3488203/how-do-i-see-where-in-the-class-hierarchy-a-method-was-defined-and-overridden-in
|
3
|
+
class Object
|
4
|
+
def self.overridden_methods(parent_class = Object, within_tree = true)
|
5
|
+
if within_tree
|
6
|
+
defined_methods = ancestors[0..ancestors.index(parent_class) - 1].map { |object| object.instance_methods(false) }.flatten.uniq
|
7
|
+
parent_methods = superclass.instance_methods
|
8
|
+
else
|
9
|
+
defined_methods = instance_methods(false)
|
10
|
+
parent_methods = parent_class.instance_methods
|
11
|
+
end
|
12
|
+
defined_methods & parent_methods
|
13
|
+
end
|
14
|
+
|
15
|
+
def overridden_methods(parent_class = Object, within_tree = true)
|
16
|
+
self.class.overridden_methods(parent_class, within_tree)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.overrides?(method, parent_class = Object, within_tree = true)
|
20
|
+
overridden_methods(parent_class, within_tree).include?(method.to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
def overrides?(method, parent_class = Object, within_tree = true)
|
24
|
+
self.class.overrides?(method, parent_class, within_tree)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Hash
|
29
|
+
def recursively_symbolize_keys!
|
30
|
+
self.symbolize_keys!
|
31
|
+
self.values.each do |v|
|
32
|
+
if v.is_a? Hash
|
33
|
+
v.recursively_symbolize_keys!
|
34
|
+
elsif v.is_a? Array
|
35
|
+
v.recursively_symbolize_keys!
|
36
|
+
end
|
37
|
+
end
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def symbolize_keys
|
42
|
+
inject({}) do |options, (key, value)|
|
43
|
+
options[(key.to_sym rescue key) || key] = value
|
44
|
+
options
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Destructively convert all keys to symbols.
|
49
|
+
def symbolize_keys!
|
50
|
+
self.replace(self.symbolize_keys)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
class Array
|
55
|
+
def chop(separator = "", max = 40, &block)
|
56
|
+
return self if self.join(separator).length < max # opt.
|
57
|
+
result = []
|
58
|
+
self.each do |word|
|
59
|
+
break if (result.join(separator).length + word.length > max)
|
60
|
+
word = yield word if block_given?
|
61
|
+
result << word
|
62
|
+
result
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
66
|
+
|
67
|
+
# separator = ", ",
|
68
|
+
# max = 10_000
|
69
|
+
# quote = true|false
|
70
|
+
def taggify(space = " ", separator = ", ", max = 10_000)
|
71
|
+
quoted = !(separator =~ /\s+/).nil?
|
72
|
+
chop(separator, max) do |word|
|
73
|
+
result = word.downcase.strip.gsub(/[^a-z0-9\.]/, space).squeeze(space)
|
74
|
+
result = "\"#{result}\"" if quoted && !(result =~ /\s+/).nil?
|
75
|
+
result
|
76
|
+
end.join(separator)
|
77
|
+
end
|
78
|
+
|
79
|
+
def recursively_symbolize_keys!
|
80
|
+
self.each do |item|
|
81
|
+
if item.is_a? Hash
|
82
|
+
item.recursively_symbolize_keys!
|
83
|
+
elsif item.is_a? Array
|
84
|
+
item.recursively_symbolize_keys!
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class NilClass; def search(*args); []; end; end
|
2
|
+
|
3
|
+
Mechanize::Chain::PostConnectHook.class_eval do
|
4
|
+
|
5
|
+
def handle(ctx, params)
|
6
|
+
headers = params[:response].to_hash
|
7
|
+
if headers.has_key?("location")
|
8
|
+
headers["location"].each do |location|
|
9
|
+
url = URI.parse(location)
|
10
|
+
# we want mechanize to return if it sees a redirect back to localhost for facebook
|
11
|
+
if url.host == "localhost" && url.port == 4567
|
12
|
+
params[:res_klass] = Net::HTTPSuccess
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
super(ctx, params)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class Mechanize::CookieJar
|
23
|
+
public
|
24
|
+
def jar=(hash)
|
25
|
+
@jar = hash
|
26
|
+
end
|
27
|
+
|
28
|
+
def save_string
|
29
|
+
return YAML::dump(@jar)
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_string(str)
|
33
|
+
@jar = YAML::load(str)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
class Base
|
3
|
+
include ActiveModel::Validations
|
4
|
+
extend ActiveModel::Callbacks
|
5
|
+
include ActiveModel::Serialization
|
6
|
+
include SubclassableCallbacks
|
7
|
+
|
8
|
+
def apply(attributes)
|
9
|
+
attributes.each do |key, value|
|
10
|
+
self.send("#{key.to_s}=", value) if self.respond_to?(key)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def debug?
|
15
|
+
Ubiquitously.debug?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
class Page < Base
|
3
|
+
attr_accessor :url, :title, :description, :tags, :image
|
4
|
+
|
5
|
+
def initialize(attributes = {})
|
6
|
+
apply attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
def parse
|
10
|
+
html = Nokogiri::HTML(open(url).read)
|
11
|
+
|
12
|
+
self.title = html.xpath("//title").first.text.to_s.strip
|
13
|
+
|
14
|
+
self.description = html.xpath("//meta[@name='description']").first["content"] rescue ""
|
15
|
+
self.description.strip!
|
16
|
+
|
17
|
+
self.tags = html.xpath("//meta[@name='keywords']").first["content"] rescue ""
|
18
|
+
self.tags = self.tags.split(/,\s+/).taggify("-", ", ").split(", ")
|
19
|
+
|
20
|
+
self.image = html.xpath("//link[@rel='image_src']").first["image_src"] rescue nil
|
21
|
+
if self.image.blank?
|
22
|
+
self.image = html.xpath("//img").first["src"] rescue nil
|
23
|
+
end
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|