ubiquitously 0.0.1.5 → 0.0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|