robro 0.1.1 → 0.2.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.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/lib/robro/browser/helpers.rb +36 -0
- data/lib/robro/browser.rb +15 -14
- data/lib/robro/cli/helpers.rb +24 -0
- data/lib/robro/cli.rb +18 -25
- data/lib/robro/download.rb +123 -0
- data/lib/robro/user_scripts.rb +28 -0
- data/lib/robro/version.rb +1 -1
- data/lib/robro.rb +2 -1
- data/robro.gemspec +3 -2
- metadata +28 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1ed8475085bbc598b8d65eb0bd03913e6c1d898156bed192373c9dd4543e025
|
4
|
+
data.tar.gz: b233b37e22fff88c921a08a524a87515805de9327531ecdb8d636cf4ec33e8ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e826a76fe9e7dce91249b2a0d5bd19495bebe6202e2ff55be5871b6acad9d8c8b3a4a160f03062b5a8b82f72f8c68ec4dfba3e8ce10b592f88fb316f9e0af003
|
7
|
+
data.tar.gz: b4de6981f340ebcc59bf0c709dde1546440a67e6a53137550a58f8d816e19ecac58cf0075da48029c66ec616036dae0e8a24fb626d8691560bd04aeb6732dc95
|
data/.gitignore
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Robro
|
2
|
+
class Browser
|
3
|
+
module Helpers
|
4
|
+
def remove_elements(css:)
|
5
|
+
page.execute_script "document.querySelectorAll('#{css}').forEach(e => { e.remove() } )"
|
6
|
+
end
|
7
|
+
|
8
|
+
def click_through_overlay(element)
|
9
|
+
element_regexp = {
|
10
|
+
firefox: /^Element (<.*>) is not clickable at point \((\d+),(\d+)\) because another element (?<element><.*>) obscures it$/,
|
11
|
+
chrome: /^element click intercepted: Element (<.*>) is not clickable at point \((\d+), (\d+)\)\. Other element would receive the click: (?<element><.*>)/,
|
12
|
+
}
|
13
|
+
loop do
|
14
|
+
element.click(wait: 1)
|
15
|
+
break
|
16
|
+
rescue StandardError => e
|
17
|
+
m_element = element_regexp[Robro.browser.application].match e.message
|
18
|
+
unless m_element.nil?
|
19
|
+
m_id = /id=\"(?<id>[a-zA-Z0-9-]+)/.match m_element[:element]
|
20
|
+
unless m_id.nil?
|
21
|
+
overlay_element_id = m_id[:id]
|
22
|
+
Robro.logger.debug "Removing '#{overlay_element_id}' that obscures target element"
|
23
|
+
remove_elements css: "##{overlay_element_id}"
|
24
|
+
else
|
25
|
+
Robro.logger.error "Unable to find element ID that obscures target (#{element}) in: '#{m_id[:element]}'"
|
26
|
+
debugger
|
27
|
+
end
|
28
|
+
else
|
29
|
+
Robro.logger.error "Unable to find element that obscures target (#{element}) in: '#{e.message}'"
|
30
|
+
debugger
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/robro/browser.rb
CHANGED
@@ -2,32 +2,35 @@ require 'capybara'
|
|
2
2
|
require 'capybara/dsl'
|
3
3
|
require 'capybara-screenshot'
|
4
4
|
|
5
|
+
require_relative 'browser/helpers'
|
6
|
+
|
5
7
|
module Robro
|
6
8
|
class Browser
|
7
9
|
attr_reader :application
|
8
10
|
|
9
11
|
include Capybara::DSL
|
12
|
+
include Robro::Browser::Helpers
|
10
13
|
|
11
|
-
def initialize(application)
|
14
|
+
def initialize(application, headless: false)
|
12
15
|
@application = application.to_sym
|
13
|
-
@headless =
|
16
|
+
@headless = headless
|
14
17
|
|
15
18
|
Capybara.configure do |config|
|
16
19
|
config.run_server = false
|
17
20
|
config.default_max_wait_time = 5
|
18
21
|
end
|
19
22
|
|
20
|
-
# FIXME Register chrome_jack_headless
|
21
23
|
require 'selenium-webdriver'
|
22
24
|
Capybara.register_driver :chrome_jack do |app|
|
23
|
-
options = ::Selenium::WebDriver::Chrome::Options.new
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
options = ::Selenium::WebDriver::Chrome::Options.new
|
26
|
+
options.add_argument('--headless') if headless
|
27
|
+
options.add_argument('--start-maximized')
|
28
|
+
options.add_argument('--disable-blink-features')
|
29
|
+
options.add_argument('--disable-blink-features=AutomationControlled')
|
30
|
+
options.add_argument('--excludeSwitches=enable-automation')
|
31
|
+
options.add_argument('--disable-gpu')
|
32
|
+
|
33
|
+
driver = Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)
|
31
34
|
bridge = driver.browser.send(:bridge)
|
32
35
|
|
33
36
|
path = '/session/:session_id/chromium/send_command'
|
@@ -104,15 +107,13 @@ module Robro
|
|
104
107
|
|
105
108
|
driver_name = case @application
|
106
109
|
when :firefox
|
107
|
-
'selenium'
|
110
|
+
@headless ? 'selenium_headless' : 'selenium'
|
108
111
|
when :chrome
|
109
112
|
'chrome_jack'
|
110
113
|
else
|
111
114
|
raise "Unsupported browser: '#{application}'"
|
112
115
|
end
|
113
116
|
|
114
|
-
driver_name += '_headless' if @headless
|
115
|
-
|
116
117
|
driver_name.to_sym
|
117
118
|
end
|
118
119
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'tty-cursor'
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Robro
|
6
|
+
class CLI < Thor
|
7
|
+
module Helpers
|
8
|
+
def self.show_download_progress(changes, details)
|
9
|
+
if changes['filename']
|
10
|
+
puts "Downloading '#{details['filename']}' (size: #{details['size']})"
|
11
|
+
puts " progress: 0% rate: --- remaining time: ---"
|
12
|
+
elsif changes['progress']
|
13
|
+
print TTY::Cursor.prev_line
|
14
|
+
print TTY::Cursor.clear_line
|
15
|
+
puts " progress: #{details['progress']} rate: #{details['rate']} remaining time: #{details['remaining_time']}"
|
16
|
+
elsif changes['rate_average']
|
17
|
+
puts " rate average: #{details['rate_average']}"
|
18
|
+
elsif changes['message']
|
19
|
+
puts "Download message: #{changes['message']}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/robro/cli.rb
CHANGED
@@ -4,8 +4,6 @@ require 'robro/user_scripts'
|
|
4
4
|
require 'thor'
|
5
5
|
require 'json'
|
6
6
|
|
7
|
-
require 'active_support/inflector'
|
8
|
-
|
9
7
|
module Robro
|
10
8
|
class CLI < Thor
|
11
9
|
desc 'browse URL', 'Start browser at URL, drop a shell'
|
@@ -18,22 +16,19 @@ module Robro
|
|
18
16
|
|
19
17
|
quit = false
|
20
18
|
until quit
|
21
|
-
UserScripts.
|
22
|
-
us = us_class.new
|
23
|
-
next unless uri.host.start_with? *(us.supported_urls)
|
19
|
+
us = UserScripts.find_for uri: uri
|
24
20
|
|
25
|
-
|
26
|
-
|
21
|
+
unless us.nil?
|
22
|
+
puts "Commands for this URL: #{us.supported_url_commands}"
|
27
23
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
end
|
24
|
+
us.supported_url_commands.each do |command|
|
25
|
+
define_singleton_method command do |*args|
|
26
|
+
us.send(command, *args)
|
32
27
|
end
|
33
28
|
end
|
34
29
|
end
|
35
30
|
|
36
|
-
|
31
|
+
debugger
|
37
32
|
end
|
38
33
|
end
|
39
34
|
|
@@ -54,22 +49,20 @@ module Robro
|
|
54
49
|
end
|
55
50
|
end
|
56
51
|
|
57
|
-
UserScripts.
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
url = args.shift
|
64
|
-
raise Thor::Error, 'URL argument is missing' if url.nil?
|
52
|
+
UserScripts.all_commands.each do |command|
|
53
|
+
desc "#{command} URL", command.to_s
|
54
|
+
method_option :browser, :type => :string, :description => 'Supported values: firefox or chromium', :default => 'firefox'
|
55
|
+
define_method command do |*args|
|
56
|
+
url = args.shift
|
57
|
+
raise Thor::Error, 'URL argument is missing' if url.nil?
|
65
58
|
|
66
|
-
|
59
|
+
uri = validate_url(url)
|
67
60
|
|
68
|
-
|
69
|
-
Robro.browser.visit uri
|
61
|
+
Robro.browser = Browser.new options[:browser]
|
70
62
|
|
71
|
-
|
72
|
-
|
63
|
+
result = UserScripts.execute command, uri
|
64
|
+
|
65
|
+
puts JSON.pretty_generate(result)
|
73
66
|
end
|
74
67
|
end
|
75
68
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Robro
|
4
|
+
class Download
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
attr_reader :details
|
8
|
+
|
9
|
+
def initialize(url)
|
10
|
+
@url = url.sub /^http:/, 'https:'
|
11
|
+
Robro.logger.debug "Attempt to download: #{@url}"
|
12
|
+
end
|
13
|
+
|
14
|
+
FILTERS = [
|
15
|
+
#--2020-11-10 22:30:31-- https://kubuntu.org/wp-content/uploads/2020/10/6ac0/GroovyBannerv4-1024x273.png
|
16
|
+
/^--\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}-- http/,
|
17
|
+
#Resolving kubuntu.org (kubuntu.org)... 162.213.33.214
|
18
|
+
/^Resolving /,
|
19
|
+
#Connecting to kubuntu.org (kubuntu.org)|162.213.33.214|:443... connected.
|
20
|
+
/^Connecting /,
|
21
|
+
#Reusing existing connection to kubuntu.org:443.
|
22
|
+
/^Reusing /,
|
23
|
+
]
|
24
|
+
|
25
|
+
def run(&block)
|
26
|
+
@details = {}
|
27
|
+
|
28
|
+
result = false
|
29
|
+
|
30
|
+
Open3.popen2e(
|
31
|
+
{ "LANG" => 'C'},
|
32
|
+
'wget', '--continue', '--content-disposition', '--progress=dot:mega', @url,
|
33
|
+
) do |stdin, stdout_and_err, thread|
|
34
|
+
stdout_and_err.each do |line|
|
35
|
+
line.chomp!
|
36
|
+
next if line.empty?
|
37
|
+
|
38
|
+
data = extract_details line
|
39
|
+
|
40
|
+
unless data.nil?
|
41
|
+
data['filename'] = eval "\"#{data['filename']}\"" unless data['filename'].nil? # Handle escaped UTF-8 chars
|
42
|
+
@details.merge! data
|
43
|
+
block.call data, @details
|
44
|
+
else
|
45
|
+
puts "==='#{line}'===" unless filtred?(line)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
exit_status = thread.value
|
50
|
+
@details.merge!({ exit_status: exit_status })
|
51
|
+
|
52
|
+
raise Error.new "Error while downloading: #{error}" unless error.nil?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def downloaded?
|
57
|
+
success?
|
58
|
+
end
|
59
|
+
|
60
|
+
def success?
|
61
|
+
error.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
def error
|
65
|
+
return "#{details['error_code']}: #{details['error_text']}" unless details['error_code'].nil?
|
66
|
+
return "#{details['error_text']}" unless details['error_text'].nil?
|
67
|
+
return "Command execution failed with exit code: #{details[:exit_status].exitstatus}" unless details[:exit_status].success?
|
68
|
+
|
69
|
+
nil
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def filtred?(line)
|
75
|
+
FILTERS.each do |regexp|
|
76
|
+
match = regexp.match line
|
77
|
+
return true unless match.nil?
|
78
|
+
end
|
79
|
+
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
EXTRACTORS = [
|
84
|
+
/^Length: (?<size>\d+)/,
|
85
|
+
/^Saving to: '(?<filename>.*)'$/,
|
86
|
+
# 64512K ,,,,,,,. ........ ........ ........ ........ ........ 2% 1.26M 37m23s
|
87
|
+
# 67584K ........ ........ ........ ........ ........ ........ 2% 870K 42m29s
|
88
|
+
# 101376K ........ ........ ........ ........ ........ ........ 4% 4.70M 8m56s
|
89
|
+
# 104448K ........ ........ ........ ........ ........ ........ 4% 5.87M 8m41s
|
90
|
+
# 107520K ........ ........ ........ ........ ........ ........ 4% 6.17M 8m27s
|
91
|
+
#2472960K ........ ........ .. 100% 5.80M=5m57s
|
92
|
+
/^.* [,. ]{8} [,. ]{8} [,. ]{8} [,. ]{8} [,. ]{8} [,. ]{8} *(?<progress>\d+%) (?<rate>.*)[ =](?<remaining_time>.*)$/,
|
93
|
+
#2020-11-08 09:29:55 ERROR 410: Gone.
|
94
|
+
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ERROR (?<error_code>\d+): (?<error_text>.*)$/,
|
95
|
+
# [ skipping 79872K ]
|
96
|
+
/^ +\[ skipping (?<skipping>.*) \]/,
|
97
|
+
#2020-11-10 22:25:48 (1.19 MB/s) - 'GroovyBannerv4-1024x273.png' saved [278174/278174]
|
98
|
+
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} \((?<rate_average>.*)\) - '.*' saved/,
|
99
|
+
# The file is already fully retrieved; nothing to do.
|
100
|
+
/^ (?<message>The file is already fully retrieved; nothing to do\.)$/,
|
101
|
+
#HTTP request sent, awaiting response... 200 OK
|
102
|
+
/^HTTP request sent, awaiting response... (?<http_status_code>\d+) (?<http_status_text>.*)$/,
|
103
|
+
#wget: unable to resolve host address 'kuubuntu.org'
|
104
|
+
/^wget: (?<error_text>unable to resolve host address '.*')$/,
|
105
|
+
#Cannot write to 'kubuntu.iso' (Success).
|
106
|
+
/^(?<error_text>Cannot write to .*)$/,
|
107
|
+
]
|
108
|
+
|
109
|
+
def extract_details(line)
|
110
|
+
result = {}
|
111
|
+
EXTRACTORS.each do |extractor|
|
112
|
+
result.merge! extract(extractor, line)
|
113
|
+
end
|
114
|
+
result == {} ? nil : result
|
115
|
+
end
|
116
|
+
|
117
|
+
def extract(regexp, line)
|
118
|
+
match = regexp.match line
|
119
|
+
return {} if match.nil?
|
120
|
+
match.named_captures
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/robro/user_scripts.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_support/inflector'
|
2
|
+
|
1
3
|
module Robro
|
2
4
|
module UserScripts
|
3
5
|
def self.all
|
@@ -8,7 +10,33 @@ module Robro
|
|
8
10
|
end
|
9
11
|
end
|
10
12
|
|
13
|
+
def self.all_commands
|
14
|
+
user_scripts = UserScripts.all.map(&:new)
|
15
|
+
user_scripts.map(&:supported_url_commands).flatten.uniq
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.find_for(uri:)
|
19
|
+
user_scripts = UserScripts.all.map(&:new)
|
20
|
+
user_scripts.find { |us| uri.host.start_with? *(us.supported_urls) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.execute(command, uri, *args)
|
24
|
+
us = find_for uri: uri
|
25
|
+
|
26
|
+
raise "No user scripts found for URI: #{uri}" if us.nil?
|
27
|
+
|
28
|
+
Robro.logger.info "Opening URL: #{uri}"
|
29
|
+
Robro.browser.visit uri
|
30
|
+
|
31
|
+
Robro.logger.info "User script: '#{us.class}' will process commnand '#{command}'"
|
32
|
+
us.send(command, uri)
|
33
|
+
end
|
34
|
+
|
11
35
|
class Base
|
36
|
+
def browser
|
37
|
+
Robro.browser
|
38
|
+
end
|
39
|
+
|
12
40
|
def commands
|
13
41
|
raise NotImplementedError
|
14
42
|
end
|
data/lib/robro/version.rb
CHANGED
data/lib/robro.rb
CHANGED
@@ -2,7 +2,7 @@ require "robro/browser"
|
|
2
2
|
require "robro/version"
|
3
3
|
|
4
4
|
require 'tty/logger'
|
5
|
-
require '
|
5
|
+
require 'debug'
|
6
6
|
|
7
7
|
module Robro
|
8
8
|
class Error < StandardError; end
|
@@ -12,6 +12,7 @@ module Robro
|
|
12
12
|
config.handlers = [
|
13
13
|
[:console, { output: $stderr, level: :debug }],
|
14
14
|
]
|
15
|
+
config.metadata = %i[date time]
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
data/robro.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
|
9
9
|
spec.summary = %q{Robotized Browser}
|
10
10
|
spec.description = %q{Automate tasks that require a _real_ browser}
|
11
|
-
spec.homepage = "https://github.com/
|
11
|
+
spec.homepage = "https://github.com/neomilium/robro"
|
12
12
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
|
13
13
|
|
14
14
|
spec.metadata["homepage_uri"] = spec.homepage
|
@@ -29,8 +29,9 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.add_dependency "capybara-screenshot"
|
30
30
|
spec.add_dependency "selenium-webdriver"
|
31
31
|
spec.add_dependency "thor"
|
32
|
+
spec.add_dependency "tty-cursor"
|
32
33
|
spec.add_dependency "tty-logger"
|
33
|
-
spec.add_development_dependency "
|
34
|
+
spec.add_development_dependency "debug"
|
34
35
|
spec.add_development_dependency "bundler"
|
35
36
|
spec.add_development_dependency "rake"
|
36
37
|
spec.add_development_dependency "rspec"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: robro
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Romuald Conty
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: tty-cursor
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: tty-logger
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,7 +109,7 @@ dependencies:
|
|
95
109
|
- !ruby/object:Gem::Version
|
96
110
|
version: '0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
112
|
+
name: debug
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
100
114
|
requirements:
|
101
115
|
- - ">="
|
@@ -169,17 +183,20 @@ files:
|
|
169
183
|
- exe/robro
|
170
184
|
- lib/robro.rb
|
171
185
|
- lib/robro/browser.rb
|
186
|
+
- lib/robro/browser/helpers.rb
|
172
187
|
- lib/robro/cli.rb
|
188
|
+
- lib/robro/cli/helpers.rb
|
189
|
+
- lib/robro/download.rb
|
173
190
|
- lib/robro/user_scripts.rb
|
174
191
|
- lib/robro/version.rb
|
175
192
|
- robro.gemspec
|
176
|
-
homepage: https://github.com/
|
193
|
+
homepage: https://github.com/neomilium/robro
|
177
194
|
licenses: []
|
178
195
|
metadata:
|
179
|
-
homepage_uri: https://github.com/
|
180
|
-
source_code_uri: https://github.com/
|
181
|
-
changelog_uri: https://github.com/
|
182
|
-
post_install_message:
|
196
|
+
homepage_uri: https://github.com/neomilium/robro
|
197
|
+
source_code_uri: https://github.com/neomilium/robro.git
|
198
|
+
changelog_uri: https://github.com/neomilium/robro/Changelog.md
|
199
|
+
post_install_message:
|
183
200
|
rdoc_options: []
|
184
201
|
require_paths:
|
185
202
|
- lib
|
@@ -194,8 +211,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
194
211
|
- !ruby/object:Gem::Version
|
195
212
|
version: '0'
|
196
213
|
requirements: []
|
197
|
-
rubygems_version: 3.
|
198
|
-
signing_key:
|
214
|
+
rubygems_version: 3.3.15
|
215
|
+
signing_key:
|
199
216
|
specification_version: 4
|
200
217
|
summary: Robotized Browser
|
201
218
|
test_files: []
|