Class: AdsPowerClient
- Inherits:
-
Object
- Object
- AdsPowerClient
- Defined in:
- lib/adspower-client.rb
Constant Summary collapse
- CLOUD_API_BASE =
'https://api.adspower.com/v1'- COUNTRY_LANG =
Constante generada en tiempo de ejecución:
ISO3166::Country.all.each_with_object({}) do |country, h| # El primer idioma oficial (ISO 639-1) que encuentre: language_code = country.languages&.first || 'en' # Construimos la etiqueta BCP47 Language-Region: h[country.alpha2] = "#{language_code}-#{country.alpha2}" end.freeze
- LOCK_FILE =
'/tmp/adspower_api_lock'- @@drivers =
control over the drivers created, in order to not create the same driver twice and not generate memory leaks. reference: github.com/leandrosardi/adspower-client/issues/4
{}
Instance Attribute Summary collapse
-
#adspower_default_browser_version ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu.
-
#adspower_listener ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu.
-
#cloud_token ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu.
-
#key ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu.
-
#port ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu.
-
#server_log ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu.
Class Method Summary collapse
-
.cleanup(id) ⇒ Object
Quit and remove a specific driver (safe).
-
.cleanup_all ⇒ Object
Quit and remove all drivers (safe).
- .drivers ⇒ Object
Instance Method Summary collapse
-
#acquire_lock ⇒ Object
Acquire the lock.
-
#check(id) ⇒ Object
Check if the browser session for the given user profile is active.
-
#cloud_profile_quota ⇒ Object
Return a hash with: • :limit ⇒ total profile slots allowed (-1 = unlimited) • :used ⇒ number of profiles currently created • :remaining ⇒ slots left (nil if unlimited) Fetch your real profile quota from the Cloud API.
-
#create ⇒ Object
Create a new user profile via API call and return the ID of the created user.
-
#create2(name:, proxy_config:, group_id: '0', browser_version: nil, os: 'linux64', fingerprint: nil, platform: '', tabs: [], username: '', password: '', fakey: '', cookie: nil) ⇒ Object
Create a new desktop profile with: • name, proxy, fingerprint, etc (unchanged) • platform (e.g. “linkedin.com”) • tabs (Array of URLs to open) • username / password / fakey for that platform.
-
#delete(id) ⇒ Object
Delete a user profile via API call.
-
#driver(id, headless = false) ⇒ Object
Attach to the existing browser session with Selenium WebDriver.
- #driver2(id, headless: false, read_timeout: 180) ⇒ Object
-
#find_group_id_by_name(name) ⇒ Object
Find the numeric ID for a given group name (exact match).
-
#geolocate(ip) ⇒ Object
Lookup GeoIP gratuito (freegeoip.app) y parseo básico.
-
#html(url) ⇒ Object
DEPRECATED - Use Zyte instead of this method.
-
#initialize(h = {}) ⇒ AdsPowerClient
constructor
A new instance of AdsPowerClient.
-
#list_groups ⇒ Object
Fetch all groups.
-
#list_profiles(group_id: nil) ⇒ Object
returns an Array of profile‑hashes from the local API.
-
#online? ⇒ Boolean
Send a GET request to “#url/status” and return true if it responded successfully.
-
#profile_count(group_id: nil) ⇒ Object
Count current profiles (optionally filtered by group).
-
#release_lock ⇒ Object
Release the lock.
-
#server_pids ⇒ Object
Return an array of PIDs of all the adspower_global processes running on the local computer.
-
#server_start(timeout = 30) ⇒ Object
Run async command to start AdsPower server in headless mode.
-
#server_stop ⇒ Object
Kill all the adspower_global processes running on the local computer.
-
#start(id, headless = false) ⇒ Object
Start the browser with the given user profile and return the connection details.
-
#stop(id) ⇒ Object
Stop the browser session for the given user profile.
-
#with_lock ⇒ Object
Wrapper method for critical sections.
Constructor Details
#initialize(h = {}) ⇒ AdsPowerClient
Returns a new instance of AdsPowerClient.
31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/adspower-client.rb', line 31 def initialize(h={}) self.key = h[:key] # mandatory self.port = h[:port] || '50325' self.server_log = h[:server_log] || '~/adspower-client.log' self.adspower_listener = h[:adspower_listener] || 'http://127.0.0.1' # DEPRECATED self.adspower_default_browser_version = h[:adspower_default_browser_version] || '116' # PENDING self.cloud_token = h[:cloud_token] end |
Instance Attribute Details
#adspower_default_browser_version ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
23 24 25 |
# File 'lib/adspower-client.rb', line 23 def adspower_default_browser_version @adspower_default_browser_version end |
#adspower_listener ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
23 24 25 |
# File 'lib/adspower-client.rb', line 23 def adspower_listener @adspower_listener end |
#cloud_token ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
23 24 25 |
# File 'lib/adspower-client.rb', line 23 def cloud_token @cloud_token end |
#key ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
23 24 25 |
# File 'lib/adspower-client.rb', line 23 def key @key end |
#port ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
23 24 25 |
# File 'lib/adspower-client.rb', line 23 def port @port end |
#server_log ⇒ Object
reference: localapi-doc-en.adspower.com/ reference: localapi-doc-en.adspower.com/docs/Rdw7Iu
23 24 25 |
# File 'lib/adspower-client.rb', line 23 def server_log @server_log end |
Class Method Details
.cleanup(id) ⇒ Object
Quit and remove a specific driver (safe)
117 118 119 120 121 122 123 124 125 126 |
# File 'lib/adspower-client.rb', line 117 def self.cleanup(id) drv = @@drivers[id] begin drv.quit if drv rescue => e # best-effort: ignore but log if desired ensure @@drivers.delete(id) end end |
.cleanup_all ⇒ Object
Quit and remove all drivers (safe)
103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/adspower-client.rb', line 103 def self.cleanup_all @@drivers.keys.each do |id| drv = @@drivers[id] begin drv.quit if drv rescue => e # best-effort: ignore but log if desired ensure @@drivers.delete(id) end end end |
.drivers ⇒ Object
98 99 100 |
# File 'lib/adspower-client.rb', line 98 def self.drivers @@drivers end |
Instance Method Details
#acquire_lock ⇒ Object
Acquire the lock
45 46 47 48 |
# File 'lib/adspower-client.rb', line 45 def acquire_lock @lockfile ||= File.open(LOCK_FILE, File::CREAT | File::RDWR) @lockfile.flock(File::LOCK_EX) end |
#check(id) ⇒ Object
Check if the browser session for the given user profile is active.
517 518 519 520 521 522 523 524 525 |
# File 'lib/adspower-client.rb', line 517 def check(id) with_lock do url = "#{self.adspower_listener}:#{port}/api/v1/browser/active?user_id=#{id}" uri = URI.parse(url) res = Net::HTTP.get(uri) return false if JSON.parse(res)['msg'] != 'success' JSON.parse(res)['data']['status'] == 'Active' end end |
#cloud_profile_quota ⇒ Object
Return a hash with:
• :limit ⇒ total profile slots allowed (-1 = unlimited)
• :used ⇒ number of profiles currently created
• :remaining ⇒ slots left (nil if unlimited)
Fetch your real profile quota from the Cloud API
240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 |
# File 'lib/adspower-client.rb', line 240 def cloud_profile_quota uri = URI("#{CLOUD_API_BASE}/account/get_info") req = Net::HTTP::Get.new(uri) req['Authorization'] = "Bearer #{self.cloud_token}" res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http| http.request(req) end data = JSON.parse(res.body) raise "Cloud API error: #{data['msg']}" unless data['code'] == 0 allowed = data['data']['total_profiles_allowed'].to_i used = data['data']['profiles_used'].to_i remaining = allowed < 0 ? nil : (allowed - used) { limit: allowed, used: used, remaining: remaining } end |
#create ⇒ Object
Create a new user profile via API call and return the ID of the created user.
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/adspower-client.rb', line 261 def create with_lock do url = "#{self.adspower_listener}:#{port}/api/v1/user/create" body = { 'group_id' => '0', 'proxyid' => '1', 'fingerprint_config' => { 'browser_kernel_config' => {"version": self.adspower_default_browser_version, "type": "chrome"} } } # API call res = BlackStack::Netting.call_post(url, body) ret = JSON.parse(res.body) raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success' ret['data']['id'] end end |
#create2(name:, proxy_config:, group_id: '0', browser_version: nil, os: 'linux64', fingerprint: nil, platform: '', tabs: [], username: '', password: '', fakey: '', cookie: nil) ⇒ Object
Create a new desktop profile with:
• name, proxy, fingerprint, etc (unchanged)
• platform (e.g. "linkedin.com")
• tabs (Array of URLs to open)
• username / password / fakey for that platform
314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 |
# File 'lib/adspower-client.rb', line 314 def create2( name:, proxy_config:, group_id: '0', browser_version: nil, os: 'linux64', # new: one of linux64, mac-x64, mac-arm64, win32, win64 fingerprint: nil, platform: '', # default: no platform tabs: [], # default: no tabs to open username: '', # default: no login password: '', # default: no password fakey: '', # leave blank if no 2FA cookie: nil # import cookies - Type: Text - Format: JSON - username/password are ignored if cookie is not nil. ) browser_version ||= adspower_default_browser_version # 0) Resolve full Chrome version ───────────────────────────── # Fetch the list of known-good Chrome versions and pick the highest uri = URI('https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json') resp = Net::HTTP.get_response(uri) unless resp.is_a?(Net::HTTPSuccess) raise "Error fetching Chrome versions: HTTP #{resp.code}" end listing = JSON.parse(resp.body) entries = listing['versions'] || [] # keep only those entries whose version matches prefix *and* has a download for our OS matches = entries. select { |e| e['version'].start_with?("#{browser_version}.") && e.dig('downloads','chrome').any? { |d| d['platform'] == os } }. map { |e| e['version'] } if matches.empty? raise "Chrome version '#{browser_version}' not found in known-good versions list" end # pick the highest patch/build by semantic compare full_version = matches .map { |ver| ver.split('.').map(&:to_i) } .max .join('.') # 1) Hacemos GeoIP sobre la IP del proxy geo = geolocate(proxy_config[:ip]) lang = COUNTRY_LANG[geo[:country_code]] || "en-US" screen_res = "1920_1080" with_lock do url = "#{adspower_listener}:#{port}/api/v2/browser-profile/create" body = { # ─── GENERAL & PROXY ───────────────────────────── 'name' => name, 'group_id' => group_id, 'user_proxy_config' => { 'proxy_soft' => proxy_config[:proxy_soft] || 'other', 'proxy_type' => proxy_config[:proxy_type] || 'socks5', 'proxy_host' => proxy_config[:ip], 'proxy_port' => proxy_config[:port].to_s, 'proxy_user' => proxy_config[:user], 'proxy_password' => proxy_config[:password], # ─── FORCE ALL DNS THROUGH PROXY ───────────────── # Avoid DNS-Leak "proxy_dns": 1, # 1 = yes, 0 = no "dns_servers": ["8.8.8.8","8.8.4.4"] # optional: your choice of DNS }, # ─── PLATFORM ───────────────────────────────────── 'platform' => platform, # must be one of AdsPower’s supported “sites” 'tabs' => tabs, # array of URLs to open 'username' => username, 'password' => password, 'fakey' => fakey, # 2FA, if any 'cookie' => , # ─── FINGERPRINT ────────────────────────────────── "fingerprint_config" => fingerprint || { # ─── 0) DNS Leak Prevention ─────────────────────────── # Even with “proxy_dns” forced on, a few ISPs will still # silently intercept every UDP:53 out of your AdsPower VPS # and shove it into their own resolver farm (the classic # “transparent DNS proxy” attack that BrowserScan is warning you about). # # Because you refuse to hot-patch your Chrome via extra args or CDP, # the only way to survive an ISP-level hijack is to push all name lookups # into an encrypted channel that the ISP simply can’t touch: DNS-over-HTTPS (DoH). # # Here’s the minimal change you need to bake into your AdsPower profile at # creation time so that every DNS query happens inside Chrome’s DoH stack: # "extra_launch_flags" => [ # === DNS over HTTPS only === "--enable-features=DnsOverHttps", "--dns-over-https-mode=secure", "--dns-over-https-templates=https://cloudflare-dns.com/dns-query", "--disable-ipv6", # === hide “Chrome is being controlled…” banner === # # Even though you baked in the DoH flags under extra_launch_flags, # you never told Chrome to hide its “automation” banners or black-hole # all other DNS lookups — and BrowserScan still sees those UDP:53 calls # leaking out. # # What you need is to push three more flags into your profile creation, # and then attach with the exact same flags when Selenium hooks in. # "--disable-blink-features=AutomationControlled", "--disable-infobars", "--disable-features=TranslateUI", # optional but reduces tell-tale infobars "--host-resolver-rules=MAP * 0.0.0.0,EXCLUDE localhost,EXCLUDE cloudflare-dns.com" ], # ─── 1) Kernel & versión ─────────────────────────── "browser_kernel_config" => { "version" => browser_version, # aquí usamos el parámetro "type" => "chrome" }, # ─── 2) Timezone & locale ────────────────────────── "automatic_timezone" => "1", #"timezone" => geo[:time_zone], "language" => [ lang ], # ─── 3) User-Agent coherente ─────────────────────── "ua_category" => "desktop", 'ua' => "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/#{full_version} Safari/537.36", "is_mobile" => false, # ─── 4) Pantalla y plataforma ────────────────────── # It turns out that “Based on User-Agent” is purely a UI setting #"screen_resolution" => screen_res, "1920_1080" "platform" => "Linux x86_64", # ─── 5) Canvas & WebGL custom ───────────────────── "canvas" => "1", "webgl_image" => "1", "webgl" => "0", # 0=deshabilitado, 2=modo custom, 3=modo random-match "webgl_config" => { "unmasked_vendor" => "Intel Inc.", "unmasked_renderer" => "ANGLE (Intel, Mesa Intel(R) Xe Graphics (TGL GT2), OpenGL 4.6)", "webgpu" => { "webgpu_switch" => "1" } }, # ─── 6) Resto de ajustes ─────────────────────────── "webrtc" => "disabled", # WebRTC sí admite “disabled” "flash" => "block", # Flash únicamente “allow” o “block” "fonts" => [] # usar fonts por defecto } } res = BlackStack::Netting.call_post(url, body) ret = JSON.parse(res.body) raise "Error creating profile: #{ret['msg']}" unless ret['code'] == 0 ret['data']['profile_id'] end end |
#delete(id) ⇒ Object
Delete a user profile via API call.
474 475 476 477 478 479 480 481 482 483 484 485 486 |
# File 'lib/adspower-client.rb', line 474 def delete(id) with_lock do url = "#{self.adspower_listener}:#{port}/api/v1/user/delete" body = { 'api_key' => self.key, 'user_ids' => [id], } # API call res = BlackStack::Netting.call_post(url, body) ret = JSON.parse(res.body) raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success' end end |
#driver(id, headless = false) ⇒ Object
Attach to the existing browser session with Selenium WebDriver.
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 |
# File 'lib/adspower-client.rb', line 528 def driver(id, headless=false) # Return the existing driver if it's still active. old = @@drivers[id] return old if old # Otherwise, start the driver ret = self.start(id, headless) # Attach test execution to the existing browser url = ret['data']['ws']['selenium'] opts = Selenium::WebDriver::Chrome::Options.new opts.add_option("debuggerAddress", url) # Connect to the existing browser driver = Selenium::WebDriver.for(:chrome, options: opts) # Save the driver @@drivers[id] = driver # Return the driver driver end |
#driver2(id, headless: false, read_timeout: 180) ⇒ Object
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 |
# File 'lib/adspower-client.rb', line 551 def driver2(id, headless: false, read_timeout: 180) #return @@drivers[id] if @@drivers[id] # If we have a cached driver, verify it's still valid if @@drivers[id] begin # quick, non-destructive sanity check: ask for window_handles @@drivers[id].window_handles return @@drivers[id] rescue Selenium::WebDriver::Error::InvalidSessionIdError, Selenium::WebDriver::Error::NoSuchWindowError, Errno::ECONNREFUSED => e # stale/broken driver: best-effort cleanup and continue to create a new one #warn "detected stale driver for #{id}: #{e.class}: #{e.message}" self.class.cleanup(id) rescue => e #warn "unexpected error checking cached driver: #{e.class}: #{e.message}" self.class.cleanup(id) end end # 1) start the AdsPower profile / grab its WebSocket URL data = start(id, headless)['data'] ws = data['ws']['selenium'] # e.g. "127.0.0.1:XXXXX" # 2) attach with DevTools (no more excludeSwitches or caps!) opts = Selenium::WebDriver::Chrome::Options.new opts.debugger_address = ws opts.add_argument('--headless') if headless http = Selenium::WebDriver::Remote::Http::Default.new http.read_timeout = read_timeout driver = Selenium::WebDriver.for(:chrome, options: opts, http_client: http) driver.execute_cdp( 'Page.addScriptToEvaluateOnNewDocument', source: <<~JS // 1) remove any leftover cdc_… / webdriver hooks for (const k of Object.getOwnPropertyNames(window)) { if (k.startsWith('cdc_') || k.includes('webdriver')) { try { delete window[k]; } catch(e){} } } // 2) stub out window.chrome so Chrome-based detection thinks this is “normal” Chrome window.chrome = { runtime: {} }; JS ) @@drivers[id] = driver driver end |
#find_group_id_by_name(name) ⇒ Object
Find the numeric ID for a given group name (exact match)
187 188 189 190 |
# File 'lib/adspower-client.rb', line 187 def find_group_id_by_name(name) grp = list_groups.find { |g| g['group_name'].casecmp(name).zero? } grp ? grp['group_id'] : nil end |
#geolocate(ip) ⇒ Object
Lookup GeoIP gratuito (freegeoip.app) y parseo básico
281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
# File 'lib/adspower-client.rb', line 281 def geolocate(ip) uri = URI("https://freegeoip.app/json/#{ip}") res = Net::HTTP.get(uri) h = JSON.parse(res) { country_code: h["country_code"], time_zone: h["time_zone"], latitude: h["latitude"], longitude: h["longitude"] } rescue # Fallback genérico { country_code: "US", time_zone: "America/New_York", latitude: 38.9, longitude: -77.0 } end |
#html(url) ⇒ Object
DEPRECATED - Use Zyte instead of this method.
Create a new profile, start the browser, visit a page, grab the HTML, and clean up.
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 |
# File 'lib/adspower-client.rb', line 610 def html(url) ret = { :profile_id => nil, :html => nil, :status => 'success', } id = nil html = nil begin # Create the profile sleep(1) id = self.create # Update the result ret[:profile_id] = id # Start the profile and attach the driver driver = self.driver(id) # Get HTML driver.get(url) html = driver.page_source # Update the result ret[:html] = html # Stop the profile sleep(1) driver.quit self.stop(id) # Delete the profile sleep(1) self.delete(id) # Reset ID id = nil rescue => e # Stop and delete current profile if an error occurs if id sleep(1) self.stop(id) sleep(1) driver.quit if driver self.delete(id) if id end # Inform the exception ret[:status] = e.to_s # # process interruption # rescue SignalException, SystemExit, Interrupt => e # if id # sleep(1) # Avoid the "Too many request per second" error # self.stop(id) # sleep(1) # Avoid the "Too many request per second" error # driver.quit # self.delete(id) if id # end # if id end # Return ret end |
#list_groups ⇒ Object
Fetch all groups
178 179 180 181 182 183 184 |
# File 'lib/adspower-client.rb', line 178 def list_groups uri = URI("#{adspower_listener}:#{port}/api/v1/group/list?api_key=#{key}") resp = Net::HTTP.get(uri) data = JSON.parse(resp) raise "Error listing groups: #{data['msg']}" unless data['code'] == 0 data['data']['list'] # => [{ "id" => 0, "name" => "Ungrouped" }, …] end |
#list_profiles(group_id: nil) ⇒ Object
returns an Array of profile‑hashes from the local API
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/adspower-client.rb', line 193 def list_profiles(group_id: nil) all = [] page = 1 loop do params = { page: page, limit: 100 } params[:group_id] = group_id if group_id url = "#{adspower_listener}:#{port}/api/v2/browser-profile/list" resp = BlackStack::Netting.call_post(url, params) data = JSON.parse(resp.body) break unless data['code'] == 0 && data.dig('data','list').any? batch = data['data']['list'] all += batch page += 1 end all end |
#online? ⇒ Boolean
Send a GET request to “#url/status” and return true if it responded successfully.
164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/adspower-client.rb', line 164 def online? with_lock do begin url = "#{self.adspower_listener}:#{port}/status" uri = URI.parse(url) res = Net::HTTP.get(uri) return JSON.parse(res)['msg'] == 'success' rescue => e return false end end end |
#profile_count(group_id: nil) ⇒ Object
Count current profiles (optionally filtered by group)
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/adspower-client.rb', line 213 def profile_count(group_id: nil) count = 0 page = 1 loop do params = { page: page, limit: 100 } params[:group_id] = group_id if group_id url = "#{adspower_listener}:#{port}/api/v2/browser-profile/list" res = BlackStack::Netting.call_post(url, params) data = JSON.parse(res.body) raise "Error listing profiles: #{data['msg']}" unless data['code'] == 0 list = data['data']['list'] count += list.size break if list.size < 100 page += 1 end count end |
#release_lock ⇒ Object
Release the lock
51 52 53 54 55 56 57 |
# File 'lib/adspower-client.rb', line 51 def release_lock if @lockfile @lockfile.flock(File::LOCK_UN) # don't close while you expect further operations; close only on object shutdown # @lockfile.close end end |
#server_pids ⇒ Object
Return an array of PIDs of all the adspower_global processes running on the local computer.
78 79 80 |
# File 'lib/adspower-client.rb', line 78 def server_pids `ps aux | grep "adspower_global" | grep -v grep | awk '{print $2}'`.split("\n") end |
#server_start(timeout = 30) ⇒ Object
Run async command to start AdsPower server in headless mode. Wait up to 10 seconds to start the server, or raise an exception.
84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'lib/adspower-client.rb', line 84 def server_start(timeout=30) `xvfb-run --auto-servernum --server-args='-screen 0 1024x768x24' /usr/bin/adspower_global --headless=true --api-key=#{self.key.to_s} --api-port=#{self.port.to_s} > #{self.server_log} 2>&1 &` # wait up to 10 seconds to start the server timeout.times do break if self.online? sleep(1) end # add a delay of 5 more seconds sleep(5) # raise an exception if the server is not running raise "Error: the server is not running" if self.online? == false return end |
#server_stop ⇒ Object
Kill all the adspower_global processes running on the local computer.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/adspower-client.rb', line 144 def server_stop with_lock do self.server_pids.each { |pid| `kill -9 #{pid}` } # clean up @@driver @@drivers.each do |id, driver| begin driver.quit if driver rescue => e # Log or handle the exception if needed ensure @@drivers[id] = nil end end end return end |
#start(id, headless = false) ⇒ Object
Start the browser with the given user profile and return the connection details.
489 490 491 492 493 494 495 496 497 498 |
# File 'lib/adspower-client.rb', line 489 def start(id, headless=false) with_lock do url = "#{self.adspower_listener}:#{port}/api/v1/browser/start?user_id=#{id}&headless=#{headless ? '1' : '0'}" uri = URI.parse(url) res = Net::HTTP.get(uri) ret = JSON.parse(res) raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success' ret end end |
#stop(id) ⇒ Object
Stop the browser session for the given user profile.
501 502 503 504 505 506 507 508 509 510 511 512 513 514 |
# File 'lib/adspower-client.rb', line 501 def stop(id) with_lock do if @@drivers[id] && self.check(id) @@drivers[id].quit @@drivers[id] = nil end uri = URI.parse("#{self.adspower_listener}:#{port}/api/v1/browser/stop?user_id=#{id}") res = Net::HTTP.get(uri) ret = JSON.parse(res) raise "Error: #{ret.to_s}" if ret['msg'].to_s.downcase != 'success' ret end end |
#with_lock ⇒ Object
Wrapper method for critical sections
70 71 72 73 74 75 |
# File 'lib/adspower-client.rb', line 70 def with_lock acquire_lock yield ensure release_lock end |