moby.rb 1.0.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 +7 -0
- data/CHANGES.txt +32 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +172 -0
- data/Rakefile +22 -0
- data/bin/moby +67 -0
- data/lib/File/self.collect.rb +10 -0
- data/lib/Moby/Backends/MechanizeBackend.rb +157 -0
- data/lib/Moby/Backends/SeleniumBackend.rb +236 -0
- data/lib/Moby/Configuration.rb +113 -0
- data/lib/Moby/RandomInputGenerator.rb +49 -0
- data/lib/Moby/VERSION.rb +6 -0
- data/lib/Moby.rb +179 -0
- data/lib/Selenium/WebDriver/Driver/Attempt/attempt.rb +18 -0
- data/lib/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb +13 -0
- data/lib/Thoran/File/SelfCollect/self.collect.rb +22 -0
- data/lib/Thoran/Selenium/WebDriver/Driver/Attempt/attempt.rb +53 -0
- data/lib/Thoran/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb +39 -0
- data/moby.gemspec +51 -0
- data/test/Moby/VERSION_test.rb +23 -0
- data/test/moby_test.rb +295 -0
- data/test/test_helper.rb +36 -0
- metadata +153 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# lib/Moby/Backends/SeleniumBackend.rb
|
|
2
|
+
# Moby::Backends::SeleniumBackend
|
|
3
|
+
|
|
4
|
+
require 'selenium-webdriver'
|
|
5
|
+
|
|
6
|
+
require_relative '../Selenium/WebDriver/Driver/Attempt/attempt'
|
|
7
|
+
require_relative '../Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ'
|
|
8
|
+
|
|
9
|
+
class Moby
|
|
10
|
+
module Backends
|
|
11
|
+
class SeleniumBackend
|
|
12
|
+
def counter_phish
|
|
13
|
+
set_up_driver
|
|
14
|
+
navigate_to_target
|
|
15
|
+
repeatedly_fill_and_submit_login_form
|
|
16
|
+
ensure
|
|
17
|
+
cleanup_driver unless @config.debug?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def initialize(config:, random_input_generator:)
|
|
23
|
+
@config = config
|
|
24
|
+
@random_input_generator = random_input_generator
|
|
25
|
+
@driver = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def driver
|
|
29
|
+
puts "Setting up #{@config.browser} driver..." if @config.verbose
|
|
30
|
+
case @config.browser.to_sym
|
|
31
|
+
when :chrome
|
|
32
|
+
options = Selenium::WebDriver::Chrome::Options.new
|
|
33
|
+
options.add_argument('--headless') unless @config.debug
|
|
34
|
+
options.add_argument('--disable-gpu')
|
|
35
|
+
options.add_argument('--no-sandbox')
|
|
36
|
+
options.add_argument("user-agent=#{@config.user_agent}") if @config.user_agent
|
|
37
|
+
@driver = Selenium::WebDriver.for(:chrome, options: options)
|
|
38
|
+
when :firefox
|
|
39
|
+
options = Selenium::WebDriver::Firefox::Options.new
|
|
40
|
+
options.add_argument('--headless') unless @config.debug
|
|
41
|
+
options.add_preference('general.useragent.override', @config.user_agent) if @config.user_agent
|
|
42
|
+
@driver = Selenium::WebDriver.for(:firefox, options: options)
|
|
43
|
+
else
|
|
44
|
+
@driver = Selenium::WebDriver.for(@config.browser)
|
|
45
|
+
end
|
|
46
|
+
puts "Driver setup complete." if @config.verbose?
|
|
47
|
+
end
|
|
48
|
+
alias_method :set_up_driver, :driver
|
|
49
|
+
|
|
50
|
+
def navigate_to_target
|
|
51
|
+
puts "Navigating to #{@config.url}..." if @config.verbose?
|
|
52
|
+
@driver.attempt do
|
|
53
|
+
@driver.get(@config.url)
|
|
54
|
+
wait_for_page_load
|
|
55
|
+
end
|
|
56
|
+
puts "Page loaded successfully." if @config.verbose?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def wait_for_page_load
|
|
60
|
+
wait = Selenium::WebDriver::Wait.new(timeout: 10)
|
|
61
|
+
wait.until do
|
|
62
|
+
@driver.execute_script('return document.readyState') == 'complete'
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def form
|
|
67
|
+
@form ||= (
|
|
68
|
+
if @config.using_form_name?
|
|
69
|
+
find_form_by_name
|
|
70
|
+
else
|
|
71
|
+
find_form_by_number
|
|
72
|
+
end
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def repeatedly_fill_and_submit_login_form
|
|
77
|
+
start_time = Time.now
|
|
78
|
+
puts "moby session begun #{start_time}."
|
|
79
|
+
submission_count = 0
|
|
80
|
+
begin
|
|
81
|
+
if @config.fixed_number_of_submissions?
|
|
82
|
+
@config.iterations.times do |iteration|
|
|
83
|
+
fill_and_submit_login_form
|
|
84
|
+
submission_count += 1
|
|
85
|
+
puts "Submission #{submission_count} of #{@config.iterations}." if @config.verbose?
|
|
86
|
+
end
|
|
87
|
+
puts "Successfully completed #{@config.iterations} submissions." if @config.verbose?
|
|
88
|
+
else
|
|
89
|
+
loop do
|
|
90
|
+
fill_and_submit_login_form
|
|
91
|
+
submission_count += 1
|
|
92
|
+
puts "#{submission_count} #{username}:#{password}" if @config.verbose?
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
ensure
|
|
96
|
+
finish_time = Time.now
|
|
97
|
+
time_delta_in_minutes = (finish_time - start_time) / 60
|
|
98
|
+
submissions_per_minute = submission_count / time_delta_in_minutes
|
|
99
|
+
puts "moby session terminated #{finish_time} with #{submission_count} counter-phishes served in #{time_delta_in_minutes} minutes for an average of #{submissions_per_minute} submissions per minute."
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def fill_and_submit_login_form
|
|
104
|
+
puts "Filling with: #{username}:#{password}" if @config.verbose?
|
|
105
|
+
driver.navigate.to(@config.url)
|
|
106
|
+
wait_for_page_load
|
|
107
|
+
driver.attempt do
|
|
108
|
+
username_field.clear
|
|
109
|
+
username_field.send_keys(username)
|
|
110
|
+
password_field.clear
|
|
111
|
+
password_field.send_keys(password)
|
|
112
|
+
submit_form(form)
|
|
113
|
+
end
|
|
114
|
+
sleep @config.delay if @config.delay > 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def find_form_by_name
|
|
118
|
+
name = @config.form_name
|
|
119
|
+
selectors = [
|
|
120
|
+
[:name, name],
|
|
121
|
+
[:id, name],
|
|
122
|
+
[:css, "form[name='#{name}']"],
|
|
123
|
+
[:css, "form##{name}"]
|
|
124
|
+
]
|
|
125
|
+
selectors.each do |type, selector|
|
|
126
|
+
if @driver.element_present?(type, selector)
|
|
127
|
+
return @driver.find_element(type, selector)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
raise("Could not find a form with the name or id of #{@config.form_name}.")
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def find_form_by_number
|
|
134
|
+
index = @config.form_number
|
|
135
|
+
forms = @driver.find_elements(:tag_name, 'form')
|
|
136
|
+
return forms[index] if forms[index]
|
|
137
|
+
raise("Could not find a form at index #{@config.form_number}.")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def username
|
|
141
|
+
@random_input_generator.random_username
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def password
|
|
145
|
+
@random_input_generator.random_password
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def username_field
|
|
149
|
+
@username_field ||= (
|
|
150
|
+
if @config.using_username_field_name?
|
|
151
|
+
find_username_field_by_name
|
|
152
|
+
else
|
|
153
|
+
find_username_field_by_number
|
|
154
|
+
end
|
|
155
|
+
)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def password_field
|
|
159
|
+
@password_field ||= (
|
|
160
|
+
if @config.using_password_field_name?
|
|
161
|
+
find_password_field_by_name
|
|
162
|
+
else
|
|
163
|
+
find_password_field_by_number
|
|
164
|
+
end
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def find_username_field_by_name
|
|
169
|
+
name = @config.username_field_name
|
|
170
|
+
selectors = [
|
|
171
|
+
[:name, name],
|
|
172
|
+
[:id, name],
|
|
173
|
+
[:css, "input[name='#{name}']"],
|
|
174
|
+
[:css, "input##{name}"]
|
|
175
|
+
]
|
|
176
|
+
selectors.each do |type, selector|
|
|
177
|
+
begin
|
|
178
|
+
element = form.find_element(type, selector)
|
|
179
|
+
return element if element
|
|
180
|
+
rescue Selenium::WebDriver::Error::NoSuchElementError
|
|
181
|
+
next
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
raise("Could not find username field: #{@config.username_field_name}")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def find_username_field_by_number
|
|
188
|
+
index = @config.username_field_number
|
|
189
|
+
inputs = form.find_elements(:tag_name, 'input')
|
|
190
|
+
inputs[index] ||
|
|
191
|
+
raise("Could not find username field at index #{index}")
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def find_password_field_by_name
|
|
195
|
+
name = @config.password_field_name
|
|
196
|
+
selectors = [
|
|
197
|
+
[:name, name],
|
|
198
|
+
[:id, name],
|
|
199
|
+
[:css, "input[name='#{name}']"],
|
|
200
|
+
[:css, "input##{name}"]
|
|
201
|
+
]
|
|
202
|
+
selectors.each do |type, selector|
|
|
203
|
+
begin
|
|
204
|
+
element = form.find_element(type, selector)
|
|
205
|
+
return element if element
|
|
206
|
+
rescue Selenium::WebDriver::Error::NoSuchElementError
|
|
207
|
+
next
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
raise("Could not find password field: #{@config.password_field_name}")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def find_password_field_by_number
|
|
214
|
+
index = @config.password_field_number
|
|
215
|
+
inputs = form.find_elements(:tag_name, 'input')
|
|
216
|
+
inputs[index] ||
|
|
217
|
+
raise("Could not find password field at index #{index}")
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def submit_form(form)
|
|
221
|
+
begin
|
|
222
|
+
submit_button = form.find_element(:css, "input[type='submit'], button[type='submit'], button")
|
|
223
|
+
submit_button.click
|
|
224
|
+
rescue
|
|
225
|
+
form.submit
|
|
226
|
+
end
|
|
227
|
+
sleep 0.5
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def cleanup_driver
|
|
231
|
+
@driver.quit if @driver
|
|
232
|
+
puts "Driver closed." if @config.verbose?
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# lib/Moby/Configuration.rb
|
|
2
|
+
# Moby::Configuration
|
|
3
|
+
|
|
4
|
+
require 'mechanize'
|
|
5
|
+
|
|
6
|
+
class Moby
|
|
7
|
+
class Configuration
|
|
8
|
+
AVAILABLE_USER_AGENTS = (Mechanize::AGENT_ALIASES.keys - ['Mechanize']).freeze
|
|
9
|
+
|
|
10
|
+
attr_accessor(
|
|
11
|
+
:url,
|
|
12
|
+
:backend,
|
|
13
|
+
:browser,
|
|
14
|
+
:debug,
|
|
15
|
+
:verbose,
|
|
16
|
+
:form_name,
|
|
17
|
+
:form_number,
|
|
18
|
+
:username_field_name,
|
|
19
|
+
:username_field_number,
|
|
20
|
+
:password_field_name,
|
|
21
|
+
:password_field_number,
|
|
22
|
+
:username_hostname,
|
|
23
|
+
:username_is_email_address,
|
|
24
|
+
:iterations,
|
|
25
|
+
:delay,
|
|
26
|
+
:word_list_path
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
attr_writer(
|
|
30
|
+
:user_agent,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def user_agent
|
|
34
|
+
@user_agent || random_user_agent
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def using_form_name?
|
|
38
|
+
!@form_name.nil?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def using_username_field_name?
|
|
42
|
+
!@username_field_name.nil?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def using_password_field_name?
|
|
46
|
+
!@password_field_name.nil?
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def mechanize?
|
|
50
|
+
@backend == :mechanize
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def selenium?
|
|
54
|
+
@backend == :selenium
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def username_is_email_address?
|
|
58
|
+
@username_is_email_address || @username_hostname || @username_field_name == 'email'
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def debug?
|
|
62
|
+
@debug
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def verbose?
|
|
66
|
+
@verbose
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def fixed_number_of_submissions?
|
|
70
|
+
!!@iterations
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
# @param url [String] The target URL
|
|
76
|
+
# @param backend [Symbol] :mechanize or :selenium (default: :selenium)
|
|
77
|
+
# @param browser [Symbol] :chrome or :firefox (default: :chrome)
|
|
78
|
+
|
|
79
|
+
def initialize(url:, backend: :selenium, browser: :chrome, **args)
|
|
80
|
+
@url = url
|
|
81
|
+
@backend = backend.to_sym
|
|
82
|
+
|
|
83
|
+
# Browser configuration (for Selenium)
|
|
84
|
+
@browser = browser.to_sym
|
|
85
|
+
@user_agent = args[:user_agent]
|
|
86
|
+
|
|
87
|
+
# Form identification
|
|
88
|
+
@form_name = args[:form_name]
|
|
89
|
+
@form_number = args[:form_number] || 0
|
|
90
|
+
|
|
91
|
+
# Field identification
|
|
92
|
+
@username_field_name = args[:username_field_name]
|
|
93
|
+
@username_field_number = args[:username_field_number] || 0
|
|
94
|
+
@password_field_name = args[:password_field_name]
|
|
95
|
+
@password_field_number = args[:password_field_number] || 1
|
|
96
|
+
|
|
97
|
+
# Username configuration
|
|
98
|
+
@username_hostname = args[:username_hostname]
|
|
99
|
+
@username_is_email_address = args[:username_is_email_address] || false
|
|
100
|
+
|
|
101
|
+
# Runtime configuration
|
|
102
|
+
@debug = args[:debug] || false
|
|
103
|
+
@verbose = args[:verbose] || false
|
|
104
|
+
@iterations = args[:iterations] || 100
|
|
105
|
+
@delay = args[:delay] || 0
|
|
106
|
+
@word_list_path = args[:word_list_path] || '/usr/share/dict/words'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def random_user_agent
|
|
110
|
+
AVAILABLE_USER_AGENTS.sample
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# lib/Moby/RandomInputGenerator.rb
|
|
2
|
+
# Moby::RandomInputGenerator
|
|
3
|
+
|
|
4
|
+
class Moby
|
|
5
|
+
class RandomInputGenerator
|
|
6
|
+
TLD = %w{com net org edu int mil gov arpa biz aero name coop info pro museum}
|
|
7
|
+
|
|
8
|
+
def random_username
|
|
9
|
+
if username_is_email_address?
|
|
10
|
+
if @username_hostname
|
|
11
|
+
"#{random_word}@#{@username_hostname}"
|
|
12
|
+
else
|
|
13
|
+
"#{random_word}@#{random_word}.#{random_TLD}"
|
|
14
|
+
end
|
|
15
|
+
else
|
|
16
|
+
random_word
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def random_password
|
|
21
|
+
random_word
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def initialize(word_list_path:, username_hostname: nil, username_is_email_address: false)
|
|
27
|
+
@word_list_path = word_list_path
|
|
28
|
+
@username_hostname = username_hostname
|
|
29
|
+
@username_is_email_address = username_is_email_address
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def random_word
|
|
33
|
+
words.sample
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def words
|
|
37
|
+
@words ||= File.readlines(@word_list_path).map(&:chomp).reject(&:empty?)
|
|
38
|
+
end
|
|
39
|
+
alias_method :load_words, :words
|
|
40
|
+
|
|
41
|
+
def random_TLD
|
|
42
|
+
TLD.sample
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def username_is_email_address?
|
|
46
|
+
@username_is_email_address
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/Moby/VERSION.rb
ADDED
data/lib/Moby.rb
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Moby.rb
|
|
2
|
+
# Moby
|
|
3
|
+
|
|
4
|
+
require 'mechanize'
|
|
5
|
+
require 'pp'
|
|
6
|
+
|
|
7
|
+
require_relative './File/self.collect'
|
|
8
|
+
require_relative './Moby/VERSION'
|
|
9
|
+
|
|
10
|
+
class Moby
|
|
11
|
+
TLD = %w{com net org edu int mil gov arpa biz aero name coop info pro museum}
|
|
12
|
+
|
|
13
|
+
attr_accessor\
|
|
14
|
+
:debug,
|
|
15
|
+
:form_name,
|
|
16
|
+
:form_number,
|
|
17
|
+
:password_field_name,
|
|
18
|
+
:password_field_number,
|
|
19
|
+
:url,
|
|
20
|
+
:username_field_name,
|
|
21
|
+
:username_field_number,
|
|
22
|
+
:username_hostname,
|
|
23
|
+
:username_is_email_address,
|
|
24
|
+
:verbose
|
|
25
|
+
|
|
26
|
+
attr_writer\
|
|
27
|
+
:user_agent
|
|
28
|
+
|
|
29
|
+
def initialize(
|
|
30
|
+
debug: false,
|
|
31
|
+
form_name: nil,
|
|
32
|
+
form_number: 0,
|
|
33
|
+
password_field_name: 'password',
|
|
34
|
+
password_field_number: 1,
|
|
35
|
+
url: nil,
|
|
36
|
+
user_agent: nil,
|
|
37
|
+
username_field_name: 'username',
|
|
38
|
+
username_field_number: 0,
|
|
39
|
+
username_hostname: nil,
|
|
40
|
+
username_is_email_address: false,
|
|
41
|
+
verbose: false
|
|
42
|
+
)
|
|
43
|
+
@debug = debug
|
|
44
|
+
@form_name = form_name
|
|
45
|
+
@form_number = form_number.to_i
|
|
46
|
+
@password_field_name = password_field_name
|
|
47
|
+
@password_field_number = password_field_number.to_i
|
|
48
|
+
@url = url
|
|
49
|
+
@user_agent = user_agent
|
|
50
|
+
@username_field_name = username_field_name
|
|
51
|
+
@username_field_number = username_field_number.to_i
|
|
52
|
+
@username_hostname = username_hostname
|
|
53
|
+
@username_is_email_address = username_is_email_address
|
|
54
|
+
@verbose = verbose
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def counter_phish
|
|
58
|
+
start_time = Time.now
|
|
59
|
+
puts "Moby session begun #{start_time}."
|
|
60
|
+
submission_count = 0
|
|
61
|
+
begin
|
|
62
|
+
loop do
|
|
63
|
+
mechanize.user_agent_alias = user_agent
|
|
64
|
+
username_field.value = username
|
|
65
|
+
password_field.value = password
|
|
66
|
+
pp page if @debug
|
|
67
|
+
result = mechanize.submit(form)
|
|
68
|
+
pp result if @debug
|
|
69
|
+
submission_count += 1
|
|
70
|
+
puts "#{submission_count} #{username}:#{password}" if @verbose
|
|
71
|
+
rescue Net::HTTP::Persistent::Error
|
|
72
|
+
puts "\n\nNet::HTTP::Persistent::Error rescued.\n\n" if @verbose
|
|
73
|
+
rescue Net::OpenTimeout
|
|
74
|
+
puts "\n\nNet::OpenTimeout rescued.\n\n" if @verbose
|
|
75
|
+
end
|
|
76
|
+
ensure
|
|
77
|
+
finish_time = Time.now
|
|
78
|
+
time_delta_in_minutes = (finish_time - start_time) / 60
|
|
79
|
+
submissions_per_minute = submission_count / time_delta_in_minutes
|
|
80
|
+
puts "Moby session terminated #{finish_time} with #{submission_count} counter-phishes served in #{time_delta_in_minutes} minutes for an average of #{submissions_per_minute} submissions per minute."
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def mechanize
|
|
87
|
+
@mechanize ||= Mechanize.new
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def words(filename = '/usr/share/dict/words')
|
|
91
|
+
@words ||= File.collect(filename){|line| line.chomp}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def random_word
|
|
95
|
+
words[rand(words.size)]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def random_TLD
|
|
99
|
+
TLD[rand(TLD.size)]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def random_user_agent
|
|
103
|
+
agent_aliases = Mechanize::AGENT_ALIASES
|
|
104
|
+
agent_aliases.delete('Mechanize')
|
|
105
|
+
agent_aliases_keys = agent_aliases.keys
|
|
106
|
+
agent_aliases_keys[rand(agent_aliases.size)]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def page
|
|
110
|
+
@page ||= (
|
|
111
|
+
page = mechanize.get(@url)
|
|
112
|
+
md = page.body.match(/meta.*?Refresh.*?url=(.*?)"/i)
|
|
113
|
+
if md
|
|
114
|
+
puts 'meta_refresh_url found' if @debug
|
|
115
|
+
page = mechanize.get(md[1])
|
|
116
|
+
end
|
|
117
|
+
pp page if @debug
|
|
118
|
+
page
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def form
|
|
123
|
+
@form ||= (
|
|
124
|
+
if @form_name
|
|
125
|
+
page.form(@form_name)
|
|
126
|
+
elsif @form_number
|
|
127
|
+
page.forms[@form_number]
|
|
128
|
+
else
|
|
129
|
+
page.forms[0]
|
|
130
|
+
end
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def username
|
|
135
|
+
if username_is_email_address?
|
|
136
|
+
if username_hostname
|
|
137
|
+
"#{random_word}@#{@username_hostname}"
|
|
138
|
+
else
|
|
139
|
+
"#{random_word}@#{random_word}.#{random_TLD}"
|
|
140
|
+
end
|
|
141
|
+
else
|
|
142
|
+
random_word
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def password
|
|
147
|
+
random_word
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def user_agent
|
|
151
|
+
@user_agent || random_user_agent
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def username_field
|
|
155
|
+
if @username_field_name
|
|
156
|
+
form.field(@username_field_name)
|
|
157
|
+
elsif @username_field_number
|
|
158
|
+
form.fields[@username_field_number]
|
|
159
|
+
else
|
|
160
|
+
form.fields[0]
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def password_field
|
|
165
|
+
if @password_field_name
|
|
166
|
+
form.field(@password_field_name)
|
|
167
|
+
elsif @password_field_number
|
|
168
|
+
form.fields[@password_field_number]
|
|
169
|
+
else
|
|
170
|
+
form.fields[1]
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# predicate methods
|
|
175
|
+
|
|
176
|
+
def username_is_email_address?
|
|
177
|
+
@username_is_email_address || @username_hostname || username_field.name == 'email'
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Selenium/WebDriver/Driver/Attempt/attempt.rb
|
|
2
|
+
# Selenium::WebDriver::Driver::Attempt.attempt
|
|
3
|
+
|
|
4
|
+
# 20200418
|
|
5
|
+
# 0.3.0
|
|
6
|
+
|
|
7
|
+
# Examples:
|
|
8
|
+
# 1. driver.attempt do |driver|
|
|
9
|
+
# driver.get('https://example.com/users/sign_in')
|
|
10
|
+
# enter_username
|
|
11
|
+
# enter_password
|
|
12
|
+
# sign_in
|
|
13
|
+
# end
|
|
14
|
+
|
|
15
|
+
# Changes:
|
|
16
|
+
# 1. Moved all the logic into the Thoran namespace.
|
|
17
|
+
|
|
18
|
+
require 'Thoran/Selenium/WebDriver/Driver/Attempt/attempt'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb
|
|
2
|
+
# Selenium::WebDriver::SearchContext::ElementPresentQ.element_present?
|
|
3
|
+
|
|
4
|
+
# 20200418
|
|
5
|
+
# 0.3.0
|
|
6
|
+
|
|
7
|
+
# Examples:
|
|
8
|
+
# 1. driver.element_present?(:xpath, '//a[@id="submit"]')
|
|
9
|
+
|
|
10
|
+
# Changes:
|
|
11
|
+
# 1. Moved all the logic into the Thoran namespace.
|
|
12
|
+
|
|
13
|
+
require 'Thoran/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ'
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Thoran/File/SelfCollect/self.collect.rb
|
|
2
|
+
# Thoran::File::SelfCollect.collect.rb
|
|
3
|
+
|
|
4
|
+
# 20190821
|
|
5
|
+
# 0.2.0
|
|
6
|
+
|
|
7
|
+
# Changes:
|
|
8
|
+
# 1. + Thoran::File::SelfCollect namespace.
|
|
9
|
+
|
|
10
|
+
module Thoran
|
|
11
|
+
module File
|
|
12
|
+
module SelfCollect
|
|
13
|
+
|
|
14
|
+
def collect(filename, &block)
|
|
15
|
+
open(filename, 'r').collect(&block)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
File.extend(Thoran::File::SelfCollect)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Thoran/Selenium/WebDriver/Attempt/attempt.rb
|
|
2
|
+
# Thoran::Selenium::WebDriver::Attempt.attempt
|
|
3
|
+
|
|
4
|
+
# 20200419
|
|
5
|
+
# 0.0.1
|
|
6
|
+
|
|
7
|
+
# Examples:
|
|
8
|
+
# 1. driver.attempt do |driver|
|
|
9
|
+
# driver.get('https://example.com/users/sign_in')
|
|
10
|
+
# enter_username
|
|
11
|
+
# enter_password
|
|
12
|
+
# sign_in
|
|
13
|
+
# end
|
|
14
|
+
|
|
15
|
+
# Changes:
|
|
16
|
+
# 1. + Thoran namespace.
|
|
17
|
+
|
|
18
|
+
module Thoran
|
|
19
|
+
module Selenium
|
|
20
|
+
module WebDriver
|
|
21
|
+
module Driver
|
|
22
|
+
module Attempt
|
|
23
|
+
|
|
24
|
+
def attempt(max_attempts = 3, &block)
|
|
25
|
+
attempts = 0
|
|
26
|
+
loop do
|
|
27
|
+
begin
|
|
28
|
+
yield
|
|
29
|
+
break
|
|
30
|
+
rescue Timeout::Error, ::Selenium::WebDriver::Error::UnknownError, ::Selenium::WebDriver::Error::NoSuchElementError => e
|
|
31
|
+
attempts += 1
|
|
32
|
+
if attempts >= max_attempts
|
|
33
|
+
@driver.quit
|
|
34
|
+
puts "Giving up after #{max_attempts} attempts to #{__method__} because #{e}."
|
|
35
|
+
exit
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
module Selenium
|
|
48
|
+
module WebDriver
|
|
49
|
+
class Driver
|
|
50
|
+
include Thoran::Selenium::WebDriver::Driver::Attempt
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|