stealth_browser_automation 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/baseproxy.rb +33 -0
- data/lib/browserfactory.rb +498 -0
- data/lib/proxy.rb +80 -0
- data/lib/proxyprovider.rb +8 -0
- data/lib/remoteproxy.rb +9 -0
- data/lib/stealth_browser_automation.rb +73 -0
- metadata +250 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bcd01c1ba08e79360a113829c62e85d65ae8f414
|
4
|
+
data.tar.gz: 4701e9a79e13ab2111d83f8c48a722eab6245f37
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 63de89c480ebcd7aa799f6a6fd32b7a8759d29454929f8941a74601bdac2b6be35356240ecd48fb1edf712e0a8281921ad9e1339794cf2628bfdca7ee9b82c9c
|
7
|
+
data.tar.gz: 27d27a9e215ae62df3eacfa9175ad756f73ed1c77e33ca8b236caf7cd458c7549df14fb96823d7914b17bd737b4c5c2e13b090f1a3b85647fba06404f767f76e
|
data/lib/baseproxy.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module BlackStack
|
2
|
+
|
3
|
+
module BaseProxy
|
4
|
+
|
5
|
+
#
|
6
|
+
def chrome_switches
|
7
|
+
return ["--proxy-server=#{self.ip}:#{self.port}","--proxy-user-and-password=#{self.user}:#{self.password}"]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Warning: Out to Date
|
11
|
+
def firefox_profile_parameter(agent_name = nil, profile_name = nil)
|
12
|
+
if (profile_name == nil)
|
13
|
+
profile = Selenium::WebDriver::Firefox::Profile.new
|
14
|
+
else
|
15
|
+
profile = Selenium::WebDriver::Firefox::Profile.from_name profile_name
|
16
|
+
end
|
17
|
+
|
18
|
+
if (agent_name!=nil)
|
19
|
+
profile['general.useragent.override'] = agent_name
|
20
|
+
end
|
21
|
+
|
22
|
+
proxy = Selenium::WebDriver::Proxy.new(:http => self.ip.to_s+":"+self.port.to_s, :ssl => self.ip.to_s+":"+self.port.to_s)
|
23
|
+
profile.proxy = proxy
|
24
|
+
return profile
|
25
|
+
end
|
26
|
+
|
27
|
+
# Warning: Out to Date
|
28
|
+
def phantomjs_switches
|
29
|
+
return ['--proxy='+self.ip.to_s+':'+self.port.to_s, '--proxy-auth='+self.user.to_s+':'+self.password.to_s, '--ignore-ssl-errors=yes', '--ssl-protocol=any', '--load-images=false']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end # module BlackStack
|
@@ -0,0 +1,498 @@
|
|
1
|
+
require 'watir-webdriver'
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module BlackStack
|
5
|
+
|
6
|
+
class PampaBrowser < Watir::Browser
|
7
|
+
|
8
|
+
attr_accessor :proxy, :lnuser, :agent_name, :profile_name
|
9
|
+
|
10
|
+
def notify(method)
|
11
|
+
url = "#{BlackStack::Pampa::api_protocol}://#{PROCESS.ws_url}:#{PROCESS.ws_port}/api1.3/pampa/browser/notify.json"
|
12
|
+
res = BlackStack::Netting::call_post(url, {
|
13
|
+
:api_key => BlackStack::Pampa::api_key,
|
14
|
+
:filename => $0,
|
15
|
+
:method => method,
|
16
|
+
:worker_name => PROCESS.fullWorkerName,
|
17
|
+
:id_proxy => self.proxy.nil? ? nil : self.proxy.id,
|
18
|
+
:id_lnuser => self.lnuser.nil? ? nil : self.lnuser.id,
|
19
|
+
:worker_assigned_process => PROCESS.worker.assigned_process,
|
20
|
+
:profile_name => self.profile_name,
|
21
|
+
:agent_name => self.agent_name,
|
22
|
+
})
|
23
|
+
parsed = JSON.parse(res.body)
|
24
|
+
if parsed['status'] != "success"
|
25
|
+
raise "Error Tracing BrowserActivity: #{parsed['status']}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def push_screenshot(filename=nil)
|
30
|
+
filename = "#{PROCESS.fullWorkerName}.png" if filename.nil?
|
31
|
+
BrowserFactory::screenshot(filename)
|
32
|
+
|
33
|
+
image = MiniMagick::Image.open(filename)
|
34
|
+
w = (image[:width].to_f * 0.25).to_i
|
35
|
+
h = (image[:height].to_f * 0.25).to_i
|
36
|
+
image.resize "#{w.to_s}x#{h.to_s}"
|
37
|
+
image.format "png"
|
38
|
+
image.write filename
|
39
|
+
|
40
|
+
url = "#{BlackStack::Pampa::api_protocol}://#{PROCESS.ws_url}:#{PROCESS.ws_port}/api1.3/pampa/browser/screenshot.json"
|
41
|
+
res = RestClient::Request.execute(
|
42
|
+
:api_key => BlackStack::Pampa::api_key,
|
43
|
+
:verify_ssl => false,
|
44
|
+
:url => url,
|
45
|
+
:method => :post,
|
46
|
+
:headers => {
|
47
|
+
:accept => 'application/json',
|
48
|
+
:params => {
|
49
|
+
:api_key => BlackStack::Pampa.api_key,
|
50
|
+
:filename => filename,
|
51
|
+
},
|
52
|
+
},
|
53
|
+
:payload => {
|
54
|
+
:file => File.new(filename, "r"),
|
55
|
+
:multipart => false,
|
56
|
+
}
|
57
|
+
)
|
58
|
+
parsed = JSON.parse(res.body)
|
59
|
+
if parsed['status'] != "success"
|
60
|
+
raise "Error Uploading Screenshot: #{parsed['status']}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def back
|
65
|
+
self.notify("back")
|
66
|
+
super
|
67
|
+
self.push_screenshot
|
68
|
+
end
|
69
|
+
|
70
|
+
def forward
|
71
|
+
self.notify("forward")
|
72
|
+
super
|
73
|
+
self.push_screenshot
|
74
|
+
end
|
75
|
+
|
76
|
+
def execute_script(script, *args)
|
77
|
+
self.notify("execute_script")
|
78
|
+
super
|
79
|
+
self.push_screenshot
|
80
|
+
end
|
81
|
+
|
82
|
+
def goto(url)
|
83
|
+
self.notify("goto")
|
84
|
+
super
|
85
|
+
self.push_screenshot
|
86
|
+
end
|
87
|
+
|
88
|
+
def screenshot
|
89
|
+
self.notify("screenshot")
|
90
|
+
super
|
91
|
+
end
|
92
|
+
|
93
|
+
def reset!
|
94
|
+
self.notify("reset!")
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def refresh
|
99
|
+
self.notify("refresh")
|
100
|
+
super
|
101
|
+
self.push_screenshot
|
102
|
+
end
|
103
|
+
|
104
|
+
def inspect
|
105
|
+
self.notify("inspect")
|
106
|
+
super
|
107
|
+
end
|
108
|
+
|
109
|
+
=begin # se da de baja, porque incremente el # de llamadas a la API de 800/hora a 5000/hora, y eso requiere mas infraestructura
|
110
|
+
def send_keys(*args)
|
111
|
+
self.notify("send_keys")
|
112
|
+
super
|
113
|
+
end
|
114
|
+
=end
|
115
|
+
|
116
|
+
def text
|
117
|
+
self.notify("text")
|
118
|
+
super
|
119
|
+
end
|
120
|
+
|
121
|
+
def title
|
122
|
+
self.notify("title")
|
123
|
+
super
|
124
|
+
end
|
125
|
+
|
126
|
+
def url
|
127
|
+
self.notify("url")
|
128
|
+
super
|
129
|
+
end
|
130
|
+
|
131
|
+
def wait(timeout = 5)
|
132
|
+
self.notify("wait")
|
133
|
+
super
|
134
|
+
end
|
135
|
+
end # class PampaBrowser
|
136
|
+
|
137
|
+
|
138
|
+
class BrowserFactory
|
139
|
+
LOCKING_FILENAME = BlackStack::MyProcess.macaddress # manejo de concurrencia en la creación de browsers
|
140
|
+
PROFILE_PID_LIST_FILENAME = "./browserfactory.%PRFILE_NAME%.list" # manejo de concurrencia en la creación de browsers
|
141
|
+
PROFILE_PID_LOCK_FILENAME = "./browserfactory.%PRFILE_NAME%.lock" # manejo de concurrencia en la creación de browsers
|
142
|
+
CHROME_EXTENSION_DIRECTORY = File.absolute_path('./chrome_extension')
|
143
|
+
DEFAULT_LOAD_TIMEOUT = 360
|
144
|
+
DEFAULT_CHROMEDRIVER_PATH = 'chromedriver.exe'
|
145
|
+
|
146
|
+
def self.addPidToProfileList(profile_name, pid)
|
147
|
+
fname = PROFILE_PID_LIST_FILENAME.gsub('%PRFILE_NAME%', profile_name)
|
148
|
+
f = File.open(fname, 'a')
|
149
|
+
f.write("#{pid},")
|
150
|
+
f.close
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.removePidToProfileList(profile_name, pid)
|
154
|
+
fname = PROFILE_PID_LIST_FILENAME.gsub('%PRFILE_NAME%', profile_name)
|
155
|
+
s = File.read(fname)
|
156
|
+
s = s.gsub("#{pid},", "")
|
157
|
+
f = File.open(fname, 'w')
|
158
|
+
f.write(s)
|
159
|
+
f.close
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.readPidToProfileList(profile_name)
|
163
|
+
if (profile_name != nil)
|
164
|
+
fname = PROFILE_PID_LIST_FILENAME.gsub('%PRFILE_NAME%', profile_name)
|
165
|
+
s = File.read(fname)
|
166
|
+
return s.split(",")
|
167
|
+
else
|
168
|
+
return []
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def self.updateProfileList()
|
173
|
+
addPidToProfileList(@@profile_name, @@pid)
|
174
|
+
PROCESS.list().select { |p| p[:ppid] == @@pid && p[:executablepath] =~ /chrome\.exe/ }.each { |p2|
|
175
|
+
addPidToProfileList(@@profile_name, p2[:pid])
|
176
|
+
}
|
177
|
+
end
|
178
|
+
|
179
|
+
TYPE_PHANTOMJS = 0 # discontinued!
|
180
|
+
TYPE_FIREFOX = 1
|
181
|
+
TYPE_CHROME = 2
|
182
|
+
|
183
|
+
@@fd = File.open(LOCKING_FILENAME,"w")
|
184
|
+
@@fd_profile = nil
|
185
|
+
@@driver = nil
|
186
|
+
@@browser = nil
|
187
|
+
@@pid = nil
|
188
|
+
@@profile_name = nil
|
189
|
+
|
190
|
+
|
191
|
+
def self.lockProfileList()
|
192
|
+
# @@fd_profile.flock(File::LOCK_EX)
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.releaseProfileList()
|
196
|
+
# @@fd_profile.flock(File::LOCK_UN)
|
197
|
+
=begin
|
198
|
+
begin
|
199
|
+
@@fd_profile.close
|
200
|
+
rescue
|
201
|
+
end
|
202
|
+
@@fd_profile = nil
|
203
|
+
=end
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
# NOTA: esta lista debe estar permitida por LinkedIn. De caso contrario, aparecera el mensaje "Upgrad your browser"
|
208
|
+
# Se puede obtener una lista actualizada de este sitio: https://techblog.willshouse.com/2012/01/03/most-common-user-agents/
|
209
|
+
@@arAgents = [
|
210
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
211
|
+
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
212
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0",
|
213
|
+
"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36",
|
214
|
+
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:56.0) Gecko/20100101 Firefox/56.0",
|
215
|
+
"Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
|
216
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063",
|
217
|
+
]
|
218
|
+
|
219
|
+
# toma una captura del area visible de la pagina
|
220
|
+
# mas informacion: https://stackoverflow.com/questions/25543651/screenshot-of-entire-webpage-using-selenium-webdriver-rc-rails
|
221
|
+
def self.screenshot(filename)
|
222
|
+
@@driver.save_screenshot(filename)
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.agents()
|
226
|
+
return @@arAgents
|
227
|
+
end
|
228
|
+
|
229
|
+
def self.getPid()
|
230
|
+
return @@pid
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.isCreated()
|
234
|
+
if (self.readPidToProfileList(@@profile_name).size>0)
|
235
|
+
return true
|
236
|
+
end
|
237
|
+
if (@@browser != nil)
|
238
|
+
return true
|
239
|
+
end
|
240
|
+
|
241
|
+
return false
|
242
|
+
end
|
243
|
+
|
244
|
+
def self.isCreated?
|
245
|
+
self.isCreated()
|
246
|
+
end
|
247
|
+
|
248
|
+
def self.reservation_id()
|
249
|
+
q = "SELECT TOP 1 reservation_id FROM browserfactory WHERE host_name='#{Socket.gethostname}'"
|
250
|
+
row = DB[q].first
|
251
|
+
if (row == nil)
|
252
|
+
DB.execute("INSERT INTO browserfactory (reservation_id, reservation_time, host_name) VALUES (NULL, NULL, '#{Socket.gethostname}')")
|
253
|
+
end
|
254
|
+
row = DB[q].first
|
255
|
+
return row[:reservation_id]
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.lock()
|
259
|
+
@@fd.flock(File::LOCK_EX)
|
260
|
+
end
|
261
|
+
|
262
|
+
def self.release()
|
263
|
+
@@fd.flock(File::LOCK_UN)
|
264
|
+
end
|
265
|
+
|
266
|
+
def self.destroy()
|
267
|
+
self.lockProfileList()
|
268
|
+
self.readPidToProfileList(@@profile_name).each { |pid|
|
269
|
+
MyProcess.kill(pid)
|
270
|
+
self.removePidToProfileList(@@profile_name, pid)
|
271
|
+
}
|
272
|
+
self.releaseProfileList()
|
273
|
+
begin
|
274
|
+
if (@@browser != nil)
|
275
|
+
@@browser.close
|
276
|
+
else
|
277
|
+
end
|
278
|
+
rescue => e
|
279
|
+
#
|
280
|
+
end
|
281
|
+
# reseteo las variables
|
282
|
+
@@browser = nil
|
283
|
+
@@pid = nil
|
284
|
+
@@profile_name = nil
|
285
|
+
@@driver = nil
|
286
|
+
@@type = nil
|
287
|
+
end
|
288
|
+
|
289
|
+
#
|
290
|
+
def self.chrome_version
|
291
|
+
res = `reg query \"HKEY_CURRENT_USER\\Software\\Google\\Chrome\\BLBeacon\" /v version`.scan(/REG_SZ (.*)$/).first
|
292
|
+
return nil if res.nil?
|
293
|
+
return res[0] if res.size > 0
|
294
|
+
return nil
|
295
|
+
end
|
296
|
+
|
297
|
+
#
|
298
|
+
def self.chromedriver_version(filename=nil)
|
299
|
+
res = `chromedriver -v`.scan(/ChromeDriver\s(.*)\s\(/).first if filename.nil?
|
300
|
+
res = `#{filename} -v`.scan(/ChromeDriver\s(.*)\s\(/).first if !filename.nil?
|
301
|
+
return nil if res.nil?
|
302
|
+
return res[0] if res.size > 0
|
303
|
+
return nil
|
304
|
+
end
|
305
|
+
|
306
|
+
#
|
307
|
+
def self.chrome(h)
|
308
|
+
# TODO: check if h is a hash
|
309
|
+
# TODO: check the variable type of each param
|
310
|
+
# TODO: check mandatory params
|
311
|
+
self.launch_chrome(
|
312
|
+
h[:proxy],
|
313
|
+
h[:load_timeout].nil? ? DEFAULT_LOAD_TIMEOUT : h[:load_timeout],
|
314
|
+
h[:user_agent],
|
315
|
+
h[:profile_name],
|
316
|
+
h[:chromedriver_path],
|
317
|
+
h[:chromedriver_signature]
|
318
|
+
)
|
319
|
+
end
|
320
|
+
|
321
|
+
def self.mimic(mimic_profile_id)
|
322
|
+
begin
|
323
|
+
# Levantar el flag de reserva a mi favor
|
324
|
+
self.lock()
|
325
|
+
|
326
|
+
@@profile_name = mimic_profile_id
|
327
|
+
url_2 = nil
|
328
|
+
i = 0
|
329
|
+
max = 2
|
330
|
+
while (i<max && url_2.nil?)
|
331
|
+
i += 1
|
332
|
+
url = "http://127.0.0.1:#{BlackStack::StealthBrowserAutomation::Multilogin::mla_local_port}/api/v1/profile/start?automation=true&profileId=#{@@profile_name}"
|
333
|
+
uri = URI.parse(url)
|
334
|
+
req = Net::HTTP::Get.new(url)
|
335
|
+
res = Net::HTTP.start(uri.host, uri.port, :use_ssl => true, :verify_mode => OpenSSL::SSL::VERIFY_NONE) {|http|
|
336
|
+
http.request(req)
|
337
|
+
}
|
338
|
+
res = JSON.parse(res.body)
|
339
|
+
if !res.has_key?('status')
|
340
|
+
if i<max
|
341
|
+
self.release() # libero el flag de reserva de turno para crar un browser
|
342
|
+
raise "Error removing Multilogin profile: #{res.to_s}"
|
343
|
+
end
|
344
|
+
elsif res['status'] != 'OK'
|
345
|
+
if i<max
|
346
|
+
self.release() # libero el flag de reserva de turno para crar un browser
|
347
|
+
raise "Error removing Multilogin profile: #{res['status'].to_s}"
|
348
|
+
end
|
349
|
+
else
|
350
|
+
url_2 = res['value']
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
if !url_2.nil?
|
355
|
+
#uri_2 = URI.parse(url_2)
|
356
|
+
#req_2 = Net::HTTP::Get.new(url_2)
|
357
|
+
#res_2 = Net::HTTP.start(uri_2.host, uri_2.port, :use_ssl => true, :verify_mode => OpenSSL::SSL::VERIFY_NONE) {|http|
|
358
|
+
# http.request(req_2)
|
359
|
+
#}
|
360
|
+
@@driver = Selenium::WebDriver.for(:remote, :url => url_2, :desired_capabilities => nil)
|
361
|
+
bridge = @@driver.instance_variable_get(:@bridge)
|
362
|
+
service = bridge.instance_variable_get(:@service)
|
363
|
+
process = service.instance_variable_get(:@process)
|
364
|
+
@@pid = process.pid.to_s # es el PID del chromedriver
|
365
|
+
@@driver.manage.window().maximize()
|
366
|
+
@@browser = Watir::Browser.new(@@driver)
|
367
|
+
self.updateProfileList
|
368
|
+
end
|
369
|
+
|
370
|
+
rescue => e
|
371
|
+
p1 = PROCESS.list().select { |p| p[:ppid] == PROCESS.pid && p[:executablepath] =~ /chromedriver(.*)\.exe/ }.first
|
372
|
+
if (p1 != nil)
|
373
|
+
@@pid = p1[:pid]
|
374
|
+
self.updateProfileList
|
375
|
+
end
|
376
|
+
self.releaseProfileList()
|
377
|
+
raise e
|
378
|
+
end
|
379
|
+
|
380
|
+
#
|
381
|
+
self.releaseProfileList()
|
382
|
+
|
383
|
+
# libero el flag de reserva de turno para crar un browser
|
384
|
+
self.release()
|
385
|
+
end
|
386
|
+
|
387
|
+
#
|
388
|
+
def self.launch_chrome(proxy=nil, load_timeout=DEFAULT_LOAD_TIMEOUT, user_agent=nil, profile_name=nil, chromedriver_path=nil, chromedriver_signature=nil)
|
389
|
+
#
|
390
|
+
@@browser = nil
|
391
|
+
@@driver = nil
|
392
|
+
@@profile_name = profile_name
|
393
|
+
|
394
|
+
#
|
395
|
+
fname = PROFILE_PID_LOCK_FILENAME.gsub('%PRFILE_NAME%', @@profile_name)
|
396
|
+
@@fd_profile = File.open(fname,"w")
|
397
|
+
|
398
|
+
#
|
399
|
+
agent = @@arAgents[0] if user_agent.nil?
|
400
|
+
agent = user_agent if !user_agent.nil?
|
401
|
+
|
402
|
+
# #959 - valido que el parametro agents sea un array - porque si se pasa un string se lo procesa como array y se toma un UA name de un char.
|
403
|
+
#raise "Array expected in parameter agents in BrowserFactory.create." if !(agent.is_a? String)
|
404
|
+
|
405
|
+
#
|
406
|
+
while (@@browser == nil)
|
407
|
+
|
408
|
+
# Levantar el flag de reserva a mi favor
|
409
|
+
self.lock()
|
410
|
+
|
411
|
+
begin
|
412
|
+
client = Selenium::WebDriver::Remote::Http::Default.new
|
413
|
+
begin
|
414
|
+
client.read_timeout = load_timeout # for newest selenium webdriver version
|
415
|
+
rescue => e
|
416
|
+
client.timeout = load_timeout # deprecated in newest selenium webdriver version
|
417
|
+
end
|
418
|
+
|
419
|
+
# se agregan extensiones por el issue #1171
|
420
|
+
#
|
421
|
+
# UPDATE: Las extensiones en JavaScript para eliminar rastros, dejan rastros en si mismas.
|
422
|
+
#
|
423
|
+
switches = ["lang=en", "--log-level=3", "--user-agent=#{agent}", "disable-infobars", "load-extension=#{File.absolute_path('../chrome_extension')}"]
|
424
|
+
|
425
|
+
#
|
426
|
+
if (profile_name!=nil)
|
427
|
+
|
428
|
+
# issue #964
|
429
|
+
filename = "#{profile_name}/#{profile_name}/Default/Preferences"
|
430
|
+
if ( File.file?(filename) == true )
|
431
|
+
File.delete(filename)
|
432
|
+
end
|
433
|
+
|
434
|
+
# issue #964
|
435
|
+
filename = "#{profile_name}/#{profile_name}/Local State"
|
436
|
+
if ( File.file?(filename) == true )
|
437
|
+
File.delete(filename)
|
438
|
+
end
|
439
|
+
|
440
|
+
# configuro el browser para que use este perfil
|
441
|
+
switches += ["--user-data-dir=#{profile_name}/#{profile_name}"]
|
442
|
+
|
443
|
+
end
|
444
|
+
|
445
|
+
if (proxy!=nil)
|
446
|
+
switches += proxy.chrome_switches
|
447
|
+
end
|
448
|
+
|
449
|
+
self.lockProfileList()
|
450
|
+
|
451
|
+
begin
|
452
|
+
|
453
|
+
Selenium::WebDriver::Chrome.driver_path = chromedriver_path if !chromedriver_path.nil?
|
454
|
+
Selenium::WebDriver::Chrome.driver_path = DEFAULT_CHROMEDRIVER_PATH if chromedriver_path.nil?
|
455
|
+
|
456
|
+
@@driver = Selenium::WebDriver.for :chrome, :switches => switches
|
457
|
+
bridge = @@driver.instance_variable_get(:@bridge)
|
458
|
+
service = bridge.instance_variable_get(:@service)
|
459
|
+
process = service.instance_variable_get(:@process)
|
460
|
+
@@pid = process.pid.to_s # es el PID del chromedriver
|
461
|
+
@@driver.manage.window().maximize()
|
462
|
+
@@browser = PampaBrowser.new(@@driver)
|
463
|
+
@@browser.proxy = proxy.clone if !proxy.nil?
|
464
|
+
@@browser.agent_name = agent
|
465
|
+
@@browser.profile_name = profile_name
|
466
|
+
self.updateProfileList
|
467
|
+
rescue => e
|
468
|
+
p1 = PROCESS.list().select { |p| p[:ppid] == PROCESS.pid && p[:executablepath] =~ /chromedriver(.*)\.exe/ }.first
|
469
|
+
if (p1 != nil)
|
470
|
+
@@pid = p1[:pid]
|
471
|
+
self.updateProfileList
|
472
|
+
end
|
473
|
+
self.releaseProfileList()
|
474
|
+
raise e
|
475
|
+
end
|
476
|
+
self.releaseProfileList()
|
477
|
+
|
478
|
+
# Liberar el flag de reserva de creacion del browser
|
479
|
+
self.release()
|
480
|
+
|
481
|
+
#
|
482
|
+
rescue => e # TODO: Se atrapa una expecion porque sigue ocurreiendo el error reportado en el issue #134 y #129
|
483
|
+
# Liberar el flag de reserva de creacion del browser
|
484
|
+
self.release()
|
485
|
+
|
486
|
+
# disparar la exepcion
|
487
|
+
raise e
|
488
|
+
end
|
489
|
+
end # while
|
490
|
+
|
491
|
+
#
|
492
|
+
@@browser
|
493
|
+
end # create
|
494
|
+
|
495
|
+
|
496
|
+
end # class
|
497
|
+
|
498
|
+
end # module BlackStack
|
data/lib/proxy.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require_relative './baseproxy'
|
2
|
+
|
3
|
+
module BlackStack
|
4
|
+
|
5
|
+
# proxy almacenado en la base de datos
|
6
|
+
class Proxy < Sequel::Model(:proxy)
|
7
|
+
include BaseProxy
|
8
|
+
|
9
|
+
Proxy.dataset = Proxy.dataset.disable_insert_output
|
10
|
+
|
11
|
+
def self.availablesWithStealth(
|
12
|
+
process,
|
13
|
+
discretion_seconds_to_wait_if_proxy_has_been_reserved_but_didnt_finish,
|
14
|
+
discretion_seconds_to_wait_if_proxy_has_finished_successfully,
|
15
|
+
discretion_seconds_to_wait_if_proxy_has_been_blocked,
|
16
|
+
discretion_seconds_to_wait_for_long_sleep,
|
17
|
+
discretion_stop_from,
|
18
|
+
discretion_stop_until
|
19
|
+
)
|
20
|
+
|
21
|
+
q =
|
22
|
+
"SELECT COUNT(*) c FROM dbo.fnProxyDiscretion(" +
|
23
|
+
"'#{process}', " +
|
24
|
+
"#{discretion_seconds_to_wait_if_proxy_has_been_reserved_but_didnt_finish.to_s}, " +
|
25
|
+
"#{discretion_seconds_to_wait_if_proxy_has_finished_successfully.to_s}, " +
|
26
|
+
"#{discretion_seconds_to_wait_if_proxy_has_been_blocked.to_s}, " +
|
27
|
+
"#{discretion_seconds_to_wait_for_long_sleep.to_s}, " +
|
28
|
+
"'#{discretion_stop_from.to_s}', " +
|
29
|
+
"'#{discretion_stop_until.to_s}')"
|
30
|
+
|
31
|
+
return DB[q].first[:c].to_i
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.getWithStealth(
|
35
|
+
process,
|
36
|
+
discretion_seconds_to_wait_if_proxy_has_been_reserved_but_didnt_finish,
|
37
|
+
discretion_seconds_to_wait_if_proxy_has_finished_successfully,
|
38
|
+
discretion_seconds_to_wait_if_proxy_has_been_blocked,
|
39
|
+
discretion_seconds_to_wait_for_long_sleep,
|
40
|
+
discretion_stop_from,
|
41
|
+
discretion_stop_until
|
42
|
+
)
|
43
|
+
rid = guid()
|
44
|
+
DB.execute(
|
45
|
+
"EXEC dbo.reserveProxyWithDiscretion " +
|
46
|
+
"'#{rid}', " +
|
47
|
+
"'#{process}', " +
|
48
|
+
"#{discretion_seconds_to_wait_if_proxy_has_been_reserved_but_didnt_finish.to_s}, " +
|
49
|
+
"#{discretion_seconds_to_wait_if_proxy_has_finished_successfully.to_s}, " +
|
50
|
+
"#{discretion_seconds_to_wait_if_proxy_has_been_blocked.to_s}, " +
|
51
|
+
"#{discretion_seconds_to_wait_for_long_sleep.to_s}, " +
|
52
|
+
"'#{discretion_stop_from.to_s}', " +
|
53
|
+
"'#{discretion_stop_until.to_s}'"
|
54
|
+
)
|
55
|
+
BlackStack::Proxy.where(:reservation_id=>rid).first
|
56
|
+
end
|
57
|
+
|
58
|
+
def startJob()
|
59
|
+
DB.execute("UPDATE proxy SET reservation_start_time=GETDATE() WHERE [id]='#{self.id}'")
|
60
|
+
end
|
61
|
+
|
62
|
+
# TODO: Reemplazar el parametro records_before_long_sleep por un registro en la tabla PARAMS
|
63
|
+
def endJob(id_object, result, description, records_before_long_sleep)
|
64
|
+
# => #464
|
65
|
+
=begin
|
66
|
+
DB.execute("UPDATE proxy SET reservation_end_time=GETDATE(), reservation_result='#{result.to_s}', reservation_description='#{description.to_s}' WHERE [id]='#{self.id}'")
|
67
|
+
|
68
|
+
DB.execute(
|
69
|
+
"EXEC dbo.endProxyJob " +
|
70
|
+
"'#{self.id}', " +
|
71
|
+
"'#{id_object}', " +
|
72
|
+
"#{result.to_s}, " +
|
73
|
+
"'#{description.to_s}', " +
|
74
|
+
"#{records_before_long_sleep.to_s} "
|
75
|
+
)
|
76
|
+
=end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end # module BlackStack
|
data/lib/remoteproxy.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'mini_magick'
|
2
|
+
require 'rest_client'
|
3
|
+
require 'pampa_workers'
|
4
|
+
|
5
|
+
require_relative './baseproxy'
|
6
|
+
require_relative './remoteproxy'
|
7
|
+
require_relative './browserfactory'
|
8
|
+
|
9
|
+
module BlackStack
|
10
|
+
|
11
|
+
module StealthBrowserAutomation
|
12
|
+
|
13
|
+
module Multilogin
|
14
|
+
#
|
15
|
+
@@auth_token = nil
|
16
|
+
@@mla_version = nil
|
17
|
+
@@mla_local_port = nil
|
18
|
+
|
19
|
+
def self.auth_token()
|
20
|
+
@@auth_token
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.mla_version()
|
24
|
+
@@mla_version
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.mla_local_port()
|
28
|
+
@@mla_local_port
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.set(h)
|
32
|
+
@@auth_token = h[:auth_token]
|
33
|
+
@@mla_version = h[:mla_version]
|
34
|
+
@@mla_local_port = h[:mla_local_port]
|
35
|
+
end
|
36
|
+
|
37
|
+
# returns the profileId of the new Mimic profile
|
38
|
+
def self.create_profile(data)
|
39
|
+
url = "https://api.multiloginapp.com/v2/profile?token=#{BlackStack::StealthBrowserAutomation::Multilogin::auth_token.to_s}&mlaVersion=#{BlackStack::StealthBrowserAutomation::Multilogin::mla_version.to_s}"
|
40
|
+
uri = URI(url)
|
41
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => true, :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
|
42
|
+
req = Net::HTTP::Post.new(uri)
|
43
|
+
req['Content-Type'] = 'application/json'
|
44
|
+
req.body = data.to_json
|
45
|
+
res = JSON.parse(http.request(req).body)
|
46
|
+
raise "Error creating Multilogin profile: #{res.to_s}" if !res.has_key?('uuid')
|
47
|
+
return res['uuid']
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# returns the profileId of the new Mimic profile
|
52
|
+
def self.remove_profile(profile_id)
|
53
|
+
url = "https://api.multiloginapp.com/v1/profile/remove?token=#{BlackStack::StealthBrowserAutomation::Multilogin::auth_token.to_s}&profileId=#{profile_id}"
|
54
|
+
uri = URI.parse(url)
|
55
|
+
req = Net::HTTP::Get.new(url)
|
56
|
+
res = Net::HTTP.start(uri.host, uri.port, :use_ssl => true, :verify_mode => OpenSSL::SSL::VERIFY_NONE) {|http|
|
57
|
+
http.request(req)
|
58
|
+
}
|
59
|
+
res = JSON.parse(res.body)
|
60
|
+
raise "Error removing Multilogin profile: #{res.to_s}" if !res.has_key?('status')
|
61
|
+
raise "Error removing Multilogin profile: #{res['status'].to_s}" if res['status'] != 'OK'
|
62
|
+
end # def self.delete_profile
|
63
|
+
end # module MultiLogin
|
64
|
+
|
65
|
+
#
|
66
|
+
def self.require_db_classes()
|
67
|
+
# You have to load all the Sinatra classes after connect the database.
|
68
|
+
require_relative '../lib/proxy.rb'
|
69
|
+
end
|
70
|
+
|
71
|
+
end # module StealthBrowserAutomation
|
72
|
+
|
73
|
+
end # module BlackStack
|
metadata
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: stealth_browser_automation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.7
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Leandro Daniel Sardi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-12-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: mini_magick
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.9.3
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 4.9.3
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 4.9.3
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 4.9.3
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rest-client
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 2.0.2
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 2.0.2
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 2.0.2
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 2.0.2
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: websocket
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 1.2.8
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 1.2.8
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.2.8
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 1.2.8
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: json
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 1.8.1
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.8.1
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.8.1
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.8.1
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: tiny_tds
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 1.0.5
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.0.5
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.0.5
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 1.0.5
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: sequel
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: 4.28.0
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 4.28.0
|
123
|
+
type: :runtime
|
124
|
+
prerelease: false
|
125
|
+
version_requirements: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - "~>"
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: 4.28.0
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: 4.28.0
|
133
|
+
- !ruby/object:Gem::Dependency
|
134
|
+
name: blackstack_commons
|
135
|
+
requirement: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - "~>"
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 0.0.20
|
140
|
+
- - ">="
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: 0.0.20
|
143
|
+
type: :runtime
|
144
|
+
prerelease: false
|
145
|
+
version_requirements: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - "~>"
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.0.20
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 0.0.20
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: simple_cloud_logging
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 1.1.16
|
160
|
+
- - ">="
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 1.1.16
|
163
|
+
type: :runtime
|
164
|
+
prerelease: false
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - "~>"
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: 1.1.16
|
170
|
+
- - ">="
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 1.1.16
|
173
|
+
- !ruby/object:Gem::Dependency
|
174
|
+
name: simple_command_line_parser
|
175
|
+
requirement: !ruby/object:Gem::Requirement
|
176
|
+
requirements:
|
177
|
+
- - "~>"
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: 1.1.1
|
180
|
+
- - ">="
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: 1.1.1
|
183
|
+
type: :runtime
|
184
|
+
prerelease: false
|
185
|
+
version_requirements: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - "~>"
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: 1.1.1
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: 1.1.1
|
193
|
+
- !ruby/object:Gem::Dependency
|
194
|
+
name: pampa_workers
|
195
|
+
requirement: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - "~>"
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: 0.0.39
|
200
|
+
- - ">="
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: 0.0.39
|
203
|
+
type: :runtime
|
204
|
+
prerelease: false
|
205
|
+
version_requirements: !ruby/object:Gem::Requirement
|
206
|
+
requirements:
|
207
|
+
- - "~>"
|
208
|
+
- !ruby/object:Gem::Version
|
209
|
+
version: 0.0.39
|
210
|
+
- - ">="
|
211
|
+
- !ruby/object:Gem::Version
|
212
|
+
version: 0.0.39
|
213
|
+
description: 'THIS GEM IS STILL IN DEVELOPMENT STAGE. Find documentation here: https://github.com/leandrosardi/stealth_browser_automation.'
|
214
|
+
email: leandro.sardi@expandedventure.com
|
215
|
+
executables: []
|
216
|
+
extensions: []
|
217
|
+
extra_rdoc_files: []
|
218
|
+
files:
|
219
|
+
- lib/baseproxy.rb
|
220
|
+
- lib/browserfactory.rb
|
221
|
+
- lib/proxy.rb
|
222
|
+
- lib/proxyprovider.rb
|
223
|
+
- lib/remoteproxy.rb
|
224
|
+
- lib/stealth_browser_automation.rb
|
225
|
+
homepage: https://rubygems.org/gems/stealth_browser_automation
|
226
|
+
licenses:
|
227
|
+
- MIT
|
228
|
+
metadata: {}
|
229
|
+
post_install_message:
|
230
|
+
rdoc_options: []
|
231
|
+
require_paths:
|
232
|
+
- lib
|
233
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
234
|
+
requirements:
|
235
|
+
- - ">="
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
version: '0'
|
238
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
239
|
+
requirements:
|
240
|
+
- - ">="
|
241
|
+
- !ruby/object:Gem::Version
|
242
|
+
version: '0'
|
243
|
+
requirements: []
|
244
|
+
rubyforge_project:
|
245
|
+
rubygems_version: 2.4.5.1
|
246
|
+
signing_key:
|
247
|
+
specification_version: 4
|
248
|
+
summary: THIS GEM IS STILL IN DEVELOPMENT STAGE. Scrape the web and automate social
|
249
|
+
accounts with not footprints. Track the browser acivity. Manage proxies stealthly.
|
250
|
+
test_files: []
|