stealth_browser_automation 1.1.34 → 1.2.38
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/baseproxy.rb +25 -1
- data/lib/browserfactory.rb +560 -188
- data/lib/proxy.rb +16 -2
- data/lib/stealth_browser_automation.rb +332 -23
- metadata +66 -6
data/lib/browserfactory.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'watir-webdriver'
|
2
|
+
require 'selenium-webdriver'
|
2
3
|
require "pathname"
|
3
4
|
|
4
5
|
# **ABSTRACT**
|
@@ -30,13 +31,203 @@ require "pathname"
|
|
30
31
|
# Custom Selenium elements to stream the browser windows when some events happen.
|
31
32
|
module Selenium
|
32
33
|
module WebDriver
|
34
|
+
=begin # TODO: enable this for mouse movement biometrics simulation
|
35
|
+
module PointerActions
|
36
|
+
alias_method :old_move_to_location, :move_to_location
|
37
|
+
alias_method :old_move_to, :move_to
|
38
|
+
|
39
|
+
@@x = 0
|
40
|
+
@@y = 0
|
41
|
+
|
42
|
+
def x()
|
43
|
+
@@x
|
44
|
+
end
|
45
|
+
|
46
|
+
def y()
|
47
|
+
@@y
|
48
|
+
end
|
49
|
+
|
50
|
+
def move_to_location(x, y)
|
51
|
+
# getting noise factor
|
52
|
+
if !BlackStack::BrowserFactory.mouse_hash.nil?
|
53
|
+
actions = []
|
54
|
+
|
55
|
+
# do a noisy moveent to (x,y)
|
56
|
+
i = { :x=>@@x.to_f, :y=>@@y.to_f } # starting position
|
57
|
+
j = { :x=>x.to_f, :y=>y.to_f } # ending position
|
58
|
+
t = 0.to_f
|
59
|
+
z = BlackStack::BrowserFactory.mouse_hash[:noise].to_f
|
60
|
+
|
61
|
+
# TODO: validate the step tiene que ser un divisor de 1. O sea: 1/step da un numero entero.
|
62
|
+
step = 0.05 # 20 steps ( 1 / 0.05 = 20 )
|
63
|
+
|
64
|
+
while t<1.to_f
|
65
|
+
t += step
|
66
|
+
# noise calculation
|
67
|
+
if t<1.to_f # there is no noise for the last step
|
68
|
+
noise = {
|
69
|
+
:x => ( z.to_f + (rand(10000).to_f / 10000.to_f ) - 1.to_f ) / 10.to_f,
|
70
|
+
:y => ( z.to_f + (rand(10000).to_f / 10000.to_f ) - 1.to_f ) / 10.to_f,
|
71
|
+
}
|
72
|
+
else
|
73
|
+
noise = {
|
74
|
+
:x => 0.to_f,
|
75
|
+
:y => 0.to_f,
|
76
|
+
}
|
77
|
+
end
|
78
|
+
w = {
|
79
|
+
:x => ( i[:x] + t*(j[:x]-i[:x]) + t*(j[:x]-i[:x])*noise[:x] ).round(0).to_i,
|
80
|
+
:y => ( i[:y] + t*(j[:y]-i[:y]) + t*(j[:y]-i[:y])*noise[:y] ).round(0).to_i,
|
81
|
+
}
|
82
|
+
# TODO: fire javascript event? --> the old_move_to_location method will fire such event
|
83
|
+
# peform action
|
84
|
+
a = old_move_to_location(w[:x], w[:y])
|
85
|
+
actions << a
|
86
|
+
a.perform if t<1.to_f
|
87
|
+
end
|
88
|
+
@@x = x
|
89
|
+
@@y = y
|
90
|
+
actions.last
|
91
|
+
else
|
92
|
+
old_move_to_location(x, y)
|
93
|
+
end # if !BlackStack::BrowserFactory.mouse_hash.nil?
|
94
|
+
end # def move_to_location(x, y)
|
95
|
+
|
96
|
+
def move_to(element, right_by = nil, down_by = nil)
|
97
|
+
# getting noise factor
|
98
|
+
if !BlackStack::BrowserFactory.mouse_hash.nil?
|
99
|
+
z = BlackStack::BrowserFactory.mouse_hash[:noise].to_f
|
100
|
+
|
101
|
+
# select a random position inside the element, and call the move_to_location method.
|
102
|
+
noise = {
|
103
|
+
:x => ( z.to_f + (rand(10000).to_f / 10000.to_f ) ) / 2.to_f,
|
104
|
+
:y => ( z.to_f + (rand(10000).to_f / 10000.to_f ) ) / 2.to_f,
|
105
|
+
}
|
106
|
+
|
107
|
+
w = {
|
108
|
+
:x => ( element.location.x.to_f + element.size.width.to_f * noise[:x].to_f ).to_i,
|
109
|
+
:y => ( element.location.y.to_f + element.size.height.to_f * noise[:y].to_f ).to_i,
|
110
|
+
}
|
111
|
+
self.move_to_location(w[:x], w[:y])
|
112
|
+
else
|
113
|
+
old_move_to(x, y)
|
114
|
+
end # if !BlackStack::BrowserFactory.mouse_hash.nil?
|
115
|
+
end
|
116
|
+
|
117
|
+
end # class ActionBuilder
|
118
|
+
=end
|
33
119
|
class Element
|
34
120
|
alias_method :old_click, :click
|
121
|
+
alias_method :old_send_keys, :send_keys
|
35
122
|
|
36
123
|
def click()
|
124
|
+
=begin # TODO: enable this for mouse movement biometrics simulation
|
125
|
+
# getting noise factor
|
126
|
+
if !BlackStack::BrowserFactory.mouse_hash.nil?
|
127
|
+
# noise move movement to the element, plus click into a random position inside the element.
|
128
|
+
BlackStack::BrowserFactory.browser.driver.action.move_to(self)
|
129
|
+
end
|
130
|
+
=end
|
131
|
+
#
|
37
132
|
old_click
|
133
|
+
#
|
38
134
|
BlackStack::BrowserFactory.browser.push_screenshot
|
39
135
|
end # def click
|
136
|
+
|
137
|
+
# Masking keystrokes biometrics
|
138
|
+
# This method is also called by Selenium::WebDriver::Element::set, and by the setter Selenium::WebDriver::Element::value
|
139
|
+
def send_keys(*args)
|
140
|
+
=begin # TODO: enable this for mouse movement biometrics simulation
|
141
|
+
# click the element before writing, that will move the cursor to the element previously
|
142
|
+
self.click()
|
143
|
+
=end
|
144
|
+
#
|
145
|
+
pevious_char = nil
|
146
|
+
if BlackStack::BrowserFactory.keystroke_signature.nil?
|
147
|
+
old_send_keys(args)
|
148
|
+
else
|
149
|
+
args.each { |s|
|
150
|
+
|
151
|
+
if s.is_a? Symbol
|
152
|
+
old_send_keys(s)
|
153
|
+
else
|
154
|
+
# convert single quotes string to double using eval() function, so I can replace escaped chars using String::escape_javascript method.
|
155
|
+
# more information here: https://www.ruby-forum.com/t/single-quoted-string-to-become-double-quoted-is-it-possible/214980
|
156
|
+
# TODO: create the method String::to_souble_quotes in blackstack_commons
|
157
|
+
eval("\"#{s.gsub('"','\\"')}\"").split('').each { |c|
|
158
|
+
# escape string to build a javascript literal string dynamically
|
159
|
+
c2 = c.escape_javascript
|
160
|
+
j = c.ord
|
161
|
+
j = j%256 # considerar caracteres no ascii, fuera del intervalo [0,255]
|
162
|
+
|
163
|
+
if !pevious_char.nil?
|
164
|
+
i = pevious_char.ord
|
165
|
+
i = i%256 # considerar caracteres no ascii, fuera del intervalo [0,255]
|
166
|
+
|
167
|
+
# d1 = delay between keys + 10% random
|
168
|
+
d = BlackStack::BrowserFactory.keystroke_matrix[i][j] # delay between 2 keys
|
169
|
+
aux1 = 1000000
|
170
|
+
rnd = ( rand((d.to_f * aux1.to_f).to_i).to_f * 0.1.to_f ) / aux1.to_f
|
171
|
+
d1 = d + rnd
|
172
|
+
|
173
|
+
# apply delay between keys
|
174
|
+
sleep(d1)
|
175
|
+
end
|
176
|
+
|
177
|
+
# d2 = delay the relative dwell and flight time
|
178
|
+
# more info here: https://www.keytrac.net/en/technology
|
179
|
+
d2 = BlackStack::BrowserFactory.keystroke_matrix[0][j] # delay between 2 keys
|
180
|
+
# TODO: agregar un random
|
181
|
+
|
182
|
+
# apply delay between dwell and flight time
|
183
|
+
|
184
|
+
# TODO: this is not working because key_down only accepts :CTRL, :ALT, :SHIFT
|
185
|
+
# BlackStack::BrowserFactory.browser.driver.action.key_down(self, c).perform
|
186
|
+
|
187
|
+
# TODO: this is working but when c.ord is outside the range [0,255]
|
188
|
+
# TODO: injected javascript is detectable by websites!
|
189
|
+
script = "arguments[0].dispatchEvent(new KeyboardEvent('keydown',{'key':\"#{c2}\"}));"
|
190
|
+
if c.ord >= 0 && c.ord <= 255
|
191
|
+
# disable pampa notifications in this case
|
192
|
+
baux = BlackStack::BrowserFactory.browser.pampa_integration
|
193
|
+
BlackStack::BrowserFactory.browser.pampa_integration = false
|
194
|
+
BlackStack::BrowserFactory.browser.execute_script(script, self)
|
195
|
+
BlackStack::BrowserFactory.browser.pampa_integration = baux
|
196
|
+
end
|
197
|
+
|
198
|
+
sleep(d2)
|
199
|
+
|
200
|
+
if c=='\n'
|
201
|
+
old_send_keys("\n") # TODO: I don't know why old_send_keys("\n") print 2 chars (\n) in the textarea, instead to do the breakline.
|
202
|
+
else
|
203
|
+
begin
|
204
|
+
old_send_keys("#{j.chr}")
|
205
|
+
rescue
|
206
|
+
# issue #112 ( https://github.com/leandrosardi/blackstack/issues/112 )
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# TODO: this is not working because key_up only accepts :CTRL, :ALT, :SHIFT
|
211
|
+
# BlackStack::BrowserFactory.browser.driver.action.key_up(self, c).perform
|
212
|
+
|
213
|
+
# TODO: this is working but when c.ord is outside the range [0,255]
|
214
|
+
# TODO: injected javascript is detectable by websites!
|
215
|
+
script = "arguments[0].dispatchEvent(new KeyboardEvent('keyup',{'key':\"#{c2}\"}));"
|
216
|
+
if c.ord >= 0 && c.ord <= 255
|
217
|
+
# disable pampa notifications in this case
|
218
|
+
baux = BlackStack::BrowserFactory.browser.pampa_integration
|
219
|
+
BlackStack::BrowserFactory.browser.pampa_integration = false
|
220
|
+
BlackStack::BrowserFactory.browser.execute_script(script, self)
|
221
|
+
BlackStack::BrowserFactory.browser.pampa_integration = baux
|
222
|
+
end
|
223
|
+
|
224
|
+
# update previous char
|
225
|
+
pevious_char = c
|
226
|
+
}
|
227
|
+
end # if s.is_a? Symbol
|
228
|
+
}
|
229
|
+
end
|
230
|
+
end # def send_keys(*args)
|
40
231
|
|
41
232
|
end # class Element
|
42
233
|
end # module WebDriver
|
@@ -75,14 +266,14 @@ module BlackStack
|
|
75
266
|
#PAMPA_BROWSER_CHANNEL_LOCK_FILENAME = './browserfactory.channel.%PROFILE_NAME%.lock' # manejo de concurrencia en la creación de browsers
|
76
267
|
#attr_accessor :lockfile
|
77
268
|
|
78
|
-
attr_accessor :proxy, :lnuser, :agent_name, :profile_name
|
269
|
+
attr_accessor :proxy, :lnuser, :agent_name, :profile_name, :pampa_integration, :rquery
|
79
270
|
|
80
271
|
def lock_channel()
|
81
|
-
self.lockfile.flock(File::LOCK_EX)
|
272
|
+
# self.lockfile.flock(File::LOCK_EX)
|
82
273
|
end
|
83
274
|
|
84
275
|
def release_channel()
|
85
|
-
self.lockfile.flock(File::LOCK_UN)
|
276
|
+
# self.lockfile.flock(File::LOCK_UN)
|
86
277
|
end
|
87
278
|
|
88
279
|
def initialize(driver)
|
@@ -90,9 +281,16 @@ module BlackStack
|
|
90
281
|
#fname = PAMPA_BROWSER_CHANNEL_LOCK_FILENAME.gsub('%PROFILE_NAME%', self.profile_name.to_s)
|
91
282
|
#self.lockfile = File.open(fname,"w")
|
92
283
|
|
284
|
+
self.pampa_integration = true
|
285
|
+
|
93
286
|
#
|
94
287
|
super(driver)
|
95
288
|
|
289
|
+
# TODO: activate it when develop the RQuery (Render Query) feature
|
290
|
+
=begin
|
291
|
+
self.rquery = BlackStack::StealthBrowserAutomation::RQuery::FixedMatching(self)
|
292
|
+
=end
|
293
|
+
|
96
294
|
# TODO: enable this when you move to the "frequency-based-streaming" model
|
97
295
|
=begin
|
98
296
|
# Can't operate the same browser from more than 1 thread.
|
@@ -134,21 +332,23 @@ module BlackStack
|
|
134
332
|
end # def initialize
|
135
333
|
|
136
334
|
def notify(method)
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
335
|
+
if self.pampa_integration
|
336
|
+
url = "#{BlackStack::Pampa::api_protocol}://#{PROCESS.ws_url}:#{PROCESS.ws_port}/api1.3/pampa/browser/notify.json"
|
337
|
+
res = BlackStack::Netting::call_post(url, {
|
338
|
+
:api_key => BlackStack::Pampa::api_key,
|
339
|
+
:filename => $0,
|
340
|
+
:method => method,
|
341
|
+
:worker_name => PROCESS.fullWorkerName,
|
342
|
+
:id_proxy => self.proxy.nil? ? nil : self.proxy.id,
|
343
|
+
:id_lnuser => self.lnuser.nil? ? nil : self.lnuser.id,
|
344
|
+
:worker_assigned_process => PROCESS.worker.assigned_process,
|
345
|
+
:profile_name => self.profile_name,
|
346
|
+
:agent_name => self.agent_name,
|
347
|
+
})
|
348
|
+
parsed = JSON.parse(res.body)
|
349
|
+
if parsed['status'] != "success"
|
350
|
+
raise "Error Tracing BrowserActivity: #{parsed['status']}"
|
351
|
+
end
|
152
352
|
end
|
153
353
|
end
|
154
354
|
|
@@ -201,7 +401,7 @@ module BlackStack
|
|
201
401
|
BlackStack::BrowserFactory.browser.push_screenshot
|
202
402
|
super
|
203
403
|
end
|
204
|
-
|
404
|
+
|
205
405
|
def execute_script(script, *args)
|
206
406
|
self.notify("execute_script")
|
207
407
|
BlackStack::BrowserFactory.browser.push_screenshot
|
@@ -331,6 +531,14 @@ module BlackStack
|
|
331
531
|
# Only TYPE_CHROME or TYPE_MIMIC are supported at this moment.
|
332
532
|
@@type = nil
|
333
533
|
|
534
|
+
# Masking keystroke biometric signature
|
535
|
+
@@keystroke_signature = nil
|
536
|
+
@@keystroke_matrix = nil
|
537
|
+
|
538
|
+
# Masking keystroke biometric signature
|
539
|
+
@@mouse_signature = nil
|
540
|
+
@@mouse_hash = nil
|
541
|
+
|
334
542
|
# Returns the PampaBrowser object
|
335
543
|
def self.browser
|
336
544
|
@@browser
|
@@ -341,6 +549,148 @@ module BlackStack
|
|
341
549
|
[BlackStack::BrowserFactory::TYPE_CHROME, BlackStack::BrowserFactory::TYPE_MIMIC]
|
342
550
|
end
|
343
551
|
|
552
|
+
# Returns the @@keystroke_signature attribute
|
553
|
+
def self.keystroke_signature
|
554
|
+
@@keystroke_signature
|
555
|
+
end
|
556
|
+
|
557
|
+
# Returns the @@keystroke_matrix attribute
|
558
|
+
def self.keystroke_matrix
|
559
|
+
@@keystroke_matrix
|
560
|
+
end
|
561
|
+
|
562
|
+
# Returns the @@mouse_signature attribute
|
563
|
+
def self.mouse_signature
|
564
|
+
@@mouse_signature
|
565
|
+
end
|
566
|
+
|
567
|
+
# Returns the @@keystroke_matrix attribute
|
568
|
+
def self.mouse_hash
|
569
|
+
@@mouse_hash
|
570
|
+
end
|
571
|
+
|
572
|
+
# Set the @@keystroke_signature attribute
|
573
|
+
def self.set_keystroke_signature(s)
|
574
|
+
return if s.nil?
|
575
|
+
|
576
|
+
raise 'The keystroke signature must be a GUID' if !s.guid?
|
577
|
+
|
578
|
+
# guid normalization
|
579
|
+
s = s.to_guid
|
580
|
+
|
581
|
+
# initializing class attributes
|
582
|
+
@@keystroke_signature = s
|
583
|
+
@@keystroke_matrix = []
|
584
|
+
|
585
|
+
# calculating the average writing speed
|
586
|
+
writing_speed = 0
|
587
|
+
s.split('').each { |c|
|
588
|
+
writing_speed += c.ord if c.ord != '-'.ord
|
589
|
+
}
|
590
|
+
writing_speed = writing_speed.to_f / 100000.to_f
|
591
|
+
|
592
|
+
# calculating average delay between each pair of keys
|
593
|
+
delays = s.split('') #.delete('-')
|
594
|
+
i = 0
|
595
|
+
while i<256
|
596
|
+
j = 0
|
597
|
+
k = 0
|
598
|
+
@@keystroke_matrix[i] = []
|
599
|
+
while j<256
|
600
|
+
k = 0 if k>=delays.size
|
601
|
+
@@keystroke_matrix[i][j] = writing_speed.to_f
|
602
|
+
@@keystroke_matrix[i][j] += delays[k-0].to_f / 100.to_f
|
603
|
+
@@keystroke_matrix[i][j] += delays[k-1].to_f / 100.to_f if k>0
|
604
|
+
@@keystroke_matrix[i][j] += delays[k-2].to_f / 100.to_f if k>1
|
605
|
+
@@keystroke_matrix[i][j] += delays[k-3].to_f / 100.to_f if k>2
|
606
|
+
@@keystroke_matrix[i][j] += delays[k-4].to_f / 100.to_f if k>3
|
607
|
+
k+=1
|
608
|
+
j+=1
|
609
|
+
end
|
610
|
+
i+=1
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
# Set the @@mouse_signature attribute
|
615
|
+
def self.set_mouse_signature(s)
|
616
|
+
return if s.nil?
|
617
|
+
|
618
|
+
raise 'The mouse signature must be a GUID' if !s.guid?
|
619
|
+
|
620
|
+
# guid normalization
|
621
|
+
s = s.to_guid
|
622
|
+
|
623
|
+
# initializing class attributes
|
624
|
+
@@mouse_signature = s
|
625
|
+
|
626
|
+
# initialize mouse hash
|
627
|
+
max = 16*32-1 # it is the sum of all the hex number from the max GUID: FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF
|
628
|
+
sum = 0 # it is the sum of all the hex number from the GUID in the `s` parameter
|
629
|
+
s.gsub('-', '').upcase.split('').each { |c|
|
630
|
+
i = "0x#{c}".to_i(16)
|
631
|
+
sum += i
|
632
|
+
}
|
633
|
+
rate = sum.to_f / max.to_f
|
634
|
+
@@mouse_hash = {
|
635
|
+
:noise => rate,
|
636
|
+
}
|
637
|
+
end
|
638
|
+
|
639
|
+
# kill both multilogin.exe and headless.exe processes.
|
640
|
+
def self.mla_kill()
|
641
|
+
# this is to run many mlalisteners in paralell
|
642
|
+
# for more information refer to #166:doc:
|
643
|
+
# https://github.com/leandrosardi/blackstack/issues/166
|
644
|
+
f = File.open('mlalistener.lock', File::CREAT)
|
645
|
+
|
646
|
+
# kill tasks
|
647
|
+
system("taskkill /F /T /IM headless.exe 1>nul 2>nul")
|
648
|
+
system("taskkill /F /T /IM multilogin.exe 1>nul 2>nul")
|
649
|
+
|
650
|
+
# this is to run many mlalisteners in paralell
|
651
|
+
# for more information refer to #166:doc:
|
652
|
+
# https://github.com/leandrosardi/blackstack/issues/166
|
653
|
+
f.flock(File::LOCK_UN)
|
654
|
+
end
|
655
|
+
|
656
|
+
# start headliness.exe, and relaunch it if it shuts down.
|
657
|
+
def self.mla_listener(h)
|
658
|
+
port = h[:port]
|
659
|
+
verbose = h[:verbose]
|
660
|
+
|
661
|
+
port = 45000 if port.nil?
|
662
|
+
verbose = true if verbose.nil?
|
663
|
+
begin
|
664
|
+
while (true)
|
665
|
+
puts 'verbose mode is active.' if verbose
|
666
|
+
puts "listening port #{port.to_s}." if verbose
|
667
|
+
start_time = Time.now
|
668
|
+
BlackStack::BrowserFactory::mla_kill
|
669
|
+
if (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
|
670
|
+
system("headless.exe -port #{port} 1>nul 2>nul")
|
671
|
+
else
|
672
|
+
# TODO: parametrizar la ruta de headless.sh
|
673
|
+
system("~/code/multilogin/headless.sh -port #{port}") # multilogin folder is uncompressed in the ~/code/ folder
|
674
|
+
end
|
675
|
+
#system("headless.exe -port #{port} 1>nul 2>nul")
|
676
|
+
end_time = Time.now
|
677
|
+
enlapsed_seconds = end_time - start_time
|
678
|
+
poll_seconds = 5 # parser.value('min_cycle_seconds')
|
679
|
+
sleep( poll_seconds - enlapsed_seconds ) if poll_seconds > enlapsed_seconds
|
680
|
+
end
|
681
|
+
rescue Interrupt => e
|
682
|
+
puts 'process interruption' if verbose
|
683
|
+
# stop this while(true) loop
|
684
|
+
rescue => e
|
685
|
+
puts "unexpected error: #{e.to_s}" if verbose
|
686
|
+
# nothing to do here.
|
687
|
+
# just restart the while
|
688
|
+
ensure
|
689
|
+
BlackStack::BrowserFactory::mla_kill
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
|
344
694
|
# Returns the description string from a browser type code
|
345
695
|
def self.typeDesc(type)
|
346
696
|
return 'Chrome' if @@type == BlackStack::BrowserFactory::TYPE_CHROME
|
@@ -386,23 +736,29 @@ module BlackStack
|
|
386
736
|
# For more information, read documentations about PROFILE_PID_LIST_FILENAME.
|
387
737
|
# This method is used for chrome browsers only.
|
388
738
|
def self.readPidToProfileList(profile_name)
|
389
|
-
if
|
390
|
-
|
391
|
-
|
392
|
-
|
739
|
+
if @@browser.pampa_integration
|
740
|
+
if profile_name != nil
|
741
|
+
fname = PROFILE_PID_LIST_FILENAME.gsub('%PROFILE_NAME%', profile_name)
|
742
|
+
s = File.read(fname)
|
743
|
+
return s.split(",")
|
744
|
+
else
|
745
|
+
return []
|
746
|
+
end
|
393
747
|
else
|
394
748
|
return []
|
395
|
-
end
|
749
|
+
end # if @@browser.pampa_integration
|
396
750
|
end
|
397
751
|
|
398
752
|
# Add the PID of this browser; and the PID of all the child processes of this, with name like /chrome\.exe/.
|
399
753
|
# For more information, read documentations about PROFILE_PID_LIST_FILENAME.
|
400
754
|
# This method is used for chrome browsers only.
|
401
755
|
def self.updateProfileList
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
756
|
+
if @@browser.pampa_integration
|
757
|
+
addPidToProfileList(@@profile_name, @@pid)
|
758
|
+
PROCESS.list().select { |p| p[:ppid] == @@pid && p[:executablepath] =~ /chrome\.exe/ }.each { |p2|
|
759
|
+
addPidToProfileList(@@profile_name, p2[:pid])
|
760
|
+
}
|
761
|
+
end # if @@browser.pampa_integration
|
406
762
|
end
|
407
763
|
|
408
764
|
# Returns true if @@browser is not nil, or @@profile_name is not nil.
|
@@ -426,21 +782,25 @@ module BlackStack
|
|
426
782
|
|
427
783
|
# Retorna true si el perfil en @@profile_name esta siendo trabajado por este proceso
|
428
784
|
def self.itIsMe?
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
785
|
+
if @@browser.pampa_integration
|
786
|
+
# el archivo tiene una unica primera linea de ancho fijo
|
787
|
+
line_chars = 100
|
788
|
+
# obtengo el id de este proceso que esta corriendo
|
789
|
+
my_pid = PROCESS.pid
|
790
|
+
# bloqueo el archivo de este perfil
|
791
|
+
@@fd_profile_lock.flock(File::LOCK_EX) if !@@fd_profile_lock.nil?
|
792
|
+
# valido que my_pid no tenga mas caracteres que al ancho fijo
|
793
|
+
raise 'Cannot work browser profile lockfile because the lenght if the id of this process is larger than the max number of chars allowed (#{line_chars.to_s})' if line_chars < my_pid.size
|
794
|
+
# obtengo el id del proceso que se reservo este perfil
|
795
|
+
@@fd_profile_lock.seek(-line_chars, IO::SEEK_CUR) if !@@fd_profile_lock.nil?
|
796
|
+
lock_pid = @@fd_profile_lock.read.to_s
|
797
|
+
# libero el archivo de este perfil
|
798
|
+
@@fd_profile_lock.flock(File::LOCK_UN) if !@@fd_profile_lock.nil?
|
799
|
+
# return
|
800
|
+
return my_pid.strip.upcase == lock_pid.strip.upcase
|
801
|
+
else
|
802
|
+
return false
|
803
|
+
end
|
444
804
|
end
|
445
805
|
|
446
806
|
# This method is to open no more than one browser of the same profile at time.
|
@@ -449,41 +809,43 @@ module BlackStack
|
|
449
809
|
# If the content of the file is different than the PID of this process, but such content is not equal to the PID of any other process alive, so this process is good to start the browser.
|
450
810
|
# If the content of the file is different than the PID of this process, and such content is equal to the PID of another process alive, this method unlock the file and will raise an exception.
|
451
811
|
def self.lockProfile
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
@@fd_profile_lock.write ' ' * (line_chars - my_pid.size)
|
466
|
-
@@fd_profile_lock.flush
|
467
|
-
elsif lock_pid.strip.upcase != my_pid.strip.upcase
|
468
|
-
# verifico si el proceso que reservo este perfil esta activo
|
469
|
-
p = PROCESS.list().select { |h| h[:pid].strip.upcase == lock_pid.strip.upcase }.first
|
470
|
-
lock_process_is_alive = !p.nil?
|
471
|
-
# levanto una excepcion de bloqueo si este perfil fue reservado por otro proceso y el proceso sigue activo
|
472
|
-
if lock_process_is_alive
|
473
|
-
# libero el archivo de este perfil
|
474
|
-
@@fd_profile_lock.flock(File::LOCK_UN)
|
475
|
-
#
|
476
|
-
raise 'Profile is locked'
|
477
|
-
# me reservo este perfil si fue reservado por un proceso que ya no esta activo
|
478
|
-
else # if lock_process_is_alive
|
479
|
-
@@fd_profile_lock.seek(-line_chars, IO::SEEK_CUR)
|
812
|
+
if @@browser.pampa_integration
|
813
|
+
# el archivo tiene una unica primera linea de ancho fijo
|
814
|
+
line_chars = 100
|
815
|
+
# bloqueo el archivo de este perfil
|
816
|
+
@@fd_profile_lock.flock(File::LOCK_EX)
|
817
|
+
# obtengo el id de este proceso que esta corriendo
|
818
|
+
my_pid = PROCESS.pid
|
819
|
+
# valido que my_pid no tenga mas caracteres que al ancho fijo
|
820
|
+
raise 'Cannot work browser profile lockfile because the lenght if the id of this process is larger than the max number of chars allowed (#{line_chars.to_s})' if line_chars < my_pid.size
|
821
|
+
# obtengo el id del proceso que se reservo este perfil
|
822
|
+
lock_pid = @@fd_profile_lock.read.to_s
|
823
|
+
#
|
824
|
+
if lock_pid.size == 0
|
480
825
|
@@fd_profile_lock.write my_pid
|
481
826
|
@@fd_profile_lock.write ' ' * (line_chars - my_pid.size)
|
482
827
|
@@fd_profile_lock.flush
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
828
|
+
elsif lock_pid.strip.upcase != my_pid.strip.upcase
|
829
|
+
# verifico si el proceso que reservo este perfil esta activo
|
830
|
+
p = PROCESS.list().select { |h| h[:pid].strip.upcase == lock_pid.strip.upcase }.first
|
831
|
+
lock_process_is_alive = !p.nil?
|
832
|
+
# levanto una excepcion de bloqueo si este perfil fue reservado por otro proceso y el proceso sigue activo
|
833
|
+
if lock_process_is_alive
|
834
|
+
# libero el archivo de este perfil
|
835
|
+
@@fd_profile_lock.flock(File::LOCK_UN)
|
836
|
+
#
|
837
|
+
raise 'Profile is locked'
|
838
|
+
# me reservo este perfil si fue reservado por un proceso que ya no esta activo
|
839
|
+
else # if lock_process_is_alive
|
840
|
+
@@fd_profile_lock.seek(-line_chars, IO::SEEK_CUR)
|
841
|
+
@@fd_profile_lock.write my_pid
|
842
|
+
@@fd_profile_lock.write ' ' * (line_chars - my_pid.size)
|
843
|
+
@@fd_profile_lock.flush
|
844
|
+
end # if lock_process_is_alive
|
845
|
+
else # lock_pid.strip.upcase == my_pid.strip.upcase
|
846
|
+
# go ahead
|
847
|
+
end # if lock_pid.size == 0
|
848
|
+
end # if @@browser.pampa_integration
|
487
849
|
end # def self.lockProfile()
|
488
850
|
|
489
851
|
# This method is to open no more than one browser of the same profile at time.
|
@@ -541,101 +903,82 @@ module BlackStack
|
|
541
903
|
|
542
904
|
# Close the browser only if itIsMe? returns true.
|
543
905
|
# Set nil to @@browser, @@pid, @@profile_name, @@driver, @@type.
|
544
|
-
def self.destroy
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
#end
|
906
|
+
def self.destroy(clear_variables=true)
|
907
|
+
begin
|
908
|
+
# borro el contenido de PROFILE_LOCK_FILENAME
|
909
|
+
#if !@@profile_name.nil?
|
910
|
+
# fname = PROFILE_LOCK_FILENAME.gsub('%PROFILE_NAME%', @@profile_name)
|
911
|
+
# File.open(fname,'w')
|
912
|
+
#end
|
552
913
|
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
914
|
+
#
|
915
|
+
if @@type == BlackStack::BrowserFactory::TYPE_CHROME
|
916
|
+
# kill all child process
|
917
|
+
self.lockProfileList()
|
918
|
+
self.readPidToProfileList(@@profile_name).each { |pid|
|
919
|
+
MyProcess.kill(pid)
|
920
|
+
self.removePidToProfileList(@@profile_name, pid)
|
921
|
+
}
|
922
|
+
self.releaseProfileList()
|
562
923
|
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
924
|
+
begin
|
925
|
+
@@browser.close if !@@browser.nil?
|
926
|
+
rescue
|
927
|
+
end
|
567
928
|
|
568
|
-
|
569
|
-
=
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
929
|
+
elsif @@type == BlackStack::BrowserFactory::TYPE_MIMIC
|
930
|
+
i = 0
|
931
|
+
body = nil
|
932
|
+
max = 90
|
933
|
+
success = 0 # I have to call this access point many times to get the browser closed.
|
934
|
+
max_success_required = 30
|
935
|
+
while i<max && success<max_success_required
|
936
|
+
i+=1
|
937
|
+
# cierro el perfil por si estaba corriendo
|
938
|
+
if BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_4
|
939
|
+
url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
|
940
|
+
ssl = true
|
941
|
+
elsif BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_5
|
942
|
+
url = "http://localhost.multiloginapp.com:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
|
943
|
+
ssl = false
|
944
|
+
else
|
945
|
+
raise 'Invalid Multilogin Version'
|
583
946
|
end
|
584
|
-
=
|
947
|
+
uri = URI.parse(url)
|
585
948
|
=begin
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
print 'Close browser... '
|
593
|
-
BlackStack::BrowserFactory.mimic(@@profile_name)
|
594
|
-
puts 'done'
|
595
|
-
print 'Close browser (2)... '
|
596
|
-
@@browser.close
|
597
|
-
puts 'done'
|
598
|
-
else
|
599
|
-
puts 'no'
|
600
|
-
end
|
601
|
-
rescue
|
602
|
-
# nothing here
|
949
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => ssl, :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
|
950
|
+
req = Net::HTTP::Get.new(uri)
|
951
|
+
req['Content-Type'] = 'application/json'
|
952
|
+
if http.request(req).body
|
953
|
+
body = http.request(req).body
|
954
|
+
end
|
603
955
|
end
|
604
956
|
=end
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
#
|
612
|
-
|
613
|
-
|
614
|
-
#
|
615
|
-
uri = URI.parse(url)
|
616
|
-
body = Net::HTTP.get(uri)
|
617
|
-
res = JSON.parse(body)
|
618
|
-
success += 1 if res['status'].to_s == 'OK'
|
619
|
-
#puts "response: #{res['status'].to_s}"
|
620
|
-
end # while i<max && && success<max_success_required
|
621
|
-
raise "Error requesting Mimic to stop (tried #{i.to_s} times)" if success<max_success_required
|
622
|
-
|
623
|
-
else
|
624
|
-
raise 'Cannot destroy browser due unknown browser type'
|
625
|
-
end
|
626
|
-
rescue => e
|
627
|
-
self.release
|
628
|
-
raise e
|
957
|
+
body = Net::HTTP.get(uri)
|
958
|
+
res = JSON.parse(body)
|
959
|
+
success += 1 if res['status'].to_s == 'OK'
|
960
|
+
#puts "i:#{i.to_s}:."
|
961
|
+
#puts "body:#{body.to_s}:."
|
962
|
+
#puts "res.status:#{res['status'].to_s}:."
|
963
|
+
end # while i<max && && success<max_success_required
|
964
|
+
raise "Error requesting Mimic to stop (tried #{i.to_s} times)" if success<max_success_required
|
965
|
+
else
|
966
|
+
# unknown browser type
|
629
967
|
end
|
630
|
-
|
968
|
+
rescue => e
|
969
|
+
self.release
|
970
|
+
raise e
|
971
|
+
end
|
631
972
|
|
632
|
-
# reseteo las variables
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
973
|
+
# reseteo las variables
|
974
|
+
if clear_variables
|
975
|
+
@@browser = nil
|
976
|
+
@@pid = nil
|
977
|
+
@@profile_name = nil
|
978
|
+
@@driver = nil
|
979
|
+
@@type = nil
|
980
|
+
end
|
981
|
+
end # if self.destroy
|
639
982
|
|
640
983
|
# Returns the version of the availalble chrome browser.
|
641
984
|
def self.chrome_version
|
@@ -660,7 +1003,7 @@ puts 'no'
|
|
660
1003
|
end
|
661
1004
|
|
662
1005
|
# Launch a Mimic browser.
|
663
|
-
def self.mimic(mimic_profile_id)
|
1006
|
+
def self.mimic(mimic_profile_id, load_timeout=DEFAULT_LOAD_TIMEOUT)
|
664
1007
|
#
|
665
1008
|
@@type = BlackStack::BrowserFactory::TYPE_MIMIC
|
666
1009
|
@@profile_name = mimic_profile_id
|
@@ -684,13 +1027,29 @@ puts 'no'
|
|
684
1027
|
i += 1
|
685
1028
|
|
686
1029
|
# cierro el perfil por si estaba corriendo
|
687
|
-
|
688
|
-
|
1030
|
+
if BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_4
|
1031
|
+
url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
|
1032
|
+
ssl = true
|
1033
|
+
elsif BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_5
|
1034
|
+
url = "http://localhost.multiloginapp.com:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/stop?profileId=#{@@profile_name}"
|
1035
|
+
ssl = false
|
1036
|
+
else
|
1037
|
+
raise 'Invalid Multilogin Version'
|
1038
|
+
end
|
1039
|
+
uri = URI.parse(url)
|
689
1040
|
body = Net::HTTP.get(uri)
|
690
1041
|
res = JSON.parse(body)
|
691
1042
|
|
692
|
-
|
693
|
-
|
1043
|
+
if BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_4
|
1044
|
+
url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/start?automation=true&profileId=#{@@profile_name}"
|
1045
|
+
ssl = true
|
1046
|
+
elsif BlackStack::StealthBrowserAutomation::Multilogin::mla_version == BlackStack::StealthBrowserAutomation::Multilogin::VERSION_5
|
1047
|
+
url = "http://localhost.multiloginapp.com:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/start?automation=true&profileId=#{@@profile_name}"
|
1048
|
+
ssl = false
|
1049
|
+
else
|
1050
|
+
raise 'Invalid Multilogin Version'
|
1051
|
+
end
|
1052
|
+
uri = URI.parse(url)
|
694
1053
|
body = Net::HTTP.get(uri)
|
695
1054
|
res = JSON.parse(body)
|
696
1055
|
|
@@ -714,6 +1073,7 @@ puts 'no'
|
|
714
1073
|
# http.request(req_2)
|
715
1074
|
#}
|
716
1075
|
@@driver = Selenium::WebDriver.for(:remote, :url => url_2)
|
1076
|
+
@@driver.manage.timeouts.page_load = load_timeout
|
717
1077
|
#bridge = @@driver.instance_variable_get(:@bridge)
|
718
1078
|
#service = bridge.instance_variable_get(:@service)
|
719
1079
|
#process = service.instance_variable_get(:@process)
|
@@ -761,17 +1121,22 @@ puts 'no'
|
|
761
1121
|
h[:load_timeout].nil? ? DEFAULT_LOAD_TIMEOUT : h[:load_timeout],
|
762
1122
|
h[:user_agent],
|
763
1123
|
h[:profile_name],
|
764
|
-
h[:chromedriver_path]
|
1124
|
+
h[:chromedriver_path],
|
1125
|
+
h[:pampa_integration],
|
1126
|
+
h[:keystroke_signature],
|
1127
|
+
h[:mouse_signature]
|
765
1128
|
)
|
766
1129
|
end
|
767
1130
|
|
768
1131
|
# Launch a chrome browser.
|
769
|
-
def self.launch_chrome(proxy=nil, load_timeout=DEFAULT_LOAD_TIMEOUT, user_agent=nil, profile_name=nil, chromedriver_path=nil)
|
1132
|
+
def self.launch_chrome(proxy=nil, load_timeout=DEFAULT_LOAD_TIMEOUT, user_agent=nil, profile_name=nil, chromedriver_path=nil, pampa_integration=true, keystroke_signature=nil, mouse_signature=nil)
|
770
1133
|
#
|
771
1134
|
@@browser = nil
|
772
1135
|
@@driver = nil
|
773
1136
|
@@type = BlackStack::BrowserFactory::TYPE_CHROME
|
774
1137
|
@@profile_name = profile_name
|
1138
|
+
self.set_keystroke_signature(keystroke_signature)
|
1139
|
+
self.set_mouse_signature(mouse_signature)
|
775
1140
|
|
776
1141
|
#
|
777
1142
|
#fname = PROFILE_LOCK_FILENAME.gsub('%PROFILE_NAME%', @@profile_name)
|
@@ -789,14 +1154,16 @@ puts 'no'
|
|
789
1154
|
while (@@browser == nil)
|
790
1155
|
|
791
1156
|
begin
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
1157
|
+
if pampa_integration
|
1158
|
+
# Levantar el flag de reserva a mi favor
|
1159
|
+
self.lock()
|
1160
|
+
|
1161
|
+
#
|
1162
|
+
#self.lockProfile()
|
1163
|
+
|
1164
|
+
#
|
1165
|
+
self.lockProfileList()
|
1166
|
+
end # pampa_integration
|
800
1167
|
|
801
1168
|
#
|
802
1169
|
client = Selenium::WebDriver::Remote::Http::Default.new
|
@@ -838,29 +1205,34 @@ puts 'no'
|
|
838
1205
|
|
839
1206
|
Selenium::WebDriver::Chrome.driver_path = chromedriver_path if !chromedriver_path.nil?
|
840
1207
|
Selenium::WebDriver::Chrome.driver_path = DEFAULT_CHROMEDRIVER_PATH if chromedriver_path.nil?
|
841
|
-
|
1208
|
+
|
842
1209
|
@@driver = Selenium::WebDriver.for :chrome, :switches => switches
|
843
1210
|
bridge = @@driver.instance_variable_get(:@bridge)
|
844
1211
|
service = bridge.instance_variable_get(:@service)
|
845
1212
|
process = service.instance_variable_get(:@process)
|
846
|
-
@@pid = process.pid.to_s # es el PID del chromedriver
|
1213
|
+
# @@pid = process.pid.to_s # es el PID del chromedriver # TODO: hacer andar esto con selenium 3.14.1
|
847
1214
|
@@driver.manage.window().maximize()
|
848
1215
|
@@browser = PampaBrowser.new(@@driver)
|
849
1216
|
@@browser.proxy = proxy.clone if !proxy.nil?
|
850
1217
|
@@browser.agent_name = agent
|
851
1218
|
@@browser.profile_name = profile_name
|
1219
|
+
@@browser.pampa_integration = pampa_integration
|
852
1220
|
self.updateProfileList
|
853
|
-
|
1221
|
+
|
854
1222
|
#
|
855
|
-
rescue => e
|
856
|
-
#
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
1223
|
+
rescue => e
|
1224
|
+
# TODO: Se atrapa una expecion porque sigue ocurreiendo el error reportado en el issue #134 y #129
|
1225
|
+
if !@@browser.nil?
|
1226
|
+
if @@browser.pampa_integration
|
1227
|
+
p1 = PROCESS.list().select { |p| p[:ppid] == PROCESS.pid && p[:executablepath] =~ /chromedriver\.exe/ }.first
|
1228
|
+
if (p1 != nil)
|
1229
|
+
@@pid = p1[:pid]
|
1230
|
+
self.updateProfileList
|
1231
|
+
end
|
1232
|
+
self.releaseProfileList()
|
1233
|
+
end
|
1234
|
+
end
|
1235
|
+
|
864
1236
|
#
|
865
1237
|
#self.releaseProfile()
|
866
1238
|
|