ubiquitously 0.0.1.5 → 0.0.1.6
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 +48 -1
- data/Rakefile +11 -1
- data/lib/ext.rb +37 -0
- data/lib/ubiquitously.rb +38 -20
- data/lib/ubiquitously/base.rb +56 -0
- data/lib/ubiquitously/dzone.rb +111 -0
- data/lib/ubiquitously/faves.rb +43 -0
- data/lib/ubiquitously/mister_wong.rb +11 -0
- data/lib/ubiquitously/mixins/resourceful.rb +50 -0
- data/lib/ubiquitously/mixx.rb +66 -0
- data/lib/ubiquitously/newsvine.rb +54 -0
- data/lib/ubiquitously/propeller.rb +69 -0
- data/lib/ubiquitously/reddit.rb +46 -0
- data/lib/ubiquitously/sphinn.rb +11 -0
- data/lib/ubiquitously/stumble_upon.rb +61 -0
- data/test/config.yml +17 -1
- data/test/test_dzone.rb +43 -0
- data/test/test_faves.rb +42 -0
- data/test/test_helper.rb +7 -1
- data/test/test_newsvine.rb +42 -0
- data/test/test_propeller.rb +43 -0
- data/test/test_reddit.rb +32 -0
- data/test/test_stumble_upon.rb +32 -0
- metadata +21 -3
data/README.markdown
CHANGED
@@ -1,3 +1,50 @@
|
|
1
1
|
# [Ubiquitously](http://ubiquitously.me/)
|
2
2
|
|
3
|
-
> Making it easy for you to be everywhere, even if there's no API
|
3
|
+
> Making it easy for you to be everywhere, even if there's no API
|
4
|
+
|
5
|
+
## Usage
|
6
|
+
|
7
|
+
### Install
|
8
|
+
|
9
|
+
sudo gem install ubiquitously
|
10
|
+
|
11
|
+
### Run Tests
|
12
|
+
|
13
|
+
Fill out `test/config.yml` with your credentials for the different services, then run
|
14
|
+
|
15
|
+
rake test
|
16
|
+
|
17
|
+
### Register for the services you haven't already
|
18
|
+
|
19
|
+
First edit the config file with a username, password, and other fields that all the systems might have. Then run this
|
20
|
+
|
21
|
+
rake ubiquitously:me
|
22
|
+
|
23
|
+
### Automatically Post to Services
|
24
|
+
|
25
|
+
require 'rubygems'
|
26
|
+
require 'ubiquitously'
|
27
|
+
|
28
|
+
# dzone
|
29
|
+
Ubiquitously::Dzone::Post.create(
|
30
|
+
:title => "A Dzone Post!",
|
31
|
+
:description => "Dzone does not let you edit or delete posts once you submit them, so be careful!",
|
32
|
+
:tags => ["dzone", "web 2.0"]
|
33
|
+
)
|
34
|
+
|
35
|
+
## How it works
|
36
|
+
|
37
|
+
Everything is built around [Mechanize](http://mechanize.rubyforge.org/mechanize/GUIDE_rdoc.html) and [Nokogiri](http://nokogiri.org/tutorials/parsing_an_html_xml_document.html), both led by [Aaron Patterson](http://tenderlovemaking.com/).
|
38
|
+
|
39
|
+
Many social bookmarking services do not have API's in order to prevent spammers from ruining their system. But what about for those of us that actually create content several times a day and want automation? We're out of luck.
|
40
|
+
|
41
|
+
So Ubiquitously creates a simple, RESTful API around some services I need to publish to now, and hopefully it will grow as you guys need more. The goal is to be semi-low-level and not to provide a full featured api to a service, as some services already have very well-done API's in Ruby.
|
42
|
+
|
43
|
+
## Other Possible Services (and Resources)
|
44
|
+
|
45
|
+
- [http://www.iwoodpecker.com/collection-of-70-best-social-bookmarking-sites-with-pr-and-alexa/](http://www.iwoodpecker.com/collection-of-70-best-social-bookmarking-sites-with-pr-and-alexa/)
|
46
|
+
- buzz.yahoo.com
|
47
|
+
- http://www.wikio.com/about-us
|
48
|
+
- http://designbump.com/
|
49
|
+
- http://scriptandstyle.com/submit
|
50
|
+
- http://www.stumpedia.com/submitlink.php
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
|
|
5
5
|
# http://docs.rubygems.org/read/chapter/20
|
6
6
|
spec = Gem::Specification.new do |s|
|
7
7
|
s.name = "ubiquitously"
|
8
|
-
s.version = "0.0.1.
|
8
|
+
s.version = "0.0.1.6"
|
9
9
|
s.author = "Lance Pollard"
|
10
10
|
s.summary = "Ubiquitously: Making it easy for you to be everywhere, even if there's no API"
|
11
11
|
s.homepage = "http://github.com/viatropos/ubiquitously"
|
@@ -66,3 +66,13 @@ end
|
|
66
66
|
task :yank do
|
67
67
|
`gem yank #{spec.name} -v #{spec.version}`
|
68
68
|
end
|
69
|
+
|
70
|
+
desc 'run unit tests'
|
71
|
+
task :test do
|
72
|
+
Dir["test/**/*"].each do |file|
|
73
|
+
next unless File.extname(file) == ".rb"
|
74
|
+
next unless File.basename(file) =~ /test_/
|
75
|
+
next if File.basename(file) =~ /test_helper/
|
76
|
+
require file
|
77
|
+
end
|
78
|
+
end
|
data/lib/ext.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
class Hash
|
2
|
+
def recursively_symbolize_keys!
|
3
|
+
self.symbolize_keys!
|
4
|
+
self.values.each do |v|
|
5
|
+
if v.is_a? Hash
|
6
|
+
v.recursively_symbolize_keys!
|
7
|
+
elsif v.is_a? Array
|
8
|
+
v.recursively_symbolize_keys!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
def symbolize_keys
|
15
|
+
inject({}) do |options, (key, value)|
|
16
|
+
options[(key.to_sym rescue key) || key] = value
|
17
|
+
options
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Destructively convert all keys to symbols.
|
22
|
+
def symbolize_keys!
|
23
|
+
self.replace(self.symbolize_keys)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Array
|
28
|
+
def recursively_symbolize_keys!
|
29
|
+
self.each do |item|
|
30
|
+
if item.is_a? Hash
|
31
|
+
item.recursively_symbolize_keys!
|
32
|
+
elsif item.is_a? Array
|
33
|
+
item.recursively_symbolize_keys!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/ubiquitously.rb
CHANGED
@@ -1,28 +1,46 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'open-uri'
|
3
|
+
require 'cgi'
|
2
4
|
require 'yaml'
|
5
|
+
require 'json'
|
6
|
+
require 'nokogiri'
|
7
|
+
require 'mechanize'
|
8
|
+
require 'highline/import'
|
9
|
+
require 'logger'
|
3
10
|
require 'active_support'
|
11
|
+
require 'active_model'
|
4
12
|
|
5
13
|
this = File.dirname(__FILE__)
|
6
|
-
require
|
14
|
+
require "#{this}/ext"
|
15
|
+
Dir["#{this}/ubiquitously/mixins/*"].each { |c| require c }
|
16
|
+
require "#{this}/ubiquitously/base"
|
7
17
|
|
8
|
-
module
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
:stylesheet => :css,
|
13
|
-
:js => :javascript,
|
14
|
-
:javascript => :js
|
15
|
-
}
|
16
|
-
|
17
|
-
end
|
18
|
-
|
19
|
-
Dir["#{this}/compressible/*"].each { |c| require c }
|
18
|
+
module Ubiquitously
|
19
|
+
class SettingsError < StandardError; end
|
20
|
+
class AuthenticationError < StandardError; end
|
21
|
+
class DuplicateError < StandardError; end
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
class << self
|
24
|
+
attr_accessor :config
|
25
|
+
|
26
|
+
def configure(value)
|
27
|
+
self.config = value.is_a?(String) ? YAML.load_file(value) : value
|
28
|
+
end
|
29
|
+
|
30
|
+
def key(path)
|
31
|
+
result = self.config
|
32
|
+
path.to_s.split(".").each { |node| result = result[node.to_s] if result }
|
33
|
+
result.to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
def credentials(service)
|
37
|
+
result = key(service)
|
38
|
+
unless result && result.has_key?("key") && result.has_key?("secret")
|
39
|
+
raise SettingsError.new("Please specify both a key and secret for ':#{service}'")
|
40
|
+
end
|
41
|
+
result
|
42
|
+
end
|
43
|
+
end
|
28
44
|
end
|
45
|
+
|
46
|
+
Dir["#{this}/ubiquitously/*"].each { |c| require c unless File.directory?(c) }
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Base
|
3
|
+
class User
|
4
|
+
include ActiveModel::Validations
|
5
|
+
include ActiveModel::Serialization
|
6
|
+
include Ubiquitously::Resourceful
|
7
|
+
|
8
|
+
attr_accessor :agent, :username, :password
|
9
|
+
|
10
|
+
validates_presence_of :username, :password
|
11
|
+
|
12
|
+
def initialize(attributes = {})
|
13
|
+
attributes = attributes.symbolize_keys
|
14
|
+
|
15
|
+
attributes[:username] ||= Ubiquitously.key("#{service_name}.key")
|
16
|
+
attributes[:password] ||= Ubiquitously.key("#{service_name}.secret")
|
17
|
+
|
18
|
+
@logged_in = false
|
19
|
+
|
20
|
+
super(attributes)
|
21
|
+
|
22
|
+
self.agent = Mechanize.new
|
23
|
+
# self.agent.log = Logger.new(STDOUT)
|
24
|
+
self.agent.user_agent = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; ru-ru) AppleWebKit/533.2+ (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10"
|
25
|
+
end
|
26
|
+
|
27
|
+
def logged_in?
|
28
|
+
@logged_in == true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Post
|
33
|
+
include ActiveModel::Validations
|
34
|
+
include ActiveModel::Serialization
|
35
|
+
include Ubiquitously::Resourceful
|
36
|
+
|
37
|
+
attr_accessor :title, :url, :description, :tags, :categories, :service_url, :user
|
38
|
+
|
39
|
+
def user
|
40
|
+
@user ||= "Ubiquitously::#{service_name.titleize}::User".constantize.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def agent
|
44
|
+
user.agent
|
45
|
+
end
|
46
|
+
|
47
|
+
class << self
|
48
|
+
def create(options = {})
|
49
|
+
record = new(options)
|
50
|
+
record.save
|
51
|
+
record
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Dzone
|
3
|
+
class User < Ubiquitously::Base::User
|
4
|
+
def login
|
5
|
+
return true if logged_in?
|
6
|
+
|
7
|
+
page = agent.get("http://www.dzone.com/links/loginLightbox.html")
|
8
|
+
form = page.form_with(:action => "/links/j_acegi_security_check")
|
9
|
+
form["j_username"] = username
|
10
|
+
form["j_password"] = password
|
11
|
+
page = form.submit
|
12
|
+
|
13
|
+
@logged_in = (page.body !~ /Invalid username or password/i).nil?
|
14
|
+
|
15
|
+
unless @logged_in
|
16
|
+
raise AuthenticationError.new("Invalid username or password for #{service_name.titleize}")
|
17
|
+
end
|
18
|
+
|
19
|
+
@logged_in
|
20
|
+
end
|
21
|
+
|
22
|
+
def create
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Post < Ubiquitously::Base::Post
|
28
|
+
validates_presence_of :url, :title, :description, :service_url
|
29
|
+
|
30
|
+
class << self
|
31
|
+
|
32
|
+
def first(url)
|
33
|
+
find(url).first
|
34
|
+
end
|
35
|
+
|
36
|
+
def last(url)
|
37
|
+
find(url).last
|
38
|
+
end
|
39
|
+
|
40
|
+
def find(url)
|
41
|
+
query = CGI.escape(url.gsub(/http(s)?\:\/\//, ""))
|
42
|
+
search = "http://www.dzone.com/links/search.html?query=#{query}"
|
43
|
+
html = Nokogiri::HTML(open(search).read)
|
44
|
+
records = []
|
45
|
+
|
46
|
+
html.css(".linkblock").each do |node|
|
47
|
+
link = node.css(".thumb a").first["href"]
|
48
|
+
header = node.css(".details h3 a").first
|
49
|
+
title = header.text
|
50
|
+
service_url = header["href"]
|
51
|
+
records << Ubiquitously::Dzone::Post.new(
|
52
|
+
:url => link,
|
53
|
+
:title => title,
|
54
|
+
:service_url => service_url
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
records
|
59
|
+
end
|
60
|
+
|
61
|
+
# http://www.dzone.com/links/feed/user/<user_id>/rss.xml
|
62
|
+
# check to see if dzone already has the url
|
63
|
+
def new_record?(url, throw_error = false)
|
64
|
+
exists = !find(url).blank?
|
65
|
+
raise DuplicateError.new("DZone already links to #{url}") if throw_error && exists
|
66
|
+
exists
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def save(options = {})
|
71
|
+
return false unless valid? && new_record?
|
72
|
+
|
73
|
+
user.login
|
74
|
+
|
75
|
+
# url
|
76
|
+
page = agent.get("http://www.dzone.com/links/add.html")
|
77
|
+
form = page.form_with(:action => "/links/add.html")
|
78
|
+
|
79
|
+
accepted_tags = []
|
80
|
+
|
81
|
+
form.checkboxes_with(:name => "tags").each do |tag|
|
82
|
+
accepted_tags << tag.value.to_s
|
83
|
+
end
|
84
|
+
|
85
|
+
unaccepted_tags = tags.select { |tag| !accepted_tags.include?(tag) }
|
86
|
+
|
87
|
+
if unaccepted_tags.length > 0
|
88
|
+
raise ArgumentError.new("DZone doesn't accept these tags: #{unaccepted_tags.inspect}, they want these:\n#{accepted_tags.inspect}")
|
89
|
+
end
|
90
|
+
|
91
|
+
form["title"] = title
|
92
|
+
form["url"] = url
|
93
|
+
form["description"] = description
|
94
|
+
|
95
|
+
form.checkboxes_with(:name => "tags").each do |checkbox|
|
96
|
+
checkbox.check if tags.include?(checkbox.value)
|
97
|
+
end
|
98
|
+
|
99
|
+
unless options[:debug] == true
|
100
|
+
page = form.submit
|
101
|
+
end
|
102
|
+
|
103
|
+
true
|
104
|
+
end
|
105
|
+
|
106
|
+
def new_record?
|
107
|
+
self.class.new_record?(url)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Faves
|
3
|
+
class User < Ubiquitously::Base::User
|
4
|
+
def login
|
5
|
+
page = agent.get("https://secure.faves.com/signIn")
|
6
|
+
form = page.forms.detect {|form| form.form_node["id"] == "signInBox"}
|
7
|
+
form["rUsername"] = username
|
8
|
+
form["rPassword"] = password
|
9
|
+
page = form.submit
|
10
|
+
|
11
|
+
@logged_in = (page.title =~ /Sign In/i).nil?
|
12
|
+
|
13
|
+
unless @logged_in
|
14
|
+
raise AuthenticationError.new("Invalid username or password for #{service_name.titleize}")
|
15
|
+
end
|
16
|
+
|
17
|
+
@logged_in
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Post < Ubiquitously::Base::Post
|
22
|
+
validates_presence_of :url, :title, :description, :tags
|
23
|
+
|
24
|
+
def save
|
25
|
+
return false unless valid?
|
26
|
+
|
27
|
+
user.login
|
28
|
+
|
29
|
+
page = agent.get("http://faves.com/createdot.aspx")
|
30
|
+
form = page.form_with(:name => "createDotForm")
|
31
|
+
form["noteText"] = description
|
32
|
+
form["urlText"] = url
|
33
|
+
form["subjectText"] = title
|
34
|
+
form["tagsText"] = tags.join(", ")
|
35
|
+
#form["rateSelect"]
|
36
|
+
|
37
|
+
unless options[:debug] == true
|
38
|
+
page = form.submit
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Resourceful
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def service_name
|
10
|
+
self.to_s.split("::")[1].underscore.downcase
|
11
|
+
end
|
12
|
+
|
13
|
+
def required_arguments(*args)
|
14
|
+
@required_arguments = args unless args.blank?
|
15
|
+
@required_arguments ||= []
|
16
|
+
@required_arguments
|
17
|
+
end
|
18
|
+
alias requires required_arguments
|
19
|
+
|
20
|
+
def assert_required_arguments(options)
|
21
|
+
missing = []
|
22
|
+
required_arguments.each do |arg|
|
23
|
+
missing << arg unless options.has_key?(arg)
|
24
|
+
end
|
25
|
+
unless missing.blank?
|
26
|
+
raise ArgumentError.new("#{service_name.titleize} requires #{missing.join(", and")}")
|
27
|
+
end
|
28
|
+
|
29
|
+
missing.blank?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module InstanceMethods
|
34
|
+
|
35
|
+
def initialize(attributes = {})
|
36
|
+
attributes.each do |key, value|
|
37
|
+
self.send("#{key.to_s}=", value) if self.respond_to?(key)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def service_name
|
42
|
+
self.class.service_name
|
43
|
+
end
|
44
|
+
|
45
|
+
def assert_required_arguments(options)
|
46
|
+
self.class.assert_required_arguments(options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Mixx
|
3
|
+
class User < Ubiquitously::Base::User
|
4
|
+
def login
|
5
|
+
return true if logged_in?
|
6
|
+
|
7
|
+
page = agent.get("https://www.mixx.com/")
|
8
|
+
form = page.form_with(:action => "https://www.mixx.com/save_login")
|
9
|
+
form["user[loginid]"] = username
|
10
|
+
form["user[password]"] = password
|
11
|
+
page = form.submit
|
12
|
+
|
13
|
+
@logged_in = (page.body =~ /login was unsuccessful/i).nil?
|
14
|
+
|
15
|
+
unless @logged_in
|
16
|
+
raise AuthenticationError.new("Invalid username or password for #{service_name.titleize}")
|
17
|
+
end
|
18
|
+
|
19
|
+
@logged_in
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Post < Ubiquitously::Base::Post
|
24
|
+
def save
|
25
|
+
return false unless valid?
|
26
|
+
|
27
|
+
user.login
|
28
|
+
|
29
|
+
page = agent.get("http://www.mixx.com/submit")
|
30
|
+
form = page.form_with(:action => "http://www.mixx.com/submit/step2")
|
31
|
+
form["thingy[page_url]"] = url
|
32
|
+
page = form.submit
|
33
|
+
|
34
|
+
# last page
|
35
|
+
form = page.form_with(:action => "http://www.mixx.com/submit/save")
|
36
|
+
|
37
|
+
# captcha
|
38
|
+
iframe_url = page.parser.css("li.captcha iframe").first["src"]
|
39
|
+
params = iframe_url.split("?").last
|
40
|
+
captcha_iframe = agent.click(page.iframes.first)
|
41
|
+
captcha_form = captcha_iframe.forms.first
|
42
|
+
captcha_image = captcha_iframe.parser.css("img").first["src"]
|
43
|
+
# open browser with captcha image
|
44
|
+
system("open", "http://api.recaptcha.net/#{captcha_image}")
|
45
|
+
# enter captcha response in terminal
|
46
|
+
captcha_says = ask("Enter Captcha from Browser Image: ") { |q| q.echo = true }
|
47
|
+
captcha_form["recaptcha_response_field"] = captcha_says
|
48
|
+
# submit captcha
|
49
|
+
captcha_form.action = "http://www.google.com/recaptcha/api/noscript?#{params}"
|
50
|
+
captcha_response = captcha_form.submit
|
51
|
+
# grab secret
|
52
|
+
captcha_response = captcha_response.parser.css("textarea").first.text
|
53
|
+
|
54
|
+
# submit post
|
55
|
+
form = page.form_with(:action => "http://www.mixx.com/submit/save")
|
56
|
+
form["thingy[title]"] = title
|
57
|
+
form["thingy[description]"] = description if description
|
58
|
+
form["thingy[new_tags]"] = tags if tags
|
59
|
+
form["recaptcha_challenge_field"] = captcha_response
|
60
|
+
form["recaptcha_response_field"] = captcha_says
|
61
|
+
|
62
|
+
form.submit
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Newsvine
|
3
|
+
class User < Ubiquitously::Base::User
|
4
|
+
def login
|
5
|
+
return true if logged_in?
|
6
|
+
|
7
|
+
page = agent.get("https://www.newsvine.com/_nv/accounts/login")
|
8
|
+
form = page.form_with(:action => "https://www.newsvine.com/_nv/api/accounts/login")
|
9
|
+
form["email"] = username
|
10
|
+
form["password"] = password
|
11
|
+
page = form.submit
|
12
|
+
|
13
|
+
# No match. Please try again
|
14
|
+
@logged_in = (page.title =~ /Log in/i).nil?
|
15
|
+
|
16
|
+
unless @logged_in
|
17
|
+
raise AuthenticationError.new("Invalid username or password for #{service_name.titleize}")
|
18
|
+
end
|
19
|
+
|
20
|
+
@logged_in
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Post < Ubiquitously::Base::Post
|
25
|
+
validates_presence_of :url, :title
|
26
|
+
|
27
|
+
def save(options = {})
|
28
|
+
return false unless valid?
|
29
|
+
|
30
|
+
user.login
|
31
|
+
|
32
|
+
page = agent.get("http://www.newsvine.com/_tools/seed")
|
33
|
+
form = page.form_with(:action => "http://www.newsvine.com/_action/tools/saveLink")
|
34
|
+
|
35
|
+
form["url"] = url
|
36
|
+
form["headline"] = title
|
37
|
+
form.radiobuttons_with(:name => "newsType").each do |button|
|
38
|
+
button.check if button.value == "x"
|
39
|
+
end
|
40
|
+
form.field_with(:name => "categoryTag").options.each do |option|
|
41
|
+
option.select if option.value == "technology"
|
42
|
+
end
|
43
|
+
form["blurb"] = description
|
44
|
+
form["tags"] = tags.join(", ")
|
45
|
+
|
46
|
+
unless options[:debug] == true
|
47
|
+
page = form.submit
|
48
|
+
end
|
49
|
+
|
50
|
+
true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Propeller
|
3
|
+
class User < Ubiquitously::Base::User
|
4
|
+
def login
|
5
|
+
puts "LOGGING IN"
|
6
|
+
page = agent.get("http://www.propeller.com/signin/")
|
7
|
+
form = page.forms.detect {|form| form.form_node["class"] == "ajax-form"}
|
8
|
+
form["member_name"] = username
|
9
|
+
form["password"] = password
|
10
|
+
form["submit"] = "sign in" # require to get around the ajax
|
11
|
+
page = form.submit
|
12
|
+
puts "DONE"
|
13
|
+
@logged_in = (page.body =~ /Invalid member name or password/i).nil? && (page.body =~ /ajax-form/).nil?
|
14
|
+
|
15
|
+
unless @logged_in
|
16
|
+
raise AuthenticationError.new("Invalid username or password for #{service_name.titleize}")
|
17
|
+
end
|
18
|
+
|
19
|
+
@logged_in
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Post < Ubiquitously::Base::Post
|
24
|
+
# title max == 150
|
25
|
+
validates_presence_of :url, :title, :tags, :description
|
26
|
+
|
27
|
+
def save
|
28
|
+
return false unless valid?
|
29
|
+
|
30
|
+
user.login
|
31
|
+
|
32
|
+
# url
|
33
|
+
page = agent.get("http://www.propeller.com/story/submit/")
|
34
|
+
form = page.forms.detect {|form| form.form_node["method"].downcase == "post"}
|
35
|
+
form["url"] = url
|
36
|
+
puts "SUBMIT FORM"
|
37
|
+
# details
|
38
|
+
# http://www.propeller.com/story/submit/content/
|
39
|
+
page = form.submit
|
40
|
+
form = page.forms.detect {|form| form.form_node["method"].downcase == "post"}
|
41
|
+
puts "DETAILS"
|
42
|
+
form.radiobuttons_with(:name => "title_multi").each do |button|
|
43
|
+
button.check if button.value == "-1"
|
44
|
+
end
|
45
|
+
|
46
|
+
form["title_multi_text"] = title
|
47
|
+
form["description"] = description
|
48
|
+
# separate by space, multi words are underscored
|
49
|
+
form["user_tags"] = tags.map {|tag| tag.underscore.gsub(/[\s|-]+/, "_")}.join(" ")
|
50
|
+
|
51
|
+
form.radiobuttons_with(:name => "category").each do |button|
|
52
|
+
button.check if button.value.to_s == "18"
|
53
|
+
end
|
54
|
+
|
55
|
+
# http://www.propeller.com/story/submit/preview/
|
56
|
+
page = form.submit
|
57
|
+
puts "SUBMIT DETAILS"
|
58
|
+
# approve
|
59
|
+
page.forms[1].submit
|
60
|
+
|
61
|
+
unless options[:debug] == true
|
62
|
+
page = form.submit
|
63
|
+
end
|
64
|
+
|
65
|
+
true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module Reddit
|
3
|
+
class User < Ubiquitously::Base::User
|
4
|
+
def login
|
5
|
+
return true if logged_in?
|
6
|
+
|
7
|
+
page = agent.get("http://www.reddit.com/")
|
8
|
+
form = page.form_with(:action => "http://www.reddit.com/post/login")
|
9
|
+
form["user"] = username
|
10
|
+
form["passwd"] = password
|
11
|
+
page = form.submit
|
12
|
+
|
13
|
+
@logged_in = (page.title =~ /login or register/i).nil?
|
14
|
+
|
15
|
+
unless @logged_in
|
16
|
+
raise AuthenticationError.new("Invalid username or password for #{service_name.titleize}")
|
17
|
+
end
|
18
|
+
|
19
|
+
@logged_in
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Post < Ubiquitously::Base::Post
|
24
|
+
validates_presence_of :url, :title
|
25
|
+
|
26
|
+
def save(options = {})
|
27
|
+
return false unless valid?
|
28
|
+
|
29
|
+
user.login
|
30
|
+
|
31
|
+
page = agent.get("http://www.reddit.com/submit")
|
32
|
+
form = page.forms.detect {|form| form.form_node["id"] == "newlink"}
|
33
|
+
form["title"] = title
|
34
|
+
form["url"] = url
|
35
|
+
form["id"] = "#newlink"
|
36
|
+
form.action = "http://www.reddit.com/api/submit"
|
37
|
+
headers = {
|
38
|
+
"Content-Type" => "application/json"
|
39
|
+
}
|
40
|
+
unless options[:debug] == true
|
41
|
+
page = form.submit(nil, headers)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Ubiquitously
|
2
|
+
module StumbleUpon
|
3
|
+
class User < Ubiquitously::Base::User
|
4
|
+
def login
|
5
|
+
page = agent.get("http://www.stumbleupon.com/login.php")
|
6
|
+
form = page.form_with(:name => "formLogin")
|
7
|
+
form["username"] = username
|
8
|
+
form["password"] = password
|
9
|
+
page = form.submit
|
10
|
+
|
11
|
+
@logged_in = (page.body =~ /Invalid username/i).nil?
|
12
|
+
|
13
|
+
unless @logged_in
|
14
|
+
raise AuthenticationError.new("Invalid username or password for #{service_name.titleize}")
|
15
|
+
end
|
16
|
+
|
17
|
+
@logged_in
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Post < Ubiquitously::Base::Post
|
22
|
+
def save
|
23
|
+
return false unless valid?
|
24
|
+
|
25
|
+
user.login
|
26
|
+
|
27
|
+
page = agent.get("http://www.stumbleupon.com/submit?url=#{url}&title=#{title}")
|
28
|
+
form = page.forms.first
|
29
|
+
form["topic"] = title
|
30
|
+
form["comment"] = description
|
31
|
+
form.radiobuttons_with(:name => "sfw").first.check
|
32
|
+
page = agent.submit(form)
|
33
|
+
|
34
|
+
# add tags
|
35
|
+
page = agent.get("http://www.stumbleupon.com/favorites/")
|
36
|
+
# get key properties
|
37
|
+
token = page.parser.css("div#wrapperContent").first["class"]
|
38
|
+
var = page.parser.css("li.listLi var").first
|
39
|
+
comment_id = var["id"]
|
40
|
+
public_id = var["class"]
|
41
|
+
|
42
|
+
# post to hidden api
|
43
|
+
url = "http://www.stumbleupon.com/ajax/edit/comment"
|
44
|
+
params = {
|
45
|
+
"syndicate_fb" =>"syndicate_fb",
|
46
|
+
"title" => title,
|
47
|
+
"token" => token,
|
48
|
+
"sticky_post" => "0",
|
49
|
+
"review" => description,
|
50
|
+
"tags" => tags.join(", "),
|
51
|
+
"publicid" => public_id,
|
52
|
+
"syndicate_tw" => "syndicate_tw",
|
53
|
+
"commentid" => comment_id,
|
54
|
+
"keep_date" => "1"
|
55
|
+
}
|
56
|
+
|
57
|
+
page = agent.post(url, params)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/test/config.yml
CHANGED
@@ -1,2 +1,18 @@
|
|
1
1
|
dzone:
|
2
|
-
|
2
|
+
key: viatropos
|
3
|
+
secret: "w!sd0m[:227]"
|
4
|
+
reddit:
|
5
|
+
key: viatropos
|
6
|
+
secret: "w!sd0m[:227]"
|
7
|
+
stumble_upon:
|
8
|
+
key: viatropos
|
9
|
+
secret: "tarq1986"
|
10
|
+
faves:
|
11
|
+
key: viatropos
|
12
|
+
secret: tarq1986
|
13
|
+
newsvine:
|
14
|
+
key: viatropos
|
15
|
+
secret: tarq1986
|
16
|
+
propeller:
|
17
|
+
key: viatropos
|
18
|
+
secret: tarq1986
|
data/test/test_dzone.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
module Ubiquitously
|
4
|
+
class DzoneTest < ActiveSupport::TestCase
|
5
|
+
context "Dzone::User" do
|
6
|
+
setup do
|
7
|
+
@user = Ubiquitously::Dzone::User.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "login" do
|
11
|
+
should "raise informative error if invalid password" do
|
12
|
+
@user.password = "bad password"
|
13
|
+
assert_raises(Ubiquitously::AuthenticationError) do
|
14
|
+
@user.login
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "Dzone::Post" do
|
21
|
+
setup do
|
22
|
+
@post = Ubiquitously::Dzone::Post.new
|
23
|
+
end
|
24
|
+
|
25
|
+
should "raise error if post exists already" do
|
26
|
+
assert_raises(Ubiquitously::DuplicateError) do
|
27
|
+
Ubiquitously::Dzone::Post.new_record?("http://www.google.com", true)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
should "create a post" do
|
32
|
+
# http://www.dzone.com/links/buttons.jsp
|
33
|
+
assert Ubiquitously::Dzone::Post.create(
|
34
|
+
:debug => true,
|
35
|
+
:title => "A Title",
|
36
|
+
:description => "A Description",
|
37
|
+
:tags => ["usability", "ruby", "web services", "open source"],
|
38
|
+
:url => "http://example.com/abcdef"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/test/test_faves.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
require 'restclient'
|
3
|
+
module Ubiquitously
|
4
|
+
class FavesTest < ActiveSupport::TestCase
|
5
|
+
context "Faves::User" do
|
6
|
+
setup do
|
7
|
+
@user = Ubiquitously::Faves::User.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "login" do
|
11
|
+
should "raise informative error if invalid password" do
|
12
|
+
@user.password = "bad password"
|
13
|
+
assert_raises(Ubiquitously::AuthenticationError) do
|
14
|
+
@user.login
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
should "login successfully if valid credentials" do
|
19
|
+
assert_equal true, @user.login
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
=begin
|
24
|
+
context "Faves::Post" do
|
25
|
+
setup do
|
26
|
+
@post = Ubiquitously::Faves::Post.new(
|
27
|
+
:debug => true,
|
28
|
+
:title => "A Title",
|
29
|
+
:description => "A Description",
|
30
|
+
:tags => ["usability", "ruby", "web services", "open source"],
|
31
|
+
:url => "http://example.com/abcdef",
|
32
|
+
:user => Ubiquitously::Faves::User.new
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
should "create a post" do
|
37
|
+
assert @post.save
|
38
|
+
end
|
39
|
+
end
|
40
|
+
=end
|
41
|
+
end
|
42
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
-
require "test/unit"
|
2
1
|
require "rubygems"
|
2
|
+
require 'active_support'
|
3
3
|
require "ruby-debug"
|
4
|
+
gem 'test-unit'
|
5
|
+
require "test/unit"
|
4
6
|
require 'active_support'
|
7
|
+
require 'active_support/test_case'
|
5
8
|
require 'shoulda'
|
9
|
+
require 'rr'
|
6
10
|
|
7
11
|
require File.dirname(__FILE__) + '/../lib/ubiquitously'
|
12
|
+
|
13
|
+
Ubiquitously.configure("test/config.yml")
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
module Ubiquitously
|
4
|
+
class NewsvineTest < ActiveSupport::TestCase
|
5
|
+
context "Newsvine::User" do
|
6
|
+
setup do
|
7
|
+
@user = Ubiquitously::Newsvine::User.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "login" do
|
11
|
+
should "raise informative error if invalid password" do
|
12
|
+
@user.password = "bad password"
|
13
|
+
assert_raises(Ubiquitously::AuthenticationError) do
|
14
|
+
@user.login
|
15
|
+
end
|
16
|
+
assert_equal false, @user.logged_in?
|
17
|
+
end
|
18
|
+
|
19
|
+
should "login successfully if valid credentials" do
|
20
|
+
assert_equal true, @user.login
|
21
|
+
assert_equal true, @user.logged_in?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "Newsvine::Post" do
|
27
|
+
setup do
|
28
|
+
@post = Ubiquitously::Newsvine::Post.new(
|
29
|
+
:title => "A Title",
|
30
|
+
:description => "A Description",
|
31
|
+
:tags => ["usability", "ruby", "web services", "open source"],
|
32
|
+
:url => "http://example.com/abcdef",
|
33
|
+
:user => Ubiquitously::Newsvine::User.new
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
should "create a post" do
|
38
|
+
assert @post.save(:debug => true)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
module Ubiquitously
|
4
|
+
class PropellerTest < ActiveSupport::TestCase
|
5
|
+
context "Propeller::User" do
|
6
|
+
setup do
|
7
|
+
@user = Ubiquitously::Propeller::User.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "login" do
|
11
|
+
should "raise informative error if invalid password" do
|
12
|
+
@user.password = "bad password"
|
13
|
+
assert_raises(Ubiquitously::AuthenticationError) do
|
14
|
+
@user.login
|
15
|
+
end
|
16
|
+
assert_equal false, @user.logged_in?
|
17
|
+
end
|
18
|
+
|
19
|
+
should "login successfully if valid credentials" do
|
20
|
+
assert_equal true, @user.login
|
21
|
+
assert_equal true, @user.logged_in?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "Propeller::Post" do
|
26
|
+
setup do
|
27
|
+
@post = Ubiquitously::Propeller::Post.new(
|
28
|
+
:debug => true,
|
29
|
+
:title => "A Title",
|
30
|
+
:description => "A Description",
|
31
|
+
:tags => ["usability", "ruby", "web services", "open source"],
|
32
|
+
:url => "http://example.com/abcdef"
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
should "create a post" do
|
37
|
+
assert @post.save(:debug => true)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
data/test/test_reddit.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
module Ubiquitously
|
4
|
+
class RedditTest < ActiveSupport::TestCase
|
5
|
+
context "Reddit::User" do
|
6
|
+
setup do
|
7
|
+
@user = Ubiquitously::Reddit::User.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "login" do
|
11
|
+
should "raise informative error if invalid password" do
|
12
|
+
@user.password = "bad password"
|
13
|
+
assert_raises(Ubiquitously::AuthenticationError) do
|
14
|
+
@user.login
|
15
|
+
end
|
16
|
+
assert_equal false, @user.logged_in?
|
17
|
+
end
|
18
|
+
|
19
|
+
should "login successfully if valid credentials" do
|
20
|
+
assert_equal true, @user.login
|
21
|
+
assert_equal true, @user.logged_in?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "Reddit::Post" do
|
27
|
+
setup do
|
28
|
+
@post = Ubiquitously::Reddit::Post.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
module Ubiquitously
|
4
|
+
class StumbleUponTest < ActiveSupport::TestCase
|
5
|
+
context "StumbleUpon::User" do
|
6
|
+
setup do
|
7
|
+
@user = Ubiquitously::StumbleUpon::User.new
|
8
|
+
end
|
9
|
+
|
10
|
+
context "login" do
|
11
|
+
should "raise informative error if invalid password" do
|
12
|
+
@user.password = "bad password"
|
13
|
+
assert_raises(Ubiquitously::AuthenticationError) do
|
14
|
+
@user.login
|
15
|
+
end
|
16
|
+
assert_equal false, @user.logged_in?
|
17
|
+
end
|
18
|
+
|
19
|
+
should "login successfully if valid credentials" do
|
20
|
+
assert_equal true, @user.login
|
21
|
+
assert_equal true, @user.logged_in?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context "StumbleUpon::Post" do
|
27
|
+
setup do
|
28
|
+
@post = Ubiquitously::StumbleUpon::Post.new
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ubiquitously
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 71
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
9
|
- 1
|
10
|
-
-
|
11
|
-
version: 0.0.1.
|
10
|
+
- 6
|
11
|
+
version: 0.0.1.6
|
12
12
|
platform: ruby
|
13
13
|
authors:
|
14
14
|
- Lance Pollard
|
@@ -48,10 +48,28 @@ files:
|
|
48
48
|
- Rakefile
|
49
49
|
- init.rb
|
50
50
|
- MIT-LICENSE
|
51
|
+
- lib/ext.rb
|
52
|
+
- lib/ubiquitously/base.rb
|
53
|
+
- lib/ubiquitously/dzone.rb
|
54
|
+
- lib/ubiquitously/faves.rb
|
55
|
+
- lib/ubiquitously/mister_wong.rb
|
56
|
+
- lib/ubiquitously/mixins/resourceful.rb
|
57
|
+
- lib/ubiquitously/mixx.rb
|
58
|
+
- lib/ubiquitously/newsvine.rb
|
59
|
+
- lib/ubiquitously/propeller.rb
|
60
|
+
- lib/ubiquitously/reddit.rb
|
61
|
+
- lib/ubiquitously/sphinn.rb
|
62
|
+
- lib/ubiquitously/stumble_upon.rb
|
51
63
|
- lib/ubiquitously.rb
|
52
64
|
- rails/init.rb
|
53
65
|
- test/config.yml
|
66
|
+
- test/test_dzone.rb
|
67
|
+
- test/test_faves.rb
|
54
68
|
- test/test_helper.rb
|
69
|
+
- test/test_newsvine.rb
|
70
|
+
- test/test_propeller.rb
|
71
|
+
- test/test_reddit.rb
|
72
|
+
- test/test_stumble_upon.rb
|
55
73
|
has_rdoc: true
|
56
74
|
homepage: http://github.com/viatropos/ubiquitously
|
57
75
|
licenses: []
|