share_to_gplus 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/ReadMe.md ADDED
@@ -0,0 +1,55 @@
1
+ # Create posts in Google+ communities
2
+
3
+ This gem uses Capybara with *selenium* driver by default to share information in Google+ Communities.
4
+
5
+ ## Usage
6
+
7
+ ### As a gem
8
+
9
+ Add *share_to_gplus* to your GemFile or just
10
+
11
+ gem install share_to_gplus
12
+
13
+ Require the gem somewhere in your app
14
+
15
+ require 'share_to_gplus'
16
+
17
+ Initialize the gem like
18
+
19
+ share_this = ShareToGplus::It.new do |config|
20
+ config.login = 'login from your G+ account here'
21
+ config.password = 'password from your G+ account here'
22
+ config.text = 'text to share'
23
+ config.url = 'Url of G+ community to share post to'
24
+ config.category = 'Category of the post in G+ community'
25
+ config.link = 'Link to be attached to the post'
26
+ end
27
+
28
+ Finally
29
+
30
+ share_this.execute
31
+
32
+ By default the gem uses *selenium* as a web driver. You can specify capybara-compatible driver if you want to. Currently you can choose between *selenium* and [poltergeist](https://github.com/teampoltergeist/poltergeist)
33
+
34
+ share_this = ShareToGplus::It.new do |config|
35
+ config.javascript_driver = :poltergeist
36
+ ...
37
+ end
38
+
39
+ In this case you need to manually install proper Capybara driver and require it before calling *ShareToGplus*
40
+
41
+ ### Using CLI
42
+
43
+ sharetogplus --link http://shre_me.com -c CATEGORY -l LOGIN -p PASSWORD -u http://link_to_google_plus_community
44
+
45
+ More information you can find when execute
46
+
47
+ sharetogplus -h
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create new Pull Request
data/bin/sharetogplus ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'share_to_gplus'
4
+ ShareToGplus::Executable.invoke(ARGV)
@@ -0,0 +1,13 @@
1
+ module ShareToGplus
2
+ class Configuration
3
+ attr_accessor :login, :password, :text, :link, :category, :url,
4
+ :javascript_driver
5
+
6
+ def initialize
7
+ %w(login password text link category url javascript_driver).each do |var|
8
+ instance_variable_set("@#{var}", nil)
9
+ end
10
+ @javascript_driver ||= :selenium # set default driver
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,90 @@
1
+ require 'optparse'
2
+
3
+ module ShareToGplus
4
+ class Executable
5
+ attr_accessor :options
6
+
7
+ def self.invoke(args)
8
+ executable = new
9
+ executable.invoke if executable.parse_arguments(args)
10
+ end
11
+
12
+ def initialize
13
+ @options = {}
14
+ end
15
+
16
+ def parse_arguments(args)
17
+ OptionParser.new do |opts|
18
+ opts.banner = 'Usage: sharetogplus [options]'
19
+
20
+ on_service_arguments(opts)
21
+ on_mandatory_arguments(opts)
22
+ on_content_arguments(opts)
23
+ end.parse!(args)
24
+
25
+ raise 'No arguments specified!' if @options.empty?
26
+
27
+ true
28
+ end
29
+
30
+ def invoke
31
+ share_this = ShareToGplus::It.new do |config|
32
+ config.login = @options[:login]
33
+ config.password = @options[:password]
34
+ config.text = @options[:text]
35
+ config.url = @options[:url]
36
+ config.category = @options[:category]
37
+ config.link = @options[:link]
38
+ config.javascript_driver = @options[:javascript_driver] if @options[:javascript_driver]
39
+ end
40
+ share_this.execute
41
+ end
42
+
43
+ private
44
+
45
+ def on_content_arguments(opts)
46
+ opts.on('--link LINK', 'LINK to share in Google+ Community') do |v|
47
+ @options[:link] = v
48
+ end
49
+
50
+ opts.on('-t', '--text TEXT', 'TEXT to share in Google+ Community') do |v|
51
+ @options[:text] = v
52
+ end
53
+
54
+ opts.on('-c', '--category CATEGORY', 'CATEGORY for new post in Google+ Community') do |v|
55
+ @options[:category] = v
56
+ end
57
+ end
58
+
59
+ def on_mandatory_arguments(opts)
60
+ opts.on('-l', '--login LOGIN', 'LOGIN for authorising in Google+') do |v|
61
+ @options[:login] = v
62
+ end
63
+
64
+ opts.on('-p', '--password PASSWORD', 'PASSWORD for authorising in Google+') do |v|
65
+ @options[:password] = v
66
+ end
67
+
68
+ opts.on('-u', '--url URL', 'URL of Google+ community to share a post to') do |v|
69
+ @options[:url] = v
70
+ end
71
+ end
72
+
73
+ def on_service_arguments(opts)
74
+ opts.on_tail('-h', '--help', 'Show this message') do
75
+ puts opts
76
+ exit
77
+ end
78
+
79
+ opts.on_tail('-v', '--version', 'Show version') do
80
+ puts VERSION
81
+ exit
82
+ end
83
+
84
+ opts.on('-d', '--javascript_driver DRIVER', 'Javascript Driver. "selenium" by default') do |v|
85
+ @options[:javascript_driver] = v
86
+ end
87
+ end
88
+ end
89
+ end
90
+
@@ -0,0 +1,34 @@
1
+ module ShareToGplus
2
+ class It
3
+ attr_accessor :config
4
+
5
+ def initialize
6
+ @config = ShareToGplus.configuration
7
+ yield(@config) if block_given?
8
+ @sharer = Sharer.new(url: config.url, config: @config)
9
+ end
10
+
11
+ def execute
12
+ navigate
13
+ share
14
+ @sharer.wait_a_little_bit
15
+ true
16
+ end
17
+
18
+ private
19
+
20
+ def navigate
21
+ @sharer.visit_community
22
+ @sharer.login(login: config.login, password: config.password)
23
+ @sharer.try_new_google_plus
24
+ end
25
+
26
+ def share
27
+ @sharer.open_share_dialog
28
+ @sharer.fill_link(link: config.link)
29
+ @sharer.fill_text(text: config.text)
30
+ @sharer.close_dialog
31
+ @sharer.set_category(name: config.category)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,90 @@
1
+ module ShareToGplus
2
+ require 'capybara/dsl'
3
+ require 'capybara/poltergeist'
4
+
5
+ class Sharer
6
+
7
+ include Capybara::DSL
8
+
9
+ def initialize(url: "", config: ShareToGplus.configuration)
10
+ @url = url
11
+ Capybara.configure do |capybara_config|
12
+ capybara_config.run_server = false
13
+ capybara_config.default_driver = config.javascript_driver.to_sym
14
+ capybara_config.app_host = url
15
+ capybara_config.default_max_wait_time = 10
16
+ capybara_config.raise_server_errors = false
17
+ end
18
+ @google_plus_version = "old" # by default
19
+ end
20
+
21
+ def visit_community
22
+ visit @url
23
+ page.current_window.resize_to(1280, 1024)
24
+ end
25
+
26
+ def login(login:, password:)
27
+ return unless page.has_selector?("#gb_70")
28
+
29
+ find("#gb_70").click
30
+ fill_in 'email', :with => login
31
+ find("input#next").click
32
+ fill_in 'Passwd', :with => password
33
+ find("input#signIn").click
34
+ end
35
+
36
+ def try_new_google_plus
37
+ @google_plus_version = "new" if !page.has_selector?("div.Ege.qMc")
38
+ if @google_plus_version == "old" && page.has_selector?(".d-k-l.b-c.b-c-Wa")
39
+ find(".d-k-l.b-c.b-c-Wa", visible: :all).click
40
+ wait_a_little_bit
41
+ @google_plus_version = "new"
42
+ end
43
+ end
44
+
45
+ def open_share_dialog
46
+ find("div.jXDCJf.Tek5Ce.BDrJf").click if @google_plus_version == "new"
47
+ find("div.kqa.es").click if @google_plus_version == "old"
48
+ end
49
+
50
+ def fill_text(text:)
51
+ if @google_plus_version == "new"
52
+ dialog = find("[role='dialog']")
53
+ dialog.find("div:nth-child(1) div:nth-child(2) textarea").set(text)
54
+ else
55
+ find("div.df.b-K.b-K-Xb.URaP8").send_keys(text)
56
+ end
57
+ end
58
+
59
+ def fill_link(link:)
60
+ if @google_plus_version == "new"
61
+ find(:xpath, '//div[@aria-label="Add link"]').click
62
+ dialog = find(:xpath, '//div[@role="dialog"]')
63
+ dialog.find('input').set(link)
64
+ dialog.find('input').send_keys :enter
65
+ else
66
+ find("span.d-s.ph.pZ").click
67
+ wait_a_little_bit
68
+ find("div.MR input.fm").set(link)
69
+ end
70
+ end
71
+
72
+ def close_dialog
73
+ find(:xpath, '//span[contains(., "Post")]').click if @google_plus_version == "new"
74
+ end
75
+
76
+ def set_category(name:)
77
+ find(:xpath, "//div[@data-name='#{name}']").click if @google_plus_version == "new"
78
+ if @google_plus_version == "old"
79
+ find("div.BA.pi.d-e.d-k-l.d-r-c").click
80
+ find(:xpath, "//div[@class='An zr d-r']/div[@class='Vm d-A']//span[. = 'Test']/../..").click
81
+ find("div.d-k-l.b-c.b-c-Ba.qy.jt").click
82
+ end
83
+ end
84
+
85
+ def wait_a_little_bit
86
+ sleep(5)
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,3 @@
1
+ module ShareToGplus
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,25 @@
1
+ require 'share_to_gplus/version'
2
+
3
+ module ShareToGplus
4
+
5
+ require 'share_to_gplus/configuration'
6
+ require 'share_to_gplus/it'
7
+ require 'share_to_gplus/sharer'
8
+ require 'share_to_gplus/executable'
9
+
10
+ class << self
11
+ attr_writer :configuration
12
+ end
13
+
14
+ def self.configuration
15
+ @configuration ||= Configuration.new
16
+ end
17
+
18
+ def self.reset
19
+ @configuration = Configuration.new
20
+ end
21
+
22
+ def self.configure
23
+ yield(configuration)
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $LOAD_PATH.push File.expand_path('../lib', __FILE__)
3
+ require 'share_to_gplus/version'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gem.authors = ['Andrey Eremin']
7
+ gem.email = ['dsoft88@gmail.com']
8
+ gem.description = 'Share text and links to Google+ community'
9
+ gem.summary = 'Share text and links to Google+ community'
10
+ gem.homepage = 'https://github.com/developer88/share_to_google_plus'
11
+
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = ['sharetogplus']
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.name = 'share_to_gplus'
16
+ gem.require_paths = ['lib']
17
+ gem.version = ShareToGplus::VERSION
18
+ gem.license = 'GPL-3.0'
19
+
20
+ gem.add_runtime_dependency 'capybara', '>=2'
21
+ gem.add_runtime_dependency 'selenium-webdriver'
22
+ gem.add_runtime_dependency 'poltergeist'
23
+
24
+ gem.add_development_dependency 'rspec'
25
+ gem.add_development_dependency 'guard'
26
+ gem.add_development_dependency 'guard-rspec'
27
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+ require 'share_to_gplus'
3
+
4
+ module ShareToGplus
5
+ describe ShareToGplus::Configuration do
6
+ subject { Configuration.new }
7
+
8
+ describe '#login' do
9
+ it 'default value is ""' do
10
+ expect(subject.login).to eq(nil)
11
+ end
12
+
13
+
14
+ end
15
+
16
+ describe '#login=' do
17
+ it 'can set value' do
18
+ subject.login = ''
19
+ expect(subject.login).to eq('')
20
+ end
21
+ end
22
+
23
+ context 'defaulte values' do
24
+ it 'returns default value' do
25
+ expect(subject.javascript_driver).to eq(:selenium)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'share_to_gplus'
3
+
4
+ module ShareToGplus
5
+ describe ShareToGplus::Executable do
6
+ subject { ShareToGplus::Executable }
7
+
8
+ describe 'self.#invoke' do
9
+ before do
10
+ expect_any_instance_of(subject).to receive(:parse_arguments).and_return(true)
11
+ expect_any_instance_of(subject).to receive(:invoke).and_return(false)
12
+ end
13
+
14
+ it { subject.invoke({}) }
15
+ end
16
+
17
+ describe '#parse_arguments' do
18
+ context 'when no arguments provided' do
19
+ let(:arguments) { [] }
20
+
21
+ it { expect{ subject.invoke(arguments) }.to raise_exception(RuntimeError) }
22
+ end
23
+
24
+ context 'when invalid arguments provided' do
25
+ let(:arguments) { %w(--login test --password) }
26
+
27
+ it { expect{ subject.invoke(arguments) }.to raise_exception(OptionParser::MissingArgument) }
28
+ end
29
+
30
+ context 'when correct arguments provided' do
31
+ let(:arguments) { %w(--login test --password pass --url http://test.com) }
32
+ before do
33
+ expect_any_instance_of(subject).to receive(:invoke).and_return(true)
34
+ end
35
+
36
+ it { expect(subject.invoke(arguments)).to be true }
37
+ end
38
+ end
39
+
40
+ describe '#invoke' do
41
+ subject { ShareToGplus::Executable.new }
42
+ let(:it_class) { ShareToGplus::It }
43
+
44
+ before do
45
+ expect(it_class).to receive(:new).and_call_original
46
+ expect_any_instance_of(it_class).to receive(:execute).and_return(true)
47
+ end
48
+
49
+ it { subject.invoke }
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'share_to_gplus'
3
+
4
+ module ShareToGplus
5
+ describe ShareToGplus::It do
6
+ subject { ShareToGplus::It.new }
7
+
8
+ describe '#execute' do
9
+ let(:it_class) { ShareToGplus::It }
10
+ let(:sharer_class) { ShareToGplus::Sharer }
11
+
12
+ before do
13
+ expect_any_instance_of(it_class).to receive(:navigate).and_return(true)
14
+ expect_any_instance_of(it_class).to receive(:share).and_return(true)
15
+ expect_any_instance_of(sharer_class).to receive(:wait_a_little_bit).and_return(true)
16
+ end
17
+
18
+ it 'should return true' do
19
+ expect(subject.execute).to be true
20
+ end
21
+ end
22
+
23
+ describe '#initialize' do
24
+ before do
25
+ expect(Sharer).to receive(:new).and_call_original
26
+ expect(ShareToGplus).to receive(:configuration).and_call_original
27
+ end
28
+
29
+ it { subject }
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+ require 'share_to_gplus'
3
+
4
+ module ShareToGplus
5
+ describe ShareToGplus::Sharer do
6
+ subject { ShareToGplus::Sharer }
7
+
8
+ describe '#initialize' do
9
+ before do
10
+ expect(Capybara).to receive(:configure).and_call_original
11
+ end
12
+
13
+ it { subject.new }
14
+ end
15
+
16
+ # No additional Capybara tests here.
17
+ # Since i use Capybara to browse Google's pages it is quite hard to
18
+ # prepare dummy Google-like page for tests and always keep it up to date.
19
+ end
20
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+ require 'share_to_gplus'
3
+
4
+ describe ShareToGplus do
5
+ describe '#configure' do
6
+ let(:url) { 'http://someurl.com' }
7
+
8
+ before :each do
9
+ ShareToGplus.configure do |config|
10
+ config.url = url
11
+ end
12
+ end
13
+
14
+ it 'returns an array with 10 elements' do
15
+ sharer = ShareToGplus::It.new
16
+
17
+ expect(sharer.config.url).to eq(url)
18
+ end
19
+
20
+ after :each do
21
+ ShareToGplus.reset
22
+ end
23
+ end
24
+
25
+ describe '#reset' do
26
+ before :each do
27
+ ShareToGplus.configure do |config|
28
+ config.login = ''
29
+ end
30
+ end
31
+
32
+ it 'resets the configuration' do
33
+ ShareToGplus.reset
34
+
35
+ config = ShareToGplus.configuration
36
+
37
+ expect(config.login).to eq(nil)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,96 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
4
+ # this file to always be loaded, without a need to explicitly require it in any
5
+ # files.
6
+ #
7
+ # Given that it is always loaded, you are encouraged to keep this file as
8
+ # light-weight as possible. Requiring heavyweight dependencies from this file
9
+ # will add to the boot time of your test suite on EVERY test run, even for an
10
+ # individual file that may not need all of that loaded. Instead, consider making
11
+ # a separate helper file that requires the additional dependencies and performs
12
+ # the additional setup, and require it from the spec files that actually need
13
+ # it.
14
+ #
15
+ # The `.rspec` file also contains a few flags that are not defaults but that
16
+ # users commonly want.
17
+ #
18
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
19
+ RSpec.configure do |config|
20
+ # rspec-expectations config goes here. You can use an alternate
21
+ # assertion/expectation library such as wrong or the stdlib/minitest
22
+ # assertions if you prefer.
23
+ config.expect_with :rspec do |expectations|
24
+ # This option will default to `true` in RSpec 4. It makes the `description`
25
+ # and `failure_message` of custom matchers include text for helper methods
26
+ # defined using `chain`, e.g.:
27
+ # be_bigger_than(2).and_smaller_than(4).description
28
+ # # => "be bigger than 2 and smaller than 4"
29
+ # ...rather than:
30
+ # # => "be bigger than 2"
31
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
+ end
33
+
34
+ # rspec-mocks config goes here. You can use an alternate test double
35
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
36
+ config.mock_with :rspec do |mocks|
37
+ # Prevents you from mocking or stubbing a method that does not exist on
38
+ # a real object. This is generally recommended, and will default to
39
+ # `true` in RSpec 4.
40
+ mocks.verify_partial_doubles = true
41
+ end
42
+
43
+ # The settings below are suggested to provide a good initial experience
44
+ # with RSpec, but feel free to customize to your heart's content.
45
+ =begin
46
+ # These two settings work together to allow you to limit a spec run
47
+ # to individual examples or groups you care about by tagging them with
48
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
49
+ # get run.
50
+ config.filter_run :focus
51
+ config.run_all_when_everything_filtered = true
52
+
53
+ # Allows RSpec to persist some state between runs in order to support
54
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
55
+ # you configure your source control system to ignore this file.
56
+ config.example_status_persistence_file_path = "spec/examples.txt"
57
+
58
+ # Limits the available syntax to the non-monkey patched syntax that is
59
+ # recommended. For more details, see:
60
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
61
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
62
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
63
+ config.disable_monkey_patching!
64
+
65
+ # This setting enables warnings. It's recommended, but in some cases may
66
+ # be too noisy due to issues in dependencies.
67
+ config.warnings = true
68
+
69
+ # Many RSpec users commonly either run the entire suite or an individual
70
+ # file, and it's useful to allow more verbose output when running an
71
+ # individual spec file.
72
+ if config.files_to_run.one?
73
+ # Use the documentation formatter for detailed output,
74
+ # unless a formatter has already been configured
75
+ # (e.g. via a command-line flag).
76
+ config.default_formatter = 'doc'
77
+ end
78
+
79
+ # Print the 10 slowest examples and example groups at the
80
+ # end of the spec run, to help surface which specs are running
81
+ # particularly slow.
82
+ config.profile_examples = 10
83
+
84
+ # Run specs in random order to surface order dependencies. If you find an
85
+ # order dependency and want to debug it, you can fix the order by providing
86
+ # the seed, which is printed after each run.
87
+ # --seed 1234
88
+ config.order = :random
89
+
90
+ # Seed global randomization in this process using the `--seed` CLI option.
91
+ # Setting this allows you to use `--seed` to deterministically reproduce
92
+ # test failures related to randomization by passing the same `--seed` value
93
+ # as the one that triggered the failure.
94
+ Kernel.srand config.seed
95
+ =end
96
+ end