stealth_browser_automation 0.0.7
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/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: []
|